diff --git a/build.gradle.kts b/build.gradle.kts index 2dd4dbb..44247c8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -48,7 +48,7 @@ kotlin { } } -// explicitApi = ExplicitApiMode.Warning + explicitApi() compilerOptions { freeCompilerArgs.add("-Xexpect-actual-classes") freeCompilerArgs.add("-opt-in=kotlinx.cinterop.BetaInteropApi") diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/context/CalculationContext.kt b/src/commonMain/kotlin/com/lightningkite/reactive/context/CalculationContext.kt index dcbddcf..c2b08b0 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/context/CalculationContext.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/context/CalculationContext.kt @@ -44,7 +44,7 @@ public interface StatusListener : CoroutineContext.Element { * @param status The reactive status of the process * @return A [Release] lambda to stop listening to the process */ - fun watchBackgroundProcess(status: Reactive<*>): Release + public fun watchBackgroundProcess(status: Reactive<*>): Release /** * Called when a reactive calculation is happening in the foreground, so this listener can respond accordingly. @@ -55,7 +55,7 @@ public interface StatusListener : CoroutineContext.Element { * @param status The reactive status of the process * @return A [Release] lambda to stop listening to the process */ - fun watchForegroundProcess(status: Reactive<*>): Release = watchBackgroundProcess(status) + public fun watchForegroundProcess(status: Reactive<*>): Release = watchBackgroundProcess(status) } /** @@ -76,7 +76,7 @@ public interface StatusListener : CoroutineContext.Element { * * @param action The cleanup action to execute when the scope completes */ -fun CoroutineScope.onRemove(action: () -> Unit) { +public fun CoroutineScope.onRemove(action: () -> Unit) { coroutineContext[CoroutineName.Key] this.coroutineContext[Job]?.invokeOnCompletion { action() } } @@ -221,7 +221,7 @@ fun CoroutineScope.onRemove(action: () -> Unit) { public sealed interface ReactiveCoroutineScope : CoroutineScope @Deprecated("No longer needed", ReplaceWith("CoroutineScope")) -typealias CalculationContext = CoroutineScope +public typealias CalculationContext = CoroutineScope /** * Checks whether this [CoroutineScope]'s dispatcher is a main thread dispatcher. @@ -238,7 +238,7 @@ typealias CalculationContext = CoroutineScope * ``` */ @OptIn(ExperimentalStdlibApi::class) -val CoroutineScope.requireMainThread: Boolean get() = coroutineContext[CoroutineDispatcher.Key] is MainCoroutineDispatcher +public val CoroutineScope.requireMainThread: Boolean get() = coroutineContext[CoroutineDispatcher.Key] is MainCoroutineDispatcher /** * Executes the given [action] on the thread associated with this [CoroutineScope]'s dispatcher. @@ -277,7 +277,7 @@ val CoroutineScope.requireMainThread: Boolean get() = coroutineContext[Coroutine * @param action The action to execute on this scope's thread */ @OptIn(ExperimentalStdlibApi::class) -fun CoroutineScope.onThread(action: () -> Unit) { +public fun CoroutineScope.onThread(action: () -> Unit) { val d = coroutineContext[CoroutineDispatcher.Key] ?: return action() if (d.isDispatchNeeded(coroutineContext)) { d.dispatch(coroutineContext, Runnable(action)) @@ -293,4 +293,4 @@ fun CoroutineScope.onThread(action: () -> Unit) { * helping prevent accidental nesting of reactive contexts and providing better IDE support. */ @DslMarker -annotation class ReactiveDsl \ No newline at end of file +public annotation class ReactiveDsl \ No newline at end of file diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/context/CoroutineScopeHelpers.kt b/src/commonMain/kotlin/com/lightningkite/reactive/context/CoroutineScopeHelpers.kt index dc2b320..7a73ba9 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/context/CoroutineScopeHelpers.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/context/CoroutineScopeHelpers.kt @@ -24,9 +24,9 @@ import kotlin.time.Duration.Companion.milliseconds * Interface for helper functions which require an additional [CoroutineScope] context. This will eventually * be removed in favor of context receivers. * */ -interface CoroutineScopeHelpers : CoroutineScope { +public interface CoroutineScopeHelpers : CoroutineScope { @ReactiveDsl - operator fun ((T) -> IGNORED).invoke(actionToCalculate: ReactiveContext.() -> T) = + public operator fun ((T) -> IGNORED).invoke(actionToCalculate: ReactiveContext.() -> T): TypedReactiveContext = this@CoroutineScopeHelpers.reactive(action = { this@invoke(actionToCalculate(this)) }) /** @@ -46,7 +46,7 @@ interface CoroutineScopeHelpers : CoroutineScope { * ``` * */ @ReactiveDsl - operator fun KMutableProperty0.invoke(actionToCalculate: ReactiveContext.() -> T) = this@CoroutineScopeHelpers.reactive(action = { set(actionToCalculate(this)) }) + public operator fun KMutableProperty0.invoke(actionToCalculate: ReactiveContext.() -> T): TypedReactiveContext = this@CoroutineScopeHelpers.reactive(action = { set(actionToCalculate(this)) }) /** @@ -69,7 +69,7 @@ interface CoroutineScopeHelpers : CoroutineScope { * actually optimize and cut out the overhead of a full `reactive` context. * */ @ReactiveDsl - infix fun KMutableProperty0.bind(reactive: Reactive) { + public infix fun KMutableProperty0.bind(reactive: Reactive) { if (reactive is ReactiveValue) { // I did benchmarks, this is just as fast as overloading and easier to use. val release = reactive.addAndRunListener { this@bind.set(reactive.value) } // no need for status listener since result is infallible @@ -89,7 +89,7 @@ interface CoroutineScopeHelpers : CoroutineScope { * Changes to either reactive value will propagate to the other. */ @ReactiveDsl - infix fun MutableReactive.bind(master: MutableReactive) { + public infix fun MutableReactive.bind(master: MutableReactive) { val reportTo = RawReactive(ReactiveState(Unit)) coroutineContext[StatusListener]?.watchBackgroundProcess(reportTo) launch { @@ -127,25 +127,25 @@ interface CoroutineScopeHelpers : CoroutineScope { * Debounces listener notifications by [timeMs] milliseconds using this scope. State is always current. * @see DebounceReactive */ - fun Reactive.debounce(timeMs: Long): Reactive = DebounceReactive(this, this@CoroutineScopeHelpers, timeMs.milliseconds) + public fun Reactive.debounce(timeMs: Long): Reactive = DebounceReactive(this, this@CoroutineScopeHelpers, timeMs.milliseconds) /** * Debounces listener notifications by [duration] using this scope. State is always current. * @see DebounceReactive */ - fun Reactive.debounce(duration: Duration): Reactive = DebounceReactive(this, this@CoroutineScopeHelpers, duration) + public fun Reactive.debounce(duration: Duration): Reactive = DebounceReactive(this, this@CoroutineScopeHelpers, duration) /** * Debounces listener notifications by [timeMs] milliseconds using this scope. * @see DebounceListenable */ - fun Listenable.debounce(timeMs: Long): Listenable = DebounceListenable(this, this@CoroutineScopeHelpers, timeMs.milliseconds) + public fun Listenable.debounce(timeMs: Long): Listenable = DebounceListenable(this, this@CoroutineScopeHelpers, timeMs.milliseconds) /** * Debounces listener notifications by [duration] using this scope. * @see DebounceListenable */ - fun Listenable.debounce(duration: Duration): Listenable = DebounceListenable(this, this@CoroutineScopeHelpers, duration) + public fun Listenable.debounce(duration: Duration): Listenable = DebounceListenable(this, this@CoroutineScopeHelpers, duration) } @OptIn(ExperimentalStdlibApi::class) diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/context/CoroutineTools.kt b/src/commonMain/kotlin/com/lightningkite/reactive/context/CoroutineTools.kt index d01f551..b05b1ba 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/context/CoroutineTools.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/context/CoroutineTools.kt @@ -18,7 +18,7 @@ import kotlin.coroutines.resumeWithException @OptIn(ExperimentalStdlibApi::class) -fun CoroutineScope.load(context: CoroutineContext = EmptyCoroutineContext, action: suspend () -> Unit): Job { +public fun CoroutineScope.load(context: CoroutineContext = EmptyCoroutineContext, action: suspend () -> Unit): Job { val state = RawReactive() val result = launch( context, @@ -35,8 +35,8 @@ fun CoroutineScope.load(context: CoroutineContext = EmptyCoroutineContext, actio return result } -class WaitGate(permit: Boolean = false) { - var permit: Boolean = permit +public class WaitGate(permit: Boolean = false) { + public var permit: Boolean = permit set(value) { field = value if (value) { @@ -46,18 +46,18 @@ class WaitGate(permit: Boolean = false) { continuations.clear() } } - fun permitOnce() { + public fun permitOnce() { permit = true permit = false } - val continuations = ArrayList>() - suspend fun await(): Unit { + private val continuations = ArrayList>() + public suspend fun await(): Unit { if (permit) return else return suspendCancellableCoroutine { continuations.add(it) } } - fun abandon() { + public fun abandon() { for (continuation in continuations) { continuation.resumeWithException(CancellationException("abandoned as requested")) } diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/context/DependencyChangeListener.kt b/src/commonMain/kotlin/com/lightningkite/reactive/context/DependencyChangeListener.kt index 9d1c692..1a916f4 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/context/DependencyChangeListener.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/context/DependencyChangeListener.kt @@ -8,13 +8,13 @@ import com.lightningkite.reactive.core.addAndRunListener import kotlinx.coroutines.* import kotlin.coroutines.* -abstract class DependencyChangeListener : DependencyTracker(), CoroutineContext.Element { +public abstract class DependencyChangeListener : DependencyTracker(), CoroutineContext.Element { override val key: CoroutineContext.Key get() = Key - object Key : CoroutineContext.Key + public object Key : CoroutineContext.Key - abstract fun onDependencyChange() - open fun onDependencyNotReady() = onDependencyChange() + public abstract fun onDependencyChange() + public open fun onDependencyNotReady(): Unit = onDependencyChange() } private fun Continuation.resumeState(state: ReactiveState) { @@ -25,7 +25,7 @@ private fun Continuation.resumeState(state: ReactiveState) { ) } -suspend fun rerunOn(listenable: Listenable) { +public suspend fun rerunOn(listenable: Listenable) { currentCoroutineContext()[DependencyChangeListener.Key]?.let { if (it.existingDependency(listenable) == null) { it.registerDependency(listenable, listenable.addListener { it.onDependencyChange() }) @@ -33,11 +33,11 @@ suspend fun rerunOn(listenable: Listenable) { } } -suspend inline operator fun Reactive.invoke(): T = await() -suspend inline operator fun ReactiveValue.invoke(): T = await() -suspend inline fun Reactive.exception(): Exception? = state { it.exception } +public suspend inline operator fun Reactive.invoke(): T = await() +public suspend inline operator fun ReactiveValue.invoke(): T = await() +public suspend inline fun Reactive.exception(): Exception? = state { it.exception } -suspend fun Reactive.state(get: (ReactiveState) -> V): V { +public suspend fun Reactive.state(get: (ReactiveState) -> V): V { return currentCoroutineContext()[DependencyChangeListener.Key]?.let { // and the value is ready to go, just add the listener and proceed with the value. var last = state.let(get) @@ -56,7 +56,7 @@ suspend fun Reactive.state(get: (ReactiveState) -> V): V { } ?: state.let(get) } -suspend fun Reactive.state(): ReactiveState { +public suspend fun Reactive.state(): ReactiveState { return currentCoroutineContext()[DependencyChangeListener.Key]?.let { // and the value is ready to go, just add the listener and proceed with the value. var last = state @@ -75,7 +75,7 @@ suspend fun Reactive.state(): ReactiveState { } ?: state } -suspend fun ReactiveValue.await(): T { +public suspend fun ReactiveValue.await(): T { return currentCoroutineContext()[DependencyChangeListener.Key]?.let { // and the value is ready to go, just add the listener and proceed with the value. var last = value @@ -94,7 +94,7 @@ suspend fun ReactiveValue.await(): T { } ?: value } -suspend fun Reactive.await(): T { +public suspend fun Reactive.await(): T { return currentCoroutineContext()[DependencyChangeListener.Key]?.let { var cont: Continuation? = null if (it.existingDependency(this) == null) { @@ -138,7 +138,7 @@ suspend fun Reactive.await(): T { * source's own nature, as with a `Signal` - is keeping that value current. Only [ReactiveState.notActive] * and notReady require listening, and the subscription is released as soon as a value arrives. */ -suspend fun Reactive.awaitOnce(): T { +public suspend fun Reactive.awaitOnce(): T { val state = state @Suppress("DEPRECATION") return if (state.ready) state.get() diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/context/DependencyTracker.kt b/src/commonMain/kotlin/com/lightningkite/reactive/context/DependencyTracker.kt index fb743aa..24a57be 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/context/DependencyTracker.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/context/DependencyTracker.kt @@ -1,6 +1,6 @@ package com.lightningkite.reactive.context -abstract class DependencyTracker { +public abstract class DependencyTracker { private val dependencies = ArrayList Unit>>() private val usedDependencies = ArrayList() @@ -19,7 +19,7 @@ abstract class DependencyTracker { * so the fast-path alignment above holds even when a call site re-reads an earlier dependency. */ @Suppress("UNCHECKED_CAST") - fun existingDependency(listenable: T): T? { + public fun existingDependency(listenable: T): T? { val index = usedDependencies.size if (index < dependencies.size) { val maybe = dependencies[index].first @@ -33,11 +33,11 @@ abstract class DependencyTracker { return found } - fun registerDependency(any: Any, remove: () -> Unit) { + public fun registerDependency(any: Any, remove: () -> Unit) { this.dependencies += any to remove } - open fun cancel() { + public open fun cancel() { dependencies.forEach { it.second() } dependencies.clear() } diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/context/ReactiveContext.kt b/src/commonMain/kotlin/com/lightningkite/reactive/context/ReactiveContext.kt index 34a8eae..ad28b47 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/context/ReactiveContext.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/context/ReactiveContext.kt @@ -32,7 +32,7 @@ import kotlin.coroutines.CoroutineContext * } * ``` */ -typealias ReactiveContext = TypedReactiveContext<*> +public typealias ReactiveContext = TypedReactiveContext<*> /** * Implements the core logic for a single reactive calculation, managing its dependencies and lifecycle. @@ -154,20 +154,20 @@ typealias ReactiveContext = TypedReactiveContext<*> * @property reportTo The underlying [RawReactive] to report state updates to. * @property action The calculation logic to execute in this context. */ -class TypedReactiveContext( - val scope: CoroutineScope, - val useLastWhileLoading: Boolean = false, - val reentrancyLimit: Int = 0, +public class TypedReactiveContext( + public val scope: CoroutineScope, + public val useLastWhileLoading: Boolean = false, + public val reentrancyLimit: Int = 0, private val reportTo: RawReactive = RawReactive(), - val action: TypedReactiveContext.() -> T + public val action: TypedReactiveContext.() -> T ) : DependencyChangeListener(), ReactiveCoroutineScope, Reactive by reportTo { - companion object + public companion object {} /** * Whether this context is currently active and tracking dependencies. * Set to false when [cancel] is called. */ - var active = false + public var active: Boolean = false private set /** @@ -180,7 +180,7 @@ class TypedReactiveContext( * arrives through this path while the calculation is still running is indistinguishable from * the calculation triggering itself, and so spends [reentrancyLimit]. */ - val rerun: () -> Unit = ::startCalculation + public val rerun: () -> Unit = ::startCalculation /** * The current job for this calculation run. @@ -229,7 +229,7 @@ class TypedReactiveContext( * than recursing until the stack overflows, or livelocking under a dispatching scheduler. The * default limit of zero forbids self-triggering outright. */ - fun startCalculation() { + public fun startCalculation() { active = true if (queued) return // Prevent duplicate queuing; the pending run will see the new state queued = true @@ -324,7 +324,7 @@ class TypedReactiveContext( /** * Starts using this [ResourceUse] and tracks it as a dependency in future loops. * */ - fun use(resourceUse: ResourceUse) { + public fun use(resourceUse: ResourceUse) { if (existingDependency(resourceUse) != null) return registerDependency(resourceUse, resourceUse.beginUse()) } @@ -343,7 +343,7 @@ class TypedReactiveContext( * } * ``` */ - fun rerunOn(listenable: Listenable) { + public fun rerunOn(listenable: Listenable) { if (existingDependency(listenable) != null) return registerDependency(listenable, listenable.addListener(rerun)) } @@ -383,7 +383,7 @@ class TypedReactiveContext( * @return The current value of this reactive * @throws ReactiveLoading if the value is not ready */ - operator fun Reactive.invoke(): R { + public operator fun Reactive.invoke(): R { if (existingDependency(this) == null) { registerDependency(this, addListener(rerun)) } @@ -407,7 +407,7 @@ class TypedReactiveContext( * @return The non-null value * @throws ReactiveLoading if the value is null or not ready */ - fun Reactive.awaitNotNull(): R { + public fun Reactive.awaitNotNull(): R { if (existingDependency(this) == null) { registerDependency(this, addListener(rerun)) } @@ -433,7 +433,7 @@ class TypedReactiveContext( * * @return The current [ReactiveState] */ - fun Reactive.state(): ReactiveState { + public fun Reactive.state(): ReactiveState { if (existingDependency(this) == null) { registerDependency(this, addListener(rerun)) } @@ -457,7 +457,7 @@ class TypedReactiveContext( * @param get Function to extract a value from the [ReactiveState] * @return The transformed value */ - fun Reactive.state(get: (ReactiveState) -> V): V { + public fun Reactive.state(get: (ReactiveState) -> V): V { var current: V = state.let(get) if (existingDependency(this) == null) { registerDependency(this, addListener { @@ -501,7 +501,7 @@ class TypedReactiveContext( * @return The value once it's ready * @throws ReactiveLoading if the value is not ready yet */ - fun Reactive.once(): T { + public fun Reactive.once(): T { val key = existingDependency(Once(this)) ?: Once(this).also { key -> registerDependency(key, addListener { if (!key.have) rerun() }) } @@ -512,19 +512,19 @@ class TypedReactiveContext( // Hack: fixes compiler weirdness around lambdas with 'this' @Suppress("NOTHING_TO_INLINE") - inline operator fun (ReactiveContext.() -> T).invoke(): T = invoke(this@TypedReactiveContext) + public inline operator fun (ReactiveContext.() -> T).invoke(): T = invoke(this@TypedReactiveContext) @Suppress("NOTHING_TO_INLINE") - inline operator fun (ReactiveContext.(A) -> T).invoke(a: A): T = invoke(this@TypedReactiveContext, a) + public inline operator fun (ReactiveContext.(A) -> T).invoke(a: A): T = invoke(this@TypedReactiveContext, a) @Suppress("NOTHING_TO_INLINE") - inline operator fun (ReactiveContext.(A, B) -> T).invoke(a: A, b: B): T = invoke(this@TypedReactiveContext, a, b) + public inline operator fun (ReactiveContext.(A, B) -> T).invoke(a: A, b: B): T = invoke(this@TypedReactiveContext, a, b) @Deprecated("Just use the invoke operator", ReplaceWith("this()")) - fun Reactive.await(): T = invoke() + public fun Reactive.await(): T = invoke() @Deprecated("Just use the once function", ReplaceWith("this.once()")) - fun Reactive.awaitOnce(): T = once() + public fun Reactive.awaitOnce(): T = once() // Suspending calculations @@ -568,7 +568,7 @@ class TypedReactiveContext( * @return The result of the calculation once complete * @throws ReactiveLoading if the calculation is not yet complete */ - fun async(identity: String, vararg dependencies: Any?, action: suspend () -> T): T { + public fun async(identity: String, vararg dependencies: Any?, action: suspend () -> T): T { // action::class distinguishes call sites: two different `async(...) { }` call sites compile // to distinct anonymous classes, so folding it into the key stops two call sites that // happen to pass identical dependencies from colliding on the same cached calculation. @@ -614,7 +614,7 @@ class TypedReactiveContext( * @return The deferred value once available * @throws ReactiveLoading if the deferred is not yet complete */ - operator fun Deferred.invoke(): T { + public operator fun Deferred.invoke(): T { val calc = SuspendCalculation(this) // Reuse existing calculation if already running @@ -671,7 +671,7 @@ class TypedReactiveContext( * @return The latest emitted value * @throws ReactiveLoading if no value has been emitted yet (except for StateFlow) */ - operator fun Flow.invoke(): T { + public operator fun Flow.invoke(): T { val new = FlowLoader(this) val existing = existingDependency(new) @@ -775,7 +775,7 @@ class TypedReactiveContext( * @see TypedReactiveContext for implementation details * @see reactiveSuspending for suspending calculations */ -fun CoroutineScope.reactive(reentrancyLimit: Int = 0, action: ReactiveContext.() -> T): TypedReactiveContext { +public fun CoroutineScope.reactive(reentrancyLimit: Int = 0, action: ReactiveContext.() -> T): TypedReactiveContext { val trc = TypedReactiveContext(this, reentrancyLimit = reentrancyLimit, action = action) trc.startCalculation() coroutineContext[StatusListener]?.watchBackgroundProcess(trc) @@ -801,7 +801,7 @@ fun CoroutineScope.reactive(reentrancyLimit: Int = 0, action: ReactiveContex * @param action The calculation logic to run reactively. * @return A [TypedReactiveContext] managing the calculation and its dependencies. */ -inline fun CoroutineScope.reactive(crossinline onLoad: () -> Unit, crossinline action: ReactiveContext.() -> Unit): TypedReactiveContext { +public inline fun CoroutineScope.reactive(crossinline onLoad: () -> Unit, crossinline action: ReactiveContext.() -> Unit): TypedReactiveContext { var wasLoadingLastTime = false return reactive { try { @@ -827,7 +827,7 @@ inline fun CoroutineScope.reactive(crossinline onLoad: () -> Unit, crossinline a * @param action The calculation logic to run reactively. */ @Deprecated("renamed to 'reactive'", ReplaceWith("this.reactive(action)")) -fun CoroutineScope.reactiveScope(action: ReactiveContext.() -> Unit): ReactiveContext = reactive(action = action) +public fun CoroutineScope.reactiveScope(action: ReactiveContext.() -> Unit): ReactiveContext = reactive(action = action) /** * Creates a [ReactiveContext] in which to run the provided [action] reactively, discarding the result, with support for loading state. @@ -839,17 +839,17 @@ fun CoroutineScope.reactiveScope(action: ReactiveContext.() -> Unit): ReactiveCo * @param action The calculation logic to run reactively. */ @Deprecated("renamed to 'reactive'", ReplaceWith("this.reactive(onLoad, action)")) -inline fun CoroutineScope.reactiveScope(crossinline onLoad: () -> Unit, crossinline action: ReactiveContext.() -> Unit): ReactiveContext = reactive(onLoad = onLoad, action = action) +public inline fun CoroutineScope.reactiveScope(crossinline onLoad: () -> Unit, crossinline action: ReactiveContext.() -> Unit): ReactiveContext = reactive(onLoad = onLoad, action = action) @InternalReactiveApi -object ReactiveLoading : Throwable() +public object ReactiveLoading : Throwable() /** * Thrown when a reactive calculation triggers its own re-execution, typically by writing to a * signal it also reads inside the same [reactive] block. This would otherwise recurse until the * stack overflows (or livelock under a dispatching scheduler), so it is surfaced as a clear error. */ -class ReactiveReentrancyException(context: ReactiveContext, reentrancyLimit: Int) : IllegalStateException( +public class ReactiveReentrancyException(context: ReactiveContext, reentrancyLimit: Int) : IllegalStateException( if (reentrancyLimit <= 0) "A reactive calculation triggered its own re-execution ($context). This usually means the " + "calculation wrote to a signal it also reads. Break the cycle so the calculation " + diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/context/ReactiveContextSuspending.kt b/src/commonMain/kotlin/com/lightningkite/reactive/context/ReactiveContextSuspending.kt index a6e42ab..be9b0a0 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/context/ReactiveContextSuspending.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/context/ReactiveContextSuspending.kt @@ -92,11 +92,11 @@ import kotlin.coroutines.CoroutineContext * @property reportTo The underlying [RawReactive] to report state updates to. * @property action The suspending calculation logic to execute in this context. */ -class ReactiveContextSuspending( - val scope: CoroutineScope, - val useLastWhileLoading: Boolean = false, +public class ReactiveContextSuspending( + public val scope: CoroutineScope, + public val useLastWhileLoading: Boolean = false, private val reportTo: RawReactive = RawReactive(), - val action: suspend ReactiveCoroutineScope.() -> T, + public val action: suspend ReactiveCoroutineScope.() -> T, ) : DependencyChangeListener(), ReactiveCoroutineScope, Reactive by reportTo { /** * The job for the current calculation run's coroutine. @@ -108,7 +108,7 @@ class ReactiveContextSuspending( * Whether this context is currently active and tracking dependencies. * Set to false when [cancel] is called. */ - var active = false + public var active: Boolean = false private set /** @@ -161,7 +161,7 @@ class ReactiveContextSuspending( * The calculation may complete synchronously (if already on correct dispatcher and no suspension points) * or asynchronously. If [useLastWhileLoading] is false, the state is set to notReady during async execution. */ - fun startCalculation() { + public fun startCalculation() { active = true lastLoopJob?.cancel() // Cancel previous calculation if still running @@ -310,7 +310,7 @@ class ReactiveContextSuspending( * @see ReactiveContextSuspending for implementation details * @see reactive for non-suspending calculations */ -fun CoroutineScope.reactiveSuspending(action: suspend ReactiveCoroutineScope.() -> Unit) = +public fun CoroutineScope.reactiveSuspending(action: suspend ReactiveCoroutineScope.() -> Unit): ReactiveContextSuspending = ReactiveContextSuspending(this, action = action).also { it.startCalculation() coroutineContext[StatusListener.Key]?.watchBackgroundProcess(it) @@ -326,7 +326,7 @@ fun CoroutineScope.reactiveSuspending(action: suspend ReactiveCoroutineScope.() * @param action The suspending calculation logic to run reactively. * @return A [ReactiveContextSuspending] managing the calculation and its dependencies. */ -inline fun CoroutineScope.reactiveSuspending(crossinline onLoad: () -> Unit, noinline action: suspend ReactiveCoroutineScope.() -> Unit): ReactiveContextSuspending { +public inline fun CoroutineScope.reactiveSuspending(crossinline onLoad: () -> Unit, noinline action: suspend ReactiveCoroutineScope.() -> Unit): ReactiveContextSuspending { return reactiveSuspending(action = action).also { it.addListener { if (!it.state.ready) onLoad() }.let(::onRemove) } diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/core/AppScope.kt b/src/commonMain/kotlin/com/lightningkite/reactive/core/AppScope.kt index 4f88acc..92c16dc 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/core/AppScope.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/core/AppScope.kt @@ -1,12 +1,13 @@ package com.lightningkite.reactive.core +import kotlinx.coroutines.CompletableJob import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob -val AppJob = SupervisorJob() +public val AppJob: CompletableJob = SupervisorJob() -val AppScope = CoroutineScope(AppJob + CoroutineExceptionHandler { coroutineContext, throwable -> +public val AppScope: CoroutineScope = CoroutineScope(AppJob + CoroutineExceptionHandler { coroutineContext, throwable -> Reactive.reportException(throwable) } + Dispatchers.Main.immediate) \ No newline at end of file diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/core/Draft.kt b/src/commonMain/kotlin/com/lightningkite/reactive/core/Draft.kt index 8d759d8..b50936c 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/core/Draft.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/core/Draft.kt @@ -24,28 +24,28 @@ import com.lightningkite.reactive.context.awaitOnce * draft.cancel() // discards changes and resets the draft. draft now reads '42' again. * ``` */ -interface Draft : ReactiveWithMutableValue { +public interface Draft : ReactiveWithMutableValue { /** * The current saved value that this [Draft] is buffering. * * NOTE: Manually setting values for [published] will not by-default update values in the draft buffer. * */ - val published: MutableReactive + public val published: MutableReactive /** * Saves all changes made to this [Draft] to the published [MutableReactive] * */ - suspend fun publish(): T + public suspend fun publish(): T /** * Discards all changes made to this [Draft] and reverts back to the [published] state * */ - fun cancel() + public fun cancel() /** * Reads `true` if there are any differences between the [published] value and the value stored in the draft buffer. * */ - val changesMade: Reactive + public val changesMade: Reactive } private class BaseDraft private constructor( @@ -67,14 +67,14 @@ private class BaseDraft private constructor( /** * Creates a [Draft] using the specified [MutableReactive] as the published value. * */ -fun Draft(published: MutableReactive): Draft = BaseDraft(published) +public fun Draft(published: MutableReactive): Draft = BaseDraft(published) /** * Creates a [Draft] where the published value is the provided [initialValue] * */ -fun Draft(initialValue: T): Draft = BaseDraft(Signal(initialValue)) +public fun Draft(initialValue: T): Draft = BaseDraft(Signal(initialValue)) /** * Creates a [Draft] where the published value is calculated based off the provided [initialValue] calculation. * */ -fun Draft(initialValue: ReactiveContext.() -> T): Draft = BaseDraft(MutableRemember(useLastWhileLoading = true, initialValue = initialValue)) \ No newline at end of file +public fun Draft(initialValue: ReactiveContext.() -> T): Draft = BaseDraft(MutableRemember(useLastWhileLoading = true, initialValue = initialValue)) \ No newline at end of file diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/core/InternalReactiveApi.kt b/src/commonMain/kotlin/com/lightningkite/reactive/core/InternalReactiveApi.kt index 7676db1..207dc9a 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/core/InternalReactiveApi.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/core/InternalReactiveApi.kt @@ -5,4 +5,4 @@ package com.lightningkite.reactive.core level = RequiresOptIn.Level.WARNING, message = "This may change, use it at your own risk" ) -annotation class InternalReactiveApi \ No newline at end of file +public annotation class InternalReactiveApi \ No newline at end of file diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/core/MutableRemember.kt b/src/commonMain/kotlin/com/lightningkite/reactive/core/MutableRemember.kt index f570fb6..6ea3393 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/core/MutableRemember.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/core/MutableRemember.kt @@ -32,7 +32,7 @@ import kotlin.time.Duration * * @see remember */ -fun mutableRemember( +public fun mutableRemember( useLastWhileLoading: Boolean = false, coroutineContext: CoroutineContext = Dispatchers.Unconfined, initialValue: ReactiveContext.() -> T @@ -57,14 +57,14 @@ fun mutableRemember( * * @see Remember */ -class MutableRemember( +public class MutableRemember( private val stopListeningWhenOverridden: Boolean = true, private val useLastWhileLoading: Boolean = false, coroutineContext: CoroutineContext = Dispatchers.Unconfined, deactivationDelay: Duration? = null, initialValue: ReactiveContext.() -> T ): ReactiveWithMutableValue, BaseReactive() { - var overridden: Boolean = false + public var overridden: Boolean = false private set private val remember = Remember(coroutineContext, useLastWhileLoading, deactivationDelay, action = initialValue) @@ -117,7 +117,7 @@ class MutableRemember( * - This does not forcefully notify listeners. If the value calculated after resetting is the same as the previously set value, * then no listeners will be notified. */ - fun reset() { + public fun reset() { if (overridden) { overridden = false if (stopListeningWhenOverridden) startListening() diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/core/MutableRememberSuspending.kt b/src/commonMain/kotlin/com/lightningkite/reactive/core/MutableRememberSuspending.kt index d837487..90c7e4b 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/core/MutableRememberSuspending.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/core/MutableRememberSuspending.kt @@ -33,7 +33,7 @@ import kotlin.time.Duration * @see rememberSuspending * @see mutableRemember */ -fun mutableRememberSuspending( +public fun mutableRememberSuspending( useLastWhileLoading: Boolean = false, coroutineContext: CoroutineContext = Dispatchers.Unconfined, initialValue: suspend CoroutineScope.() -> T @@ -58,14 +58,14 @@ fun mutableRememberSuspending( * * @see [MutableRemember] */ -class MutableRememberSuspending( +public class MutableRememberSuspending( private val stopListeningWhenOverridden: Boolean = true, private val useLastWhileLoading: Boolean = false, coroutineContext: CoroutineContext = Dispatchers.Unconfined, deactivationDelay: Duration? = null, initialValue: suspend CoroutineScope.() -> T ) : ReactiveWithMutableValue, BaseReactive() { - var overridden: Boolean = false + public var overridden: Boolean = false private set private val remember = RememberSuspending(coroutineContext, useLastWhileLoading, deactivationDelay, initialValue) @@ -123,7 +123,7 @@ class MutableRememberSuspending( * - This does not forcefully notify listeners. If the value calculated after resetting is the same as the previously set value, * then no listeners will be notified. */ - fun reset() { + public fun reset() { if (overridden) { overridden = false if (stopListeningWhenOverridden) startListening() diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/core/ReactiveMutableList.kt b/src/commonMain/kotlin/com/lightningkite/reactive/core/ReactiveMutableList.kt index acd630a..7259f40 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/core/ReactiveMutableList.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/core/ReactiveMutableList.kt @@ -6,10 +6,10 @@ import com.lightningkite.reactive.lensing.lens /** * A wrapper around [ArrayList] that signals its listeners whenever it is mutated * */ -class ReactiveMutableList(private val list: ArrayList): MutableList by list, MutableReactiveValue>, BaseListenable() { - constructor() : this(ArrayList()) - constructor(items: List) : this(ArrayList(items)) - constructor(vararg startingItems: T) : this(ArrayList(startingItems.asList())) +public class ReactiveMutableList(private val list: ArrayList): MutableList by list, MutableReactiveValue>, BaseListenable() { + public constructor() : this(ArrayList()) + public constructor(items: List) : this(ArrayList(items)) + public constructor(vararg startingItems: T) : this(ArrayList(startingItems.asList())) override var value: List get() = list @@ -33,11 +33,11 @@ class ReactiveMutableList(private val list: ArrayList): MutableList by override fun removeAll(elements: Collection): Boolean = signalChange { removeAll(elements) } override fun addAll(elements: Collection): Boolean = signalChange { addAll(elements) } override fun addAll(index: Int, elements: Collection): Boolean = signalChange { addAll(index, elements) } - override fun add(index: Int, element: T) = signal { add(index, element) } + override fun add(index: Int, element: T): Unit = signal { add(index, element) } override fun add(element: T): Boolean = signal { add(element) } override fun remove(element: T): Boolean = signalChange { remove(element) } - fun reactiveContains(element: T) = object : MutableReactiveValue { + public fun reactiveContains(element: T): MutableReactiveValue = object : MutableReactiveValue { private val lens = this@ReactiveMutableList.lens { element in it } override fun addListener(listener: () -> Unit): Release = lens.addListener(listener) diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/core/ReactiveMutableMap.kt b/src/commonMain/kotlin/com/lightningkite/reactive/core/ReactiveMutableMap.kt index a57014c..0c4d165 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/core/ReactiveMutableMap.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/core/ReactiveMutableMap.kt @@ -5,8 +5,8 @@ import com.lightningkite.reactive.lensing.lens /** * A wrapper around [HashMap] that signals its listeners whenever it is mutated * */ -class ReactiveMutableMap(private val map: HashMap): MutableMap by map, MutableReactiveValue>, BaseListenable() { - constructor() : this(HashMap()) +public class ReactiveMutableMap(private val map: HashMap): MutableMap by map, MutableReactiveValue>, BaseListenable() { + public constructor() : this(HashMap()) override var value: Map get() = map @@ -18,26 +18,26 @@ class ReactiveMutableMap(private val map: HashMap): MutableMap } } - val reactiveEntries: ReactiveValue>> = + public val reactiveEntries: ReactiveValue>> = object : ReactiveValue>>, Listenable by this { override val value: Set> get() = map.entries } - val reactiveKeys: ReactiveValue> = + public val reactiveKeys: ReactiveValue> = object : ReactiveValue>, Listenable by this { override val value: Set get() = map.keys } - val reactiveValues: ReactiveValue> = + public val reactiveValues: ReactiveValue> = object : ReactiveValue>, Listenable by this { override val value: Collection get() = map.values } private inline fun signal(operation: MutableMap.()->T) = map.operation().also { invokeAllListeners() } - override fun clear() = signal { clear() } + override fun clear(): Unit = signal { clear() } override fun remove(key: K): V? = map.remove(key).also { if (it != null) invokeAllListeners() } - override fun putAll(from: Map) = signal { putAll(from) } + override fun putAll(from: Map): Unit = signal { putAll(from) } override fun put(key: K, value: V): V? = signal { put(key, value) } private inner class Element(private val key: K) : MutableReactiveValue { @@ -53,5 +53,5 @@ class ReactiveMutableMap(private val map: HashMap): MutableMap } } - fun getReactive(key: K): MutableReactiveValue = Element(key) + public fun getReactive(key: K): MutableReactiveValue = Element(key) } \ No newline at end of file diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/core/ReactiveMutableSet.kt b/src/commonMain/kotlin/com/lightningkite/reactive/core/ReactiveMutableSet.kt index b4b2195..2616d79 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/core/ReactiveMutableSet.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/core/ReactiveMutableSet.kt @@ -6,10 +6,10 @@ import com.lightningkite.reactive.lensing.lens /** * A wrapper around [LinkedHashSet] that signals its listeners whenever it is mutated * */ -class ReactiveMutableSet(private val hashSet: LinkedHashSet): MutableSet by hashSet, MutableReactiveValue>, BaseListenable() { - constructor() : this(LinkedHashSet()) - constructor(items: Set) : this(LinkedHashSet(items)) - constructor(vararg startingItems: T) : this(LinkedHashSet(startingItems.toList())) +public class ReactiveMutableSet(private val hashSet: LinkedHashSet): MutableSet by hashSet, MutableReactiveValue>, BaseListenable() { + public constructor() : this(LinkedHashSet()) + public constructor(items: Set) : this(LinkedHashSet(items)) + public constructor(vararg startingItems: T) : this(LinkedHashSet(startingItems.toList())) override var value: Set get() = hashSet @@ -34,7 +34,7 @@ class ReactiveMutableSet(private val hashSet: LinkedHashSet): MutableSet { + public fun reactiveContains(element: T): MutableReactiveValue = object : MutableReactiveValue { private val lens = this@ReactiveMutableSet.lens { element in it } override fun addListener(listener: () -> Unit): Release = lens.addListener(listener) diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/core/ReactiveState.kt b/src/commonMain/kotlin/com/lightningkite/reactive/core/ReactiveState.kt index da8ad4a..fd8bd8a 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/core/ReactiveState.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/core/ReactiveState.kt @@ -28,49 +28,49 @@ import kotlin.jvm.JvmInline */ @JvmInline @OptIn(InternalReactiveApi::class) -value class ReactiveState(val raw: T) { - inline val ready: Boolean get() = raw != InternalReactiveNotReady && raw != InternalReactiveNotActive - inline val success: Boolean get() = ready && raw !is InternalReactiveThrownException +public value class ReactiveState(public val raw: T) { + public inline val ready: Boolean get() = raw != InternalReactiveNotReady && raw != InternalReactiveNotActive + public inline val success: Boolean get() = ready && raw !is InternalReactiveThrownException /** True when nothing is maintaining this value; see the [ReactiveState] docs. */ - inline val notActive: Boolean get() = raw is InternalReactiveNotActive + public inline val notActive: Boolean get() = raw is InternalReactiveNotActive - inline fun onSuccess(action: (T)->R): R? = handle( + public inline fun onSuccess(action: (T)->R): R? = handle( success = { action(it) }, exception = { null }, notReady = { null } ) - inline val exception: Exception? get() = (raw as? InternalReactiveThrownException)?.exception + public inline val exception: Exception? get() = (raw as? InternalReactiveThrownException)?.exception @Deprecated("Only use this if you are *Absolutely Sure* that there is a value ready to retrieve. Otherwise, use `handle`.") - fun get(): T = handle( + public fun get(): T = handle( success = { it }, exception = { throw it }, notReady = { throw NotReadyException() }, notActive = { throw NotActiveException() } ) - fun getOrNull(): T? = handle( + public fun getOrNull(): T? = handle( success = { it }, exception = { null }, notReady = { null } ) - companion object Companion { + public companion object Companion { @Suppress("UNCHECKED_CAST") - val notReady: ReactiveState = ReactiveState(InternalReactiveNotReady) as ReactiveState + public val notReady: ReactiveState = ReactiveState(InternalReactiveNotReady) as ReactiveState /** No value, because nothing is maintaining one; see the [ReactiveState] docs. */ @Suppress("UNCHECKED_CAST") - val notActive: ReactiveState = ReactiveState(InternalReactiveNotActive) as ReactiveState + public val notActive: ReactiveState = ReactiveState(InternalReactiveNotActive) as ReactiveState @Suppress("UNCHECKED_CAST") - fun exception(exception: Exception) = (if(exception is CancellationException) notReady else ReactiveState(InternalReactiveThrownException(exception))) as ReactiveState + public fun exception(exception: Exception): ReactiveState = (if(exception is CancellationException) notReady else ReactiveState(InternalReactiveThrownException(exception))) as ReactiveState @Suppress("UNCHECKED_CAST") - fun wrap(value: T) = ReactiveState(InternalReactiveWrapper(value)) as ReactiveState + public fun wrap(value: T): ReactiveState = ReactiveState(InternalReactiveWrapper(value)) as ReactiveState } @Suppress("UNCHECKED_CAST") - inline fun map(mapper: (T)->B): ReactiveState { + public inline fun map(mapper: (T)->B): ReactiveState { // notActive propagates like the other valueless states: a value derived from a source // nobody is maintaining is equally unmaintained. if(raw is InternalReactiveNotReady || raw is InternalReactiveNotActive || raw is InternalReactiveThrownException) return this as ReactiveState @@ -90,14 +90,14 @@ value class ReactiveState(val raw: T) { * value" and "there is no value yet" are the same thing to code that only wants to display or * wait for one. Use the four-argument overload to do something better, such as subscribing. */ - inline fun handle( + public inline fun handle( success: (T)->R, exception: (Exception)->R, notReady: ()->R ): R = handle(success, exception, notReady, notReady) @Suppress("UNCHECKED_CAST") - inline fun handle( + public inline fun handle( success: (T)->R, exception: (Exception)->R, notReady: ()->R, @@ -112,7 +112,7 @@ value class ReactiveState(val raw: T) { } } - fun asResult(): Result = handle( + public fun asResult(): Result = handle( success = { Result.success(it) }, exception = { Result.failure(it) }, notReady = { Result.failure(NotReadyException()) }, @@ -128,24 +128,24 @@ value class ReactiveState(val raw: T) { } } @InternalReactiveApi -data class InternalReactiveWrapper(val other: T) +public data class InternalReactiveWrapper(val other: T) @InternalReactiveApi -data class InternalReactiveThrownException(val exception: Exception) +public data class InternalReactiveThrownException(val exception: Exception) @InternalReactiveApi -object InternalReactiveNotReady +public object InternalReactiveNotReady @InternalReactiveApi -object InternalReactiveNotActive +public object InternalReactiveNotActive -open class NotReadyException(message: String? = null) : IllegalStateException(message) +public open class NotReadyException(message: String? = null) : IllegalStateException(message) /** * Thrown when reading a value from a reactive that nothing is listening to, and which therefore * has no value to give. Subscribe to it first, or use `awaitOnce`, which subscribes for as long * as it takes to obtain a value. */ -class NotActiveException(message: String = "Nothing is listening to this reactive value, so it has no value to report. Subscribe to it, or use awaitOnce.") : NotReadyException(message) +public class NotActiveException(message: String = "Nothing is listening to this reactive value, so it has no value to report. Subscribe to it, or use awaitOnce.") : NotReadyException(message) -inline fun reactiveState(action: () -> T): ReactiveState { +public inline fun reactiveState(action: () -> T): ReactiveState { @OptIn(InternalReactiveApi::class) return try { ReactiveState(action()) @@ -161,7 +161,7 @@ inline fun reactiveState(action: () -> T): ReactiveState { } } -fun Result.toReactiveState(): ReactiveState { +public fun Result.toReactiveState(): ReactiveState { @Suppress("UNCHECKED_CAST") return if(this.isFailure) ReactiveState.exception(this.exceptionOrNull() as Exception) else ReactiveState.wrap(this.getOrNull() as T) diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/core/Remember.kt b/src/commonMain/kotlin/com/lightningkite/reactive/core/Remember.kt index d0134a5..6044c3a 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/core/Remember.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/core/Remember.kt @@ -39,7 +39,7 @@ import kotlin.time.Duration * b.value = 2 // prints "sum: 3" * ``` */ -fun remember( +public fun remember( coroutineContext: CoroutineContext = Dispatchers.Unconfined, useLastWhileLoading: Boolean = false, deactivationDelay: Duration? = null, @@ -74,11 +74,11 @@ fun remember( * Listeners can be added to be notified when the value changes. The calculation is protected against * cancellation exceptions, and any other exceptions are reported via [Reactive.reportException]. */ -class Remember( - val incomingCoroutineContext: CoroutineContext = Dispatchers.Unconfined, +public class Remember( + public val incomingCoroutineContext: CoroutineContext = Dispatchers.Unconfined, private val useLastWhileLoading: Boolean = false, private val deactivationDelay: Duration? = null, - private val reentrancyLimit: Int = 0, + reentrancyLimit: Int = 0, private val action: ReactiveContext.() -> T, ) : Reactive, CoroutineScope, BaseListenable() { @@ -96,7 +96,7 @@ class Remember( // and TypedReactiveContext.init's `scope.onRemove { cancel() }` then attaches an // invokeOnCompletion handler to the app-lifetime Job that never fires — leaking every // Remember/shared reactive graph forever. Matches RememberSuspending's ordering. - override val coroutineContext get() = restOfContext + job + override val coroutineContext: CoroutineContext get() = restOfContext + job // Starts notActive rather than notReady: nothing is listening yet, so there is no value to // be had, as opposed to one that is on its way. diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/core/RememberSuspending.kt b/src/commonMain/kotlin/com/lightningkite/reactive/core/RememberSuspending.kt index 0d0e43b..f1e1368 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/core/RememberSuspending.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/core/RememberSuspending.kt @@ -38,7 +38,7 @@ import kotlin.time.Duration * * @see [remember] */ -fun rememberSuspending( +public fun rememberSuspending( coroutineContext: CoroutineContext = Dispatchers.Unconfined, useLastWhileLoading: Boolean = false, deactivationDelay: Duration? = null, @@ -72,9 +72,9 @@ fun rememberSuspending( * * @see [Remember] */ -class RememberSuspending( - val incomingCoroutineContext: CoroutineContext = Dispatchers.Unconfined, - private val useLastWhileLoading: Boolean = false, +public class RememberSuspending( + public val incomingCoroutineContext: CoroutineContext = Dispatchers.Unconfined, + useLastWhileLoading: Boolean = false, private val deactivationDelay: Duration? = null, private val action: suspend ReactiveCoroutineScope.() -> T, ) : Reactive, CoroutineScope, BaseListenable() { diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/core/abstracts.kt b/src/commonMain/kotlin/com/lightningkite/reactive/core/abstracts.kt index e02a11b..e8d0668 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/core/abstracts.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/core/abstracts.kt @@ -22,7 +22,7 @@ import kotlin.coroutines.cancellation.CancellationException * } * ``` */ -abstract class BaseListenable : Listenable { +public abstract class BaseListenable : Listenable { /** * Called when the first listener is added. * Override to start calculations, resource usage, or subscriptions. @@ -41,7 +41,7 @@ abstract class BaseListenable : Listenable { * Number of currently-registered listeners. Exposed for tests that need to assert * that a listenable is not leaking subscriptions. */ - val listenerCount: Int get() = listeners.size + public val listenerCount: Int get() = listeners.size override fun addListener(listener: () -> Unit): Release { if (listeners.isEmpty()) activate() @@ -76,7 +76,7 @@ abstract class BaseListenable : Listenable { * * @see BaseListenable */ -abstract class BaseReactive(start: ReactiveState = ReactiveState.notReady) : Reactive, BaseListenable() { +public abstract class BaseReactive(start: ReactiveState = ReactiveState.notReady) : Reactive, BaseListenable() { override var state: ReactiveState = start protected set(value) { if (field.raw !== value.raw && field != value) { @@ -95,7 +95,7 @@ abstract class BaseReactive(start: ReactiveState = ReactiveState.notReady) * * @see BaseListenable */ -abstract class BaseReactiveValue(start: T) : ReactiveValue, BaseListenable() { +public abstract class BaseReactiveValue(start: T) : ReactiveValue, BaseListenable() { override var value: T = start set(value) { @Suppress("SuspiciousEqualsCombination") diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/core/coreInterfaces.kt b/src/commonMain/kotlin/com/lightningkite/reactive/core/coreInterfaces.kt index 2af73fc..6440e18 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/core/coreInterfaces.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/core/coreInterfaces.kt @@ -28,7 +28,7 @@ import kotlin.reflect.KProperty * release() // Safe to call again, no effect * ``` */ -typealias Release = () -> Unit +public typealias Release = () -> Unit /** * Represents a resource that can be used and released. @@ -36,11 +36,11 @@ typealias Release = () -> Unit * * @see Listenable */ -interface ResourceUse { +public interface ResourceUse { /** * Begins using the resource. Returns a function to stop using the resource. */ - fun beginUse(): Release + public fun beginUse(): Release } @@ -52,16 +52,16 @@ interface ResourceUse { * * @see ResourceUse */ -interface Listenable : ResourceUse { +public interface Listenable : ResourceUse { /** * Adds the [listener] to be called every time this event fires. * @return a [Release] handle to remove the [listener] that was added. Removing multiple times should not cause issues. */ - fun addListener(listener: () -> Unit): Release + public fun addListener(listener: () -> Unit): Release override fun beginUse(): Release = addListener { } - object Never: Listenable { + public object Never: Listenable { public val NOOP_RELEASE: Release = {} override fun addListener(listener: () -> Unit): Release = NOOP_RELEASE @@ -72,7 +72,7 @@ interface Listenable : ResourceUse { * Adds a listener and immediately runs it once. * @return a [Release] handle to remove the listener. */ -fun Listenable.addAndRunListener(listener: () -> Unit): Release { +public fun Listenable.addAndRunListener(listener: () -> Unit): Release { val release = addListener(listener) listener() return release @@ -91,7 +91,7 @@ fun Listenable.addAndRunListener(listener: () -> Unit): Release { * @see ReactiveState * @see com.lightningkite.reactive.context.ReactiveContext */ -interface Reactive : Listenable { +public interface Reactive : Listenable { /** * The current state. * @@ -100,26 +100,26 @@ interface Reactive : Listenable { * a value they are no longer keeping up to date. Anything else is a promise that the state is * current, which is what lets callers read it without subscribing. */ - val state: ReactiveState + public val state: ReactiveState - object Never: Reactive { + public object Never: Reactive { override val state: ReactiveState get() = ReactiveState.notReady override fun addListener(listener: () -> Unit): Release = Listenable.Never.NOOP_RELEASE } - companion object Companion { + public companion object Companion { /** * Used to report exceptions thrown in listeners or reactive calculations. */ - var reportException: (Throwable) -> Unit = { it.printStackTrace() } + public var reportException: (Throwable) -> Unit = { it.printStackTrace() } } } /** * Represents a mutable value that can be set asynchronously. */ -interface Mutable { - suspend infix fun set(value: T) +public interface Mutable { + public suspend infix fun set(value: T) } /** @@ -132,7 +132,7 @@ interface Mutable { * @see Reactive * @see Mutable */ -interface MutableReactive : Reactive, Mutable { +public interface MutableReactive : Reactive, Mutable { /** * 'Lenses' a new type from this [MutableReactive]. This is useful when translating one @@ -156,7 +156,7 @@ interface MutableReactive : Reactive, Mutable { * ) * ``` */ - fun lens( + public fun lens( get: (T) -> L, set: (L) -> T ): MutableReactive = SetLens(this, get, set) @@ -185,7 +185,7 @@ interface MutableReactive : Reactive, Mutable { * ) * ``` */ - fun lens( + public fun lens( get: (T) -> L, modify: (T, L) -> T ): MutableReactive = ModifyLens(this, get, modify) @@ -202,8 +202,8 @@ interface MutableReactive : Reactive, Mutable { * * @see Reactive */ -interface ReactiveValue : Reactive, ReadOnlyProperty { - val value: T +public interface ReactiveValue : Reactive, ReadOnlyProperty { + public val value: T // A ReactiveValue always has a value, so its state can never be notReady or notActive. That // makes it the wrong interface for anything that only maintains a value while listened to. @@ -219,8 +219,8 @@ interface ReactiveValue : Reactive, ReadOnlyProperty { * * @see Mutable */ -interface MutableValue: Mutable { - infix fun valueSet(value: T) +public interface MutableValue: Mutable { + public infix fun valueSet(value: T) override suspend fun set(value: T) { valueSet(value) } } @@ -229,14 +229,14 @@ interface MutableValue: Mutable { * * Combines [MutableReactive] and [ReactiveValue]. */ -interface MutableWithReactiveValue : MutableReactive, ReactiveValue +public interface MutableWithReactiveValue : MutableReactive, ReactiveValue /** * A [Reactive] that can be synchronously modified. * * Combines [MutableReactive] and [MutableValue]. */ -interface ReactiveWithMutableValue : MutableReactive, MutableValue +public interface ReactiveWithMutableValue : MutableReactive, MutableValue /** * Represents a mutable reactive value that can be modified and observed for changes. @@ -251,7 +251,7 @@ interface ReactiveWithMutableValue : MutableReactive, MutableValue * @see MutableWithReactiveValue * @see ReactiveWithMutableValue */ -interface MutableReactiveValue : MutableValue, ReactiveValue, +public interface MutableReactiveValue : MutableValue, ReactiveValue, // Interfaces below are just for typing convenience, they are already implemented by intersection of MutableSignal and ValueSignal MutableReactive, MutableWithReactiveValue, diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/core/processes.kt b/src/commonMain/kotlin/com/lightningkite/reactive/core/processes.kt index 4891cef..dcde6bb 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/core/processes.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/core/processes.kt @@ -7,12 +7,12 @@ import kotlinx.coroutines.launch import kotlin.jvm.JvmName -interface Emitter: CoroutineScope { - fun emit(value: T) +public interface Emitter: CoroutineScope { + public fun emit(value: T) } @JvmName("reactiveProcessImplicit") -fun CoroutineScope.reactiveProcess(emitter: suspend Emitter.() -> Unit): Reactive { +public fun CoroutineScope.reactiveProcess(emitter: suspend Emitter.() -> Unit): Reactive { val prop = LateInitSignal() launch { emitter(object : Emitter, CoroutineScope by this { @@ -25,7 +25,7 @@ fun CoroutineScope.reactiveProcess(emitter: suspend Emitter.() -> Unit): } // The two below only run their emitter while something is listening, so with no listeners they // report notActive: whatever the emitter last produced is no longer being kept up to date. -fun reactiveProcess(scope: CoroutineScope = AppScope, emitter: suspend Emitter.() -> Unit): Reactive { +public fun reactiveProcess(scope: CoroutineScope = AppScope, emitter: suspend Emitter.() -> Unit): Reactive { return object: BaseReactive(ReactiveState.notActive) { var job: Job? = null override fun activate() { @@ -45,7 +45,7 @@ fun reactiveProcess(scope: CoroutineScope = AppScope, emitter: suspend Emitt } } } -fun rawReactiveProcess(scope: CoroutineScope = AppScope, emitter: suspend Emitter>.() -> Unit): Reactive { +public fun rawReactiveProcess(scope: CoroutineScope = AppScope, emitter: suspend Emitter>.() -> Unit): Reactive { return object: BaseReactive(ReactiveState.notActive) { var job: Job? = null override fun activate() { diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/core/signals.kt b/src/commonMain/kotlin/com/lightningkite/reactive/core/signals.kt index 8587986..1718e56 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/core/signals.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/core/signals.kt @@ -8,7 +8,7 @@ import kotlin.jvm.JvmInline * A reactive value that exposes its state and allows direct mutation. * Used for low-level reactive state management. */ -class RawReactive(start: ReactiveState = ReactiveState.notReady) : BaseReactive(start) { +public class RawReactive(start: ReactiveState = ReactiveState.notReady) : BaseReactive(start) { override var state: ReactiveState get() = super.state public set(value) { super.state = value } @@ -18,13 +18,13 @@ class RawReactive(start: ReactiveState = ReactiveState.notReady) : BaseRea * A basic implementation of a listenable object. * Can invoke all listeners and provides a unique identifier for debugging. */ -class BasicListenable : BaseListenable() { +public class BasicListenable : BaseListenable() { private var id = Random.nextInt(0, 100000) override fun toString(): String { return "BasicListenable($id)" } - fun invokeAll() { + public fun invokeAll() { super.invokeAllListeners() } } @@ -47,18 +47,18 @@ class BasicListenable : BaseListenable() { * number.value = 2 // prints "Number: 2" * ``` */ -class Signal(startValue: T) : MutableReactiveValue, BaseReactiveValue(startValue) +public class Signal(startValue: T) : MutableReactiveValue, BaseReactiveValue(startValue) /** * A reactive value that can be set after initialization and unset to a not-ready state. * Useful for cases where the value is not available at construction time. */ -class LateInitSignal() : ReactiveWithMutableValue, BaseReactive() { +public class LateInitSignal() : ReactiveWithMutableValue, BaseReactive() { override fun valueSet(value: T) { state = ReactiveState(value) } - fun unset() { + public fun unset() { state = ReactiveState.notReady } } @@ -71,6 +71,6 @@ class LateInitSignal() : ReactiveWithMutableValue, BaseReactive() { * no overhead. */ @JvmInline -value class Constant(override val value: T) : ReactiveValue { +public value class Constant(override val value: T) : ReactiveValue { override fun addListener(listener: () -> Unit): Release = Listenable.Never.NOOP_RELEASE } diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/deprecated.kt b/src/commonMain/kotlin/com/lightningkite/reactive/deprecated.kt index 9edca9a..a875866 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/deprecated.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/deprecated.kt @@ -32,84 +32,84 @@ import kotlin.jvm.JvmName @Deprecated("Only exists to not break imports", level = DeprecationLevel.ERROR) -fun Nothing.bind(): Nothing = TODO() +public fun Nothing.bind(): Nothing = TODO() // Naming deprecations @Deprecated("Use Reactive", ReplaceWith("Reactive", "com.lightningkite.reactive.core")) -typealias Readable = Reactive +public typealias Readable = Reactive @Deprecated("Use MutableReactive", ReplaceWith("MutableReactive", "com.lightningkite.reactive.core")) -typealias Writable = MutableReactive +public typealias Writable = MutableReactive @Deprecated("Use Remember", ReplaceWith("Remember", "com.lightningkite.reactive.core")) -typealias SharedReadable = Remember +public typealias SharedReadable = Remember @Deprecated("Use ReactiveValue", ReplaceWith("ReactiveValue", "com.lightningkite.reactive.core")) -typealias ImmediateReadable = ReactiveValue +public typealias ImmediateReadable = ReactiveValue @Deprecated("Use MutableReactiveValue", ReplaceWith("MutableReactiveValue", "com.lightningkite.reactive.core")) -typealias ImmediateWritable = MutableReactiveValue +public typealias ImmediateWritable = MutableReactiveValue @Deprecated("Use ReactiveWithMutableValue", ReplaceWith("ReactiveWithMutableValue", "com.lightningkite.reactive.core")) -typealias ReadableWithImmediateWrite = ReactiveWithMutableValue +public typealias ReadableWithImmediateWrite = ReactiveWithMutableValue @Deprecated("Use MutableWithReactiveValue", ReplaceWith("MutableWithReactiveValue", "com.lightningkite.reactive.core")) -typealias ImmediateReadableWithWrite = MutableWithReactiveValue +public typealias ImmediateReadableWithWrite = MutableWithReactiveValue @Deprecated("Use Signal", ReplaceWith("Signal", "com.lightningkite.reactive.core")) -typealias Property = Signal +public typealias Property = Signal @Deprecated("Use MutableRemember", ReplaceWith("MutableRemember", "com.lightningkite.reactive.core")) -typealias LazyProperty = MutableRemember +public typealias LazyProperty = MutableRemember @Deprecated("Use DebounceReactive", ReplaceWith("DebounceReactive", "com.lightningkite.reactive.extensions")) -typealias DebounceReadable = DebounceReactive +public typealias DebounceReadable = DebounceReactive @OptIn(InternalReactiveApi::class) @Deprecated("Use InternalSignalWrapper", ReplaceWith("InternalSignalWrapper", "com.lightningkite.reactive.core")) -typealias InternalReadableWrapper = InternalReactiveWrapper +public typealias InternalReadableWrapper = InternalReactiveWrapper @Deprecated("Use RawReactive", ReplaceWith("RawReactive", "com.lightningkite.reactive.core")) -typealias RawReadable = RawReactive +public typealias RawReadable = RawReactive @Deprecated("Use ReactiveState", ReplaceWith("ReactiveState", "com.lightningkite.reactive.core")) -typealias ReadableState = ReactiveState +public typealias ReadableState = ReactiveState @Deprecated("Use SignalEmitter", ReplaceWith("SignalEmitter", "com.lightningkite.reactive.core")) -typealias ReadableEmitter = Emitter +public typealias ReadableEmitter = Emitter @Deprecated("Use MutableValue", ReplaceWith("MutableValue", "com.lightningkite.reactive.core")) -typealias ImmediateWriteOnly = MutableValue +public typealias ImmediateWriteOnly = MutableValue @Deprecated("Use BaseReactiveValue", ReplaceWith("BaseReactiveValue", "com.lightningkite.reactive.core")) -typealias BaseImmediateReadable = BaseReactiveValue +public typealias BaseImmediateReadable = BaseReactiveValue @Deprecated("Use BaseReactive", ReplaceWith("BaseReactive", "com.lightningkite.reactive.core")) -typealias BaseReadable = BaseReactive +public typealias BaseReadable = BaseReactive @Deprecated("Use BaseReactive", ReplaceWith("BaseReactive", "com.lightningkite.reactive.core")) -typealias BaseWritable = BaseReactive +public typealias BaseWritable = BaseReactive @Deprecated("Use BaseReactiveValue", ReplaceWith("BaseReactiveValue", "com.lightningkite.reactive.core")) -typealias BaseReadWrite = BaseReactiveValue +public typealias BaseReadWrite = BaseReactiveValue @Deprecated("Use LateInitReactiveValue", ReplaceWith("LateInitReactiveValue", "com.lightningkite.reactive.core")) -typealias LateInitProperty = LateInitSignal +public typealias LateInitProperty = LateInitSignal @Deprecated("Use remember", ReplaceWith("remember", "com.lightningkite.reactive.core")) -fun shared(coroutineContext: CoroutineContext = Dispatchers.Unconfined, useLastWhileLoading: Boolean = false, action: ReactiveContext.() -> T): Reactive = +public fun shared(coroutineContext: CoroutineContext = Dispatchers.Unconfined, useLastWhileLoading: Boolean = false, action: ReactiveContext.() -> T): Reactive = remember(coroutineContext, useLastWhileLoading, null, action = action) @Deprecated("Use reactiveProcess", ReplaceWith("reactiveProcess", "com.lightningkite.reactive.core")) -fun sharedProcess(scope: CoroutineScope = AppScope, emitter: suspend Emitter.() -> Unit): Reactive = reactiveProcess(scope, emitter) +public fun sharedProcess(scope: CoroutineScope = AppScope, emitter: suspend Emitter.() -> Unit): Reactive = reactiveProcess(scope, emitter) @Deprecated("Use reactiveProcess", ReplaceWith("reactiveProcess", "com.lightningkite.reactive.core")) @JvmName("sharedProcessReceiving") -fun CoroutineScope.sharedProcess(emitter: suspend Emitter.() -> Unit): Reactive = reactiveProcess(emitter) +public fun CoroutineScope.sharedProcess(emitter: suspend Emitter.() -> Unit): Reactive = reactiveProcess(emitter) @Deprecated("Use reactiveState", ReplaceWith("reactiveState", "com.lightningkite.reactive.core")) -inline fun readableState(action: () -> T): ReactiveState = reactiveState(action) +public inline fun readableState(action: () -> T): ReactiveState = reactiveState(action) @Deprecated("Use toReactiveState", ReplaceWith("toReactiveState", "com.lightningkite.reactive.core")) -fun Result.toReadableState(): ReactiveState = toReactiveState() \ No newline at end of file +public fun Result.toReadableState(): ReactiveState = toReactiveState() \ No newline at end of file diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/extensions/Listenable.ext.kt b/src/commonMain/kotlin/com/lightningkite/reactive/extensions/Listenable.ext.kt index b6efdc2..c28e095 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/extensions/Listenable.ext.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/extensions/Listenable.ext.kt @@ -6,22 +6,22 @@ import com.lightningkite.reactive.core.Reactive import com.lightningkite.reactive.core.ReactiveState import com.lightningkite.reactive.core.ReactiveValue -inline fun Reactive.addStateListener(crossinline listener: (ReactiveState) -> Unit): Release { +public inline fun Reactive.addStateListener(crossinline listener: (ReactiveState) -> Unit): Release { return addListener { listener(state) } } -inline fun Reactive.addAndRunStateListener(crossinline listener: (ReactiveState) -> Unit): Release { +public inline fun Reactive.addAndRunStateListener(crossinline listener: (ReactiveState) -> Unit): Release { val listener: () -> Unit = { listener(state) } val release = addListener(listener) listener() return release } -inline fun ReactiveValue.addValueListener(crossinline listener: (T) -> Unit): Release { +public inline fun ReactiveValue.addValueListener(crossinline listener: (T) -> Unit): Release { return addListener { listener(value) } } -inline fun ReactiveValue.addAndRunValueListener(crossinline listener: (T) -> Unit): Release { +public inline fun ReactiveValue.addAndRunValueListener(crossinline listener: (T) -> Unit): Release { val listener: () -> Unit = { listener(value) } val release = addListener(listener) listener() diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/extensions/ReactiveState.ext.kt b/src/commonMain/kotlin/com/lightningkite/reactive/extensions/ReactiveState.ext.kt index 24f9465..47332df 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/extensions/ReactiveState.ext.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/extensions/ReactiveState.ext.kt @@ -2,7 +2,7 @@ package com.lightningkite.reactive.extensions import com.lightningkite.reactive.core.ReactiveState -inline fun ReactiveState.getOrElse(default: () -> R): R { +public inline fun ReactiveState.getOrElse(default: () -> R): R { return handle( success = { it }, exception = { default() }, diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/extensions/WaitForNotNull.kt b/src/commonMain/kotlin/com/lightningkite/reactive/extensions/WaitForNotNull.kt index bbb098f..1d3b1d2 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/extensions/WaitForNotNull.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/extensions/WaitForNotNull.kt @@ -22,14 +22,14 @@ internal class WaitForNotNull(val wraps: Reactive) : Reactive { override fun equals(other: Any?): Boolean = other is WaitForNotNull<*> && this.wraps == other.wraps } -val Reactive.waitForNotNull: Reactive get() = WaitForNotNull(this) +public val Reactive.waitForNotNull: Reactive get() = WaitForNotNull(this) -val MutableReactive.waitForNotNull: MutableReactive get() = +public val MutableReactive.waitForNotNull: MutableReactive get() = object : MutableReactive, Reactive by (this as Reactive).waitForNotNull { // DO NOT REMOVE THE TYPECAST override suspend fun set(value: T) = this@waitForNotNull.set(value) } -suspend fun Reactive.awaitNotNull(): T { +public suspend fun Reactive.awaitNotNull(): T { val basis = await() return basis ?: suspendCancellableCoroutine { } } \ No newline at end of file diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/extensions/commaString.kt b/src/commonMain/kotlin/com/lightningkite/reactive/extensions/commaString.kt index e78f3a7..5a12a2c 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/extensions/commaString.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/extensions/commaString.kt @@ -4,7 +4,7 @@ import kotlin.math.pow import kotlin.math.roundToInt -fun Double.toStringNoExponential(): String { +public fun Double.toStringNoExponential(): String { val preDecimal = toLong().toString() val r = rem(1) if (r == 0.0) return preDecimal @@ -14,15 +14,15 @@ fun Double.toStringNoExponential(): String { else return preDecimal + "." + postDecimal.toString().padStart(availableDigits, '0').trimEnd('0') } -fun Double.commaString(): String { +public fun Double.commaString(): String { val clean = this.toStringNoExponential().filter { it.isDigit() || it in setOf('.', '-') } val preDecimal = clean.substringBefore('.').reversed().chunked(3) { it.reversed() }.reversed().joinToString(",") val postDecimal = clean.substringAfter('.', "") return if (clean.contains('.')) "$preDecimal.$postDecimal" else preDecimal } -fun Int.commaString(): String { +public fun Int.commaString(): String { return toString().substringBefore('.').reversed().chunked(3) { it.reversed() }.reversed().joinToString(",") } -fun Long.commaString(): String { +public fun Long.commaString(): String { return toString().substringBefore('.').reversed().chunked(3) { it.reversed() }.reversed().joinToString(",") } diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/extensions/commonLenses.kt b/src/commonMain/kotlin/com/lightningkite/reactive/extensions/commonLenses.kt index 0db43fa..6eca470 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/extensions/commonLenses.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/extensions/commonLenses.kt @@ -9,78 +9,78 @@ import com.lightningkite.reactive.lensing.validation.MutableValidatedValue import kotlin.collections.plus import kotlin.jvm.JvmName -infix fun MutableReactive.equalTo(value: T): MutableReactive = lens( +public infix fun MutableReactive.equalTo(value: T): MutableReactive = lens( get = { it == value }, modify = { o, it -> if (it) value else o } ) -fun MutableReactive.notNull(default: T): MutableReactive = lens( +public fun MutableReactive.notNull(default: T): MutableReactive = lens( get = { it ?: default }, set = { it } ) -fun MutableReactive.nullToBlank(): MutableReactive = lens( +public fun MutableReactive.nullToBlank(): MutableReactive = lens( get = { it ?: "" }, set = { it.takeUnless { it.isBlank() } } ) -infix fun MutableReactive>.contains(value: T): MutableReactive = lens( +public infix fun MutableReactive>.contains(value: T): MutableReactive = lens( get = { value in it }, modify = { items, bool -> if (bool) items + value else items - value } ) @JvmName("containsList") -infix fun MutableReactive>.contains(value: T): MutableReactive = lens( +public infix fun MutableReactive>.contains(value: T): MutableReactive = lens( get = { value in it }, modify = { items, bool -> if (bool) items + value else items - value } ) @JvmName("writableStringAsDouble") -fun MutableReactive.asDouble(): MutableReactive = lens(get = { it.filter { it.isDigit() || it == '.' }.toDoubleOrNull() }, set = { it?.commaString() ?: "" }) +public fun MutableReactive.asDouble(): MutableReactive = lens(get = { it.filter { it.isDigit() || it == '.' }.toDoubleOrNull() }, set = { it?.commaString() ?: "" }) @JvmName("writableStringAsFloat") -fun MutableReactive.asFloat(): MutableReactive = lens(get = { it.filter { it.isDigit() || it == '.' }.toFloatOrNull() }, set = { it?.toDouble()?.commaString() ?: "" }) +public fun MutableReactive.asFloat(): MutableReactive = lens(get = { it.filter { it.isDigit() || it == '.' }.toFloatOrNull() }, set = { it?.toDouble()?.commaString() ?: "" }) @JvmName("writableStringAsByte") -fun MutableReactive.asByte(): MutableReactive = lens(get = { it.filter { it.isDigit() || it == '.' }.toByteOrNull() }, set = { it?.toInt()?.commaString() ?: "" }) +public fun MutableReactive.asByte(): MutableReactive = lens(get = { it.filter { it.isDigit() || it == '.' }.toByteOrNull() }, set = { it?.toInt()?.commaString() ?: "" }) @JvmName("writableStringAsShort") -fun MutableReactive.asShort(): MutableReactive = lens(get = { it.filter { it.isDigit() || it == '.' }.toShortOrNull() }, set = { it?.toInt()?.commaString() ?: "" }) +public fun MutableReactive.asShort(): MutableReactive = lens(get = { it.filter { it.isDigit() || it == '.' }.toShortOrNull() }, set = { it?.toInt()?.commaString() ?: "" }) @JvmName("writableStringAsInt") -fun MutableReactive.asInt(): MutableReactive = lens(get = { it.filter { it.isDigit() || it == '.' }.toIntOrNull() }, set = { it?.commaString() ?: "" }) +public fun MutableReactive.asInt(): MutableReactive = lens(get = { it.filter { it.isDigit() || it == '.' }.toIntOrNull() }, set = { it?.commaString() ?: "" }) @JvmName("writableStringAsLong") -fun MutableReactive.asLong(): MutableReactive = lens(get = { it.filter { it.isDigit() || it == '.' }.toLongOrNull() }, set = { it?.commaString() ?: "" }) +public fun MutableReactive.asLong(): MutableReactive = lens(get = { it.filter { it.isDigit() || it == '.' }.toLongOrNull() }, set = { it?.commaString() ?: "" }) @JvmName("writableStringAsByteHex") -fun MutableReactive.asByteHex(): MutableReactive = lens(get = { it.toByteOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableReactive.asByteHex(): MutableReactive = lens(get = { it.toByteOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsUByteHex") -fun MutableReactive.asUByteHex(): MutableReactive = lens(get = { it.toUByteOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableReactive.asUByteHex(): MutableReactive = lens(get = { it.toUByteOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsShortHex") -fun MutableReactive.asShortHex(): MutableReactive = lens(get = { it.toShortOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableReactive.asShortHex(): MutableReactive = lens(get = { it.toShortOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsUShortHex") -fun MutableReactive.asUShortHex(): MutableReactive = lens(get = { it.toUShortOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableReactive.asUShortHex(): MutableReactive = lens(get = { it.toUShortOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsIntHex") -fun MutableReactive.asIntHex(): MutableReactive = lens(get = { it.toIntOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableReactive.asIntHex(): MutableReactive = lens(get = { it.toIntOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsUIntHex") -fun MutableReactive.asUIntHex(): MutableReactive = lens(get = { it.toUIntOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableReactive.asUIntHex(): MutableReactive = lens(get = { it.toUIntOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsLongHex") -fun MutableReactive.asLongHex(): MutableReactive = lens(get = { it.toLongOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableReactive.asLongHex(): MutableReactive = lens(get = { it.toLongOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsULongHex") -fun MutableReactive.asULongHex(): MutableReactive = lens(get = { it.toULongOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableReactive.asULongHex(): MutableReactive = lens(get = { it.toULongOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableIntAsDoubleNullable") -fun MutableReactive.asDouble(): MutableReactive = lens(get = { it?.toDouble() }, set = { it?.toInt() }) +public fun MutableReactive.asDouble(): MutableReactive = lens(get = { it?.toDouble() }, set = { it?.toInt() }) -fun MutableReactive.nullToZero(): MutableReactive = +public fun MutableReactive.nullToZero(): MutableReactive = object : MutableReactive, Reactive by this { override suspend fun set(value: Double?) { this@nullToZero.set(value ?: 0.0) @@ -88,144 +88,144 @@ fun MutableReactive.nullToZero(): MutableReactive = } @JvmName("writableStringAsDouble") -fun MutableReactiveValue.asDouble(): MutableReactiveValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toDoubleOrNull() }, set = { it?.commaString() ?: "" }) +public fun MutableReactiveValue.asDouble(): MutableReactiveValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toDoubleOrNull() }, set = { it?.commaString() ?: "" }) @JvmName("writableStringAsFloat") -fun MutableReactiveValue.asFloat(): MutableReactiveValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toFloatOrNull() }, set = { it?.toDouble()?.commaString() ?: "" }) +public fun MutableReactiveValue.asFloat(): MutableReactiveValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toFloatOrNull() }, set = { it?.toDouble()?.commaString() ?: "" }) @JvmName("writableStringAsByte") -fun MutableReactiveValue.asByte(): MutableReactiveValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toByteOrNull() }, set = { it?.toInt()?.commaString() ?: "" }) +public fun MutableReactiveValue.asByte(): MutableReactiveValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toByteOrNull() }, set = { it?.toInt()?.commaString() ?: "" }) @JvmName("writableStringAsShort") -fun MutableReactiveValue.asShort(): MutableReactiveValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toShortOrNull() }, set = { it?.toInt()?.commaString() ?: "" }) +public fun MutableReactiveValue.asShort(): MutableReactiveValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toShortOrNull() }, set = { it?.toInt()?.commaString() ?: "" }) @JvmName("writableStringAsInt") -fun MutableReactiveValue.asInt(): MutableReactiveValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toIntOrNull() }, set = { it?.commaString() ?: "" }) +public fun MutableReactiveValue.asInt(): MutableReactiveValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toIntOrNull() }, set = { it?.commaString() ?: "" }) @JvmName("writableStringAsLong") -fun MutableReactiveValue.asLong(): MutableReactiveValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toLongOrNull() }, set = { it?.commaString() ?: "" }) +public fun MutableReactiveValue.asLong(): MutableReactiveValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toLongOrNull() }, set = { it?.commaString() ?: "" }) @JvmName("writableStringAsByteHex") -fun MutableReactiveValue.asByteHex(): MutableReactiveValue = lens(get = { it.toByteOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableReactiveValue.asByteHex(): MutableReactiveValue = lens(get = { it.toByteOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsUByteHex") -fun MutableReactiveValue.asUByteHex(): MutableReactiveValue = lens(get = { it.toUByteOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableReactiveValue.asUByteHex(): MutableReactiveValue = lens(get = { it.toUByteOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsShortHex") -fun MutableReactiveValue.asShortHex(): MutableReactiveValue = lens(get = { it.toShortOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableReactiveValue.asShortHex(): MutableReactiveValue = lens(get = { it.toShortOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsUShortHex") -fun MutableReactiveValue.asUShortHex(): MutableReactiveValue = lens(get = { it.toUShortOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableReactiveValue.asUShortHex(): MutableReactiveValue = lens(get = { it.toUShortOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsIntHex") -fun MutableReactiveValue.asIntHex(): MutableReactiveValue = lens(get = { it.toIntOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableReactiveValue.asIntHex(): MutableReactiveValue = lens(get = { it.toIntOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsUIntHex") -fun MutableReactiveValue.asUIntHex(): MutableReactiveValue = lens(get = { it.toUIntOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableReactiveValue.asUIntHex(): MutableReactiveValue = lens(get = { it.toUIntOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsLongHex") -fun MutableReactiveValue.asLongHex(): MutableReactiveValue = lens(get = { it.toLongOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableReactiveValue.asLongHex(): MutableReactiveValue = lens(get = { it.toLongOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsULongHex") -fun MutableReactiveValue.asULongHex(): MutableReactiveValue = lens(get = { it.toULongOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableReactiveValue.asULongHex(): MutableReactiveValue = lens(get = { it.toULongOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableIntAsDoubleNullable") -fun MutableReactiveValue.asDouble(): MutableReactiveValue = lens(get = { it?.toDouble() }, set = { it?.toInt() }) +public fun MutableReactiveValue.asDouble(): MutableReactiveValue = lens(get = { it?.toDouble() }, set = { it?.toInt() }) // Validated variants -fun MutableValidated.nullToBlank(): MutableValidated = lens( +public fun MutableValidated.nullToBlank(): MutableValidated = lens( get = { it ?: "" }, set = { it.takeUnless { it.isBlank() } } ) @JvmName("writableStringAsDoubleValidated") -fun MutableValidated.asDouble(): MutableValidated = lens(get = { it.filter { it.isDigit() || it == '.' }.toDoubleOrNull() }, set = { it?.commaString() ?: "" }) +public fun MutableValidated.asDouble(): MutableValidated = lens(get = { it.filter { it.isDigit() || it == '.' }.toDoubleOrNull() }, set = { it?.commaString() ?: "" }) @JvmName("writableStringAsFloatValidated") -fun MutableValidated.asFloat(): MutableValidated = lens(get = { it.filter { it.isDigit() || it == '.' }.toFloatOrNull() }, set = { it?.toDouble()?.commaString() ?: "" }) +public fun MutableValidated.asFloat(): MutableValidated = lens(get = { it.filter { it.isDigit() || it == '.' }.toFloatOrNull() }, set = { it?.toDouble()?.commaString() ?: "" }) @JvmName("writableStringAsByteValidated") -fun MutableValidated.asByte(): MutableValidated = lens(get = { it.filter { it.isDigit() || it == '.' }.toByteOrNull() }, set = { it?.toInt()?.commaString() ?: "" }) +public fun MutableValidated.asByte(): MutableValidated = lens(get = { it.filter { it.isDigit() || it == '.' }.toByteOrNull() }, set = { it?.toInt()?.commaString() ?: "" }) @JvmName("writableStringAsShortValidated") -fun MutableValidated.asShort(): MutableValidated = lens(get = { it.filter { it.isDigit() || it == '.' }.toShortOrNull() }, set = { it?.toInt()?.commaString() ?: "" }) +public fun MutableValidated.asShort(): MutableValidated = lens(get = { it.filter { it.isDigit() || it == '.' }.toShortOrNull() }, set = { it?.toInt()?.commaString() ?: "" }) @JvmName("writableStringAsIntValidated") -fun MutableValidated.asInt(): MutableValidated = lens(get = { it.filter { it.isDigit() || it == '.' }.toIntOrNull() }, set = { it?.commaString() ?: "" }) +public fun MutableValidated.asInt(): MutableValidated = lens(get = { it.filter { it.isDigit() || it == '.' }.toIntOrNull() }, set = { it?.commaString() ?: "" }) @JvmName("writableStringAsLongValidated") -fun MutableValidated.asLong(): MutableValidated = lens(get = { it.filter { it.isDigit() || it == '.' }.toLongOrNull() }, set = { it?.commaString() ?: "" }) +public fun MutableValidated.asLong(): MutableValidated = lens(get = { it.filter { it.isDigit() || it == '.' }.toLongOrNull() }, set = { it?.commaString() ?: "" }) @JvmName("writableStringAsByteHexValidated") -fun MutableValidated.asByteHex(): MutableValidated = lens(get = { it.toByteOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableValidated.asByteHex(): MutableValidated = lens(get = { it.toByteOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsUByteHexValidated") -fun MutableValidated.asUByteHex(): MutableValidated = lens(get = { it.toUByteOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableValidated.asUByteHex(): MutableValidated = lens(get = { it.toUByteOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsShortHexValidated") -fun MutableValidated.asShortHex(): MutableValidated = lens(get = { it.toShortOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableValidated.asShortHex(): MutableValidated = lens(get = { it.toShortOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsUShortHexValidated") -fun MutableValidated.asUShortHex(): MutableValidated = lens(get = { it.toUShortOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableValidated.asUShortHex(): MutableValidated = lens(get = { it.toUShortOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsIntHexValidated") -fun MutableValidated.asIntHex(): MutableValidated = lens(get = { it.toIntOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableValidated.asIntHex(): MutableValidated = lens(get = { it.toIntOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsUIntHexValidated") -fun MutableValidated.asUIntHex(): MutableValidated = lens(get = { it.toUIntOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableValidated.asUIntHex(): MutableValidated = lens(get = { it.toUIntOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsLongHexValidated") -fun MutableValidated.asLongHex(): MutableValidated = lens(get = { it.toLongOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableValidated.asLongHex(): MutableValidated = lens(get = { it.toLongOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsULongHexValidated") -fun MutableValidated.asULongHex(): MutableValidated = lens(get = { it.toULongOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableValidated.asULongHex(): MutableValidated = lens(get = { it.toULongOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableIntAsDoubleNullableValidated") -fun MutableValidated.asDouble(): MutableValidated = lens(get = { it?.toDouble() }, set = { it?.toInt() }) +public fun MutableValidated.asDouble(): MutableValidated = lens(get = { it?.toDouble() }, set = { it?.toInt() }) @JvmName("writableStringAsDoubleValidatedValue") -fun MutableValidatedValue.asDouble(): MutableValidatedValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toDoubleOrNull() }, set = { it?.commaString() ?: "" }) +public fun MutableValidatedValue.asDouble(): MutableValidatedValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toDoubleOrNull() }, set = { it?.commaString() ?: "" }) @JvmName("writableStringAsFloatValidatedValue") -fun MutableValidatedValue.asFloat(): MutableValidatedValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toFloatOrNull() }, set = { it?.toDouble()?.commaString() ?: "" }) +public fun MutableValidatedValue.asFloat(): MutableValidatedValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toFloatOrNull() }, set = { it?.toDouble()?.commaString() ?: "" }) @JvmName("writableStringAsByteValidatedValue") -fun MutableValidatedValue.asByte(): MutableValidatedValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toByteOrNull() }, set = { it?.toInt()?.commaString() ?: "" }) +public fun MutableValidatedValue.asByte(): MutableValidatedValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toByteOrNull() }, set = { it?.toInt()?.commaString() ?: "" }) @JvmName("writableStringAsShortValidatedValue") -fun MutableValidatedValue.asShort(): MutableValidatedValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toShortOrNull() }, set = { it?.toInt()?.commaString() ?: "" }) +public fun MutableValidatedValue.asShort(): MutableValidatedValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toShortOrNull() }, set = { it?.toInt()?.commaString() ?: "" }) @JvmName("writableStringAsIntValidatedValue") -fun MutableValidatedValue.asInt(): MutableValidatedValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toIntOrNull() }, set = { it?.commaString() ?: "" }) +public fun MutableValidatedValue.asInt(): MutableValidatedValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toIntOrNull() }, set = { it?.commaString() ?: "" }) @JvmName("writableStringAsLongValidatedValue") -fun MutableValidatedValue.asLong(): MutableValidatedValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toLongOrNull() }, set = { it?.commaString() ?: "" }) +public fun MutableValidatedValue.asLong(): MutableValidatedValue = lens(get = { it.filter { it.isDigit() || it == '-' || it == '.'}.toLongOrNull() }, set = { it?.commaString() ?: "" }) @JvmName("writableStringAsByteHexValidatedValue") -fun MutableValidatedValue.asByteHex(): MutableValidatedValue = lens(get = { it.toByteOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableValidatedValue.asByteHex(): MutableValidatedValue = lens(get = { it.toByteOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsUByteHexValidatedValue") -fun MutableValidatedValue.asUByteHex(): MutableValidatedValue = lens(get = { it.toUByteOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableValidatedValue.asUByteHex(): MutableValidatedValue = lens(get = { it.toUByteOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsShortHexValidatedValue") -fun MutableValidatedValue.asShortHex(): MutableValidatedValue = lens(get = { it.toShortOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableValidatedValue.asShortHex(): MutableValidatedValue = lens(get = { it.toShortOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsUShortHexValidatedValue") -fun MutableValidatedValue.asUShortHex(): MutableValidatedValue = lens(get = { it.toUShortOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableValidatedValue.asUShortHex(): MutableValidatedValue = lens(get = { it.toUShortOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsIntHexValidatedValue") -fun MutableValidatedValue.asIntHex(): MutableValidatedValue = lens(get = { it.toIntOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableValidatedValue.asIntHex(): MutableValidatedValue = lens(get = { it.toIntOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsUIntHexValidatedValue") -fun MutableValidatedValue.asUIntHex(): MutableValidatedValue = lens(get = { it.toUIntOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableValidatedValue.asUIntHex(): MutableValidatedValue = lens(get = { it.toUIntOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsLongHexValidatedValue") -fun MutableValidatedValue.asLongHex(): MutableValidatedValue = lens(get = { it.toLongOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableValidatedValue.asLongHex(): MutableValidatedValue = lens(get = { it.toLongOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableStringAsULongHexValidatedValue") -fun MutableValidatedValue.asULongHex(): MutableValidatedValue = lens(get = { it.toULongOrNull(16) }, set = { it?.toString(16) ?: "" }) +public fun MutableValidatedValue.asULongHex(): MutableValidatedValue = lens(get = { it.toULongOrNull(16) }, set = { it?.toString(16) ?: "" }) @JvmName("writableIntAsDoubleNullableValidatedValue") -fun MutableValidatedValue.asDouble(): MutableValidatedValue = lens(get = { it?.toDouble() }, set = { it?.toInt() }) +public fun MutableValidatedValue.asDouble(): MutableValidatedValue = lens(get = { it?.toDouble() }, set = { it?.toInt() }) diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/extensions/debounce.kt b/src/commonMain/kotlin/com/lightningkite/reactive/extensions/debounce.kt index dc1d182..6c0a0c4 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/extensions/debounce.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/extensions/debounce.kt @@ -32,10 +32,10 @@ import kotlin.time.Duration.Companion.milliseconds * * @see DebounceListenable */ -class DebounceReactive( - val source: Reactive, - val scope: CoroutineScope, - val duration: Duration +public class DebounceReactive( + public val source: Reactive, + public val scope: CoroutineScope, + public val duration: Duration ) : Reactive, Listenable by DebounceListenable(source, scope, duration) { override val state: ReactiveState get() = source.state } @@ -60,7 +60,7 @@ class DebounceReactive( * * @see DebounceReactive */ -class DebounceListenable(val source: Listenable, val scope: CoroutineScope, val duration: Duration) : BaseListenable() { +public class DebounceListenable(public val source: Listenable, public val scope: CoroutineScope, public val duration: Duration) : BaseListenable() { @Volatile private var changeCount = 0 @@ -87,25 +87,25 @@ class DebounceListenable(val source: Listenable, val scope: CoroutineScope, val * Debounces listener notifications by [timeMs] milliseconds. State is always current. * @see DebounceReactive */ -fun Reactive.debounce(timeMs: Long, scope: CoroutineScope): Reactive = DebounceReactive(this, scope, timeMs.milliseconds) +public fun Reactive.debounce(timeMs: Long, scope: CoroutineScope): Reactive = DebounceReactive(this, scope, timeMs.milliseconds) /** * Debounces listener notifications by [duration]. State is always current. * @see DebounceReactive */ -fun Reactive.debounce(duration: Duration, scope: CoroutineScope): Reactive = DebounceReactive(this, scope, duration) +public fun Reactive.debounce(duration: Duration, scope: CoroutineScope): Reactive = DebounceReactive(this, scope, duration) /** * Debounces listener notifications by [timeMs] milliseconds. * @see DebounceListenable */ -fun Listenable.debounce(timeMs: Long, scope: CoroutineScope): Listenable = DebounceListenable(this, scope, timeMs.milliseconds) +public fun Listenable.debounce(timeMs: Long, scope: CoroutineScope): Listenable = DebounceListenable(this, scope, timeMs.milliseconds) /** * Debounces listener notifications by [duration]. * @see DebounceListenable */ -fun Listenable.debounce(duration: Duration, scope: CoroutineScope): Listenable = DebounceListenable(this, scope, duration) +public fun Listenable.debounce(duration: Duration, scope: CoroutineScope): Listenable = DebounceListenable(this, scope, duration) /** * Debounces write operations to this [MutableReactive]. @@ -119,7 +119,7 @@ fun Listenable.debounce(duration: Duration, scope: CoroutineScope): Listenable = * @param duration The debounce delay for write operations. * @return A [MutableReactive] wrapper with debounced writes. */ -fun MutableReactive.debounceWrite(duration: Duration): MutableReactive = object: MutableReactive by this { +public fun MutableReactive.debounceWrite(duration: Duration): MutableReactive = object: MutableReactive by this { @Volatile var setIndex = 0 diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/extensions/helpers.kt b/src/commonMain/kotlin/com/lightningkite/reactive/extensions/helpers.kt index 1aae25a..cd698f5 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/extensions/helpers.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/extensions/helpers.kt @@ -24,8 +24,8 @@ import kotlin.jvm.JvmName @JsName("invokeAllSafeMutable") @JvmName("invokeAllSafeMutable") -fun MutableList<() -> Unit>.invokeAllSafe() = toList().invokeAllSafe() -fun List<() -> Unit>.invokeAllSafe() = forEach { +public fun MutableList<() -> Unit>.invokeAllSafe(): Unit = toList().invokeAllSafe() +public fun List<() -> Unit>.invokeAllSafe(): Unit = forEach { try { it() } catch (e: Exception) { @@ -34,7 +34,7 @@ fun List<() -> Unit>.invokeAllSafe() = forEach { } } -var MutableValue.value: T +public var MutableValue.value: T @Deprecated("This is syntax sugar for SETTING values. Retrieving will always throw an exception.", level = DeprecationLevel.ERROR) get() = throw IllegalStateException("Attempted to retrieve value for set-only property") @JvmName("setValue2") @@ -42,7 +42,7 @@ var MutableValue.value: T valueSet(value) } -operator fun Listenable.plus(other: Listenable): Listenable = object: Listenable { +public operator fun Listenable.plus(other: Listenable): Listenable = object: Listenable { override fun addListener(listener: () -> Unit): Release { val a = this@plus.addListener(listener) val b = other.addListener(listener) @@ -53,14 +53,14 @@ operator fun Listenable.plus(other: Listenable): Listenable = object: Listenable } } -fun Reactive.withWrite(action: suspend Reactive.(T) -> Unit): MutableReactive = +public fun Reactive.withWrite(action: suspend Reactive.(T) -> Unit): MutableReactive = object : MutableReactive, Reactive by this { override suspend fun set(value: T) { action(this@withWrite, value) } } -fun Reactive.onNextSuccess(action: (T) -> Unit): Release? { +public fun Reactive.onNextSuccess(action: (T) -> Unit): Release? { // A successful state is one somebody is maintaining, so it needs no subscription at all. if (state.success) { state.onSuccess(action) @@ -82,28 +82,28 @@ fun Reactive.onNextSuccess(action: (T) -> Unit): Release? { return release } -fun MutableReactive.nullable(): MutableReactive = +public fun MutableReactive.nullable(): MutableReactive = object : MutableReactive, Reactive by this { override suspend fun set(value: T?) { if (value != null) this@nullable.set(value) } } -suspend infix fun MutableReactive.modify(action: suspend (T) -> T) { +public suspend infix fun MutableReactive.modify(action: suspend (T) -> T) { set(action(await())) } -suspend infix fun MutableReactiveValue.modify(action: suspend (T) -> T) { +public suspend infix fun MutableReactiveValue.modify(action: suspend (T) -> T) { value = action(value) } -suspend fun MutableReactive.toggle() { set(!awaitOnce()) } -fun MutableReactiveValue.toggle() { value = !value } +public suspend fun MutableReactive.toggle() { set(!awaitOnce()) } +public fun MutableReactiveValue.toggle() { value = !value } /** * Starts using this [ResourceUse] and tracks it as a dependency in future loops. * */ -fun DependencyTracker.use(resourceUse: ResourceUse) { +public fun DependencyTracker.use(resourceUse: ResourceUse) { if (existingDependency(resourceUse) == null) { registerDependency(resourceUse, resourceUse.beginUse()) } @@ -116,7 +116,7 @@ fun DependencyTracker.use(resourceUse: ResourceUse) { * If this scope contains a [DependencyChangeListener] then the resource use * is attached as a dependency. * */ -fun CoroutineScope.use(resourceUse: ResourceUse) { +public fun CoroutineScope.use(resourceUse: ResourceUse) { coroutineContext[DependencyChangeListener.Key]?.let { it.use(resourceUse) return @@ -125,23 +125,23 @@ fun CoroutineScope.use(resourceUse: ResourceUse) { resourceUse.beginUse().also(::onRemove) } -fun > WRITE.interceptWrite(action: suspend WRITE.(T) -> Unit): MutableReactive = +public fun > WRITE.interceptWrite(action: suspend WRITE.(T) -> Unit): MutableReactive = object : MutableReactive, Reactive by this { override suspend fun set(value: T) { action(this@interceptWrite, value) } } -fun Reactive>.flatten(): Reactive = remember { this@flatten()() } +public fun Reactive>.flatten(): Reactive = remember { this@flatten()() } -fun Reactive>.flatten(): MutableReactive = +public fun Reactive>.flatten(): MutableReactive = remember { this@flatten()() }.withWrite { // awaitOnce rather than reading state: if the outer reactive is lazy it has no value to // read unless something is listening, and the write would be silently dropped. this@flatten.awaitOnce().set(it) } -fun CoroutineScope.asyncReactive(action: suspend () -> T): Reactive { +public fun CoroutineScope.asyncReactive(action: suspend () -> T): Reactive { val prop = LateInitSignal() launch { prop.value = action() @@ -150,7 +150,7 @@ fun CoroutineScope.asyncReactive(action: suspend () -> T): Reactive { } @OptIn(ExperimentalCoroutinesApi::class) -fun Deferred.toReactive() = object : BaseReactive() { +public fun Deferred.toReactive(): Reactive = object : BaseReactive() { init { this@toReactive[Job]?.invokeOnCompletion { state = if (it == null) ReactiveState(getCompleted()) else ReactiveState.exception(it as? Exception ?: Exception("Must be exception, not throwable", it)) @@ -158,15 +158,15 @@ fun Deferred.toReactive() = object : BaseReactive() { } } -suspend operator fun (ReactiveContext.()->R).invoke(): R { +public suspend operator fun (ReactiveContext.()->R).invoke(): R { return remember { this@invoke() }.awaitOnce() } -suspend operator fun (ReactiveContext.(A)->R).invoke(a: A): R { +public suspend operator fun (ReactiveContext.(A)->R).invoke(a: A): R { return remember { this@invoke(a) }.awaitOnce() } -suspend operator fun (ReactiveContext.(A, B)->R).invoke(a: A, b: B): R { +public suspend operator fun (ReactiveContext.(A, B)->R).invoke(a: A, b: B): R { return remember { this@invoke(a, b) }.awaitOnce() } -suspend operator fun (ReactiveContext.(A, B, C)->R).invoke(a: A, b: B, c: C): R { +public suspend operator fun (ReactiveContext.(A, B, C)->R).invoke(a: A, b: B, c: C): R { return remember { this@invoke(a, b, c) }.awaitOnce() } \ No newline at end of file diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/LensByElement.kt b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/LensByElement.kt index 88fe020..a6462e9 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/LensByElement.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/LensByElement.kt @@ -14,42 +14,43 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlin.collections.plus +import kotlin.coroutines.CoroutineContext import kotlin.coroutines.cancellation.CancellationException import kotlin.jvm.JvmName -fun MutableReactive>.lensByElementWithIdentity( +public fun MutableReactive>.lensByElementWithIdentity( identity: (E) -> ID, map: CoroutineScope.(MutableWithReactiveValue) -> W -) = LensByElement(this, identity = identity, elementLens = { it.map(it) }) +): LensByElement = LensByElement(this, identity = identity, elementLens = { it.map(it) }) -fun MutableReactive>.lensByElementWithIdentity( +public fun MutableReactive>.lensByElementWithIdentity( identity: (E) -> ID -) = LensElements(this, identity = identity, elementLens = { it }) +): LensElements = LensElements(this, identity = identity, elementLens = { it }) @JvmName("setLensByElementWithIdentity") @Suppress("Deprecation") -fun MutableReactive>.lensByElementWithIdentity( +public fun MutableReactive>.lensByElementWithIdentity( identity: (E) -> ID, map: CoroutineScope.(MutableWithReactiveValue) -> W -) = lens(get = { it.toList() }, set = { it.toSet() }).lensByElement(identity, map) +): LensByElement = lens(get = { it.toList() }, set = { it.toSet() }).lensByElement(identity, map) @JvmName("setLensByElementWithIdentity") @Suppress("Deprecation") -fun MutableReactive>.lensByElementWithIdentity( +public fun MutableReactive>.lensByElementWithIdentity( identity: (E) -> ID -) = lens(get = { it.toList() }, set = { it.toSet() }).lensByElement(identity) +): LensElements = lens(get = { it.toList() }, set = { it.toSet() }).lensByElement(identity) -typealias LensElements = LensByElement.Element> +public typealias LensElements = LensByElement.Element> -class LensByElement( - val source: MutableReactive>, - val identity: (E) -> ID, - val elementLens: (LensByElement.Element) -> T +public class LensByElement( + public val source: MutableReactive>, + public val identity: (E) -> ID, + public val elementLens: (LensByElement.Element) -> T ) : Reactive> { private val node = IssueNode(parent = (source as? MutableValidated)?.node).apply { connect() } - inner class Element internal constructor(valueInit: E) : MutableWithReactiveValue, MutableValidated, CoroutineScope { + public inner class Element internal constructor(valueInit: E) : MutableWithReactiveValue, MutableValidated, CoroutineScope { override val node: IssueNode = this@LensByElement.node.child() private var job = Job() @@ -58,7 +59,7 @@ class LensByElement( Reactive.reportException(throwable) } } - override val coroutineContext get() = restOfContext + job + override val coroutineContext: CoroutineContext get() = restOfContext + job internal var dead = false set(value) { @@ -68,7 +69,7 @@ class LensByElement( job.cancel() job = Job() } - var id: ID = identity(valueInit) + public var id: ID = identity(valueInit) private set private val listeners = ArrayList<() -> Unit>() override var value: E = valueInit @@ -114,10 +115,10 @@ class LensByElement( } } - val view = elementLens(this) + public val view: T = elementLens(this) } - inner class Elements : MutableReactive> { + public inner class Elements : MutableReactive> { override suspend fun set(value: List) { source.set(value.map { it.queuedOrValue }) } @@ -178,22 +179,22 @@ class LensByElement( } } - val elements = Elements() + public val elements: Elements = Elements() - fun newElement(e: E): Element = Element(e) - suspend fun add(index: Int, value: E): T { + public fun newElement(e: E): Element = Element(e) + public suspend fun add(index: Int, value: E): T { val newly = newElement(value) elements.set(elements.awaitOnce().toMutableList().apply { add(index, newly) }) return newly.view } - suspend fun add(value: E): T { + public suspend fun add(value: E): T { val newly = newElement(value) elements.set(elements.awaitOnce() + newly) return newly.view } - suspend fun upsert(value: E): T { + public suspend fun upsert(value: E): T { val id = identity(value) val existing = elements.awaitOnce().find { it.id == id } return if (existing == null) add(value) else { @@ -202,12 +203,12 @@ class LensByElement( } } - suspend fun remove(element: E) { + public suspend fun remove(element: E) { val id = identity(element) removeById(id) } - suspend fun removeById(id: ID) { + public suspend fun removeById(id: ID) { elements.set(elements.awaitOnce().filter { it.id != id }) } @@ -219,21 +220,21 @@ class LensByElement( @Deprecated("Be specific about what kind you need.") -fun MutableReactive>.lensByElement(identity: (E) -> ID, map: CoroutineScope.(MutableWithReactiveValue) -> W) = +public fun MutableReactive>.lensByElement(identity: (E) -> ID, map: CoroutineScope.(MutableWithReactiveValue) -> W): LensByElement = LensByElement(this, identity = identity, elementLens = { it.map(it) }) @Deprecated("Be specific about what kind you need.") -fun MutableReactive>.lensByElement(identity: (E) -> ID) = +public fun MutableReactive>.lensByElement(identity: (E) -> ID): LensElements = LensElements(this, identity = identity, elementLens = { it }) @Deprecated("Be specific about what kind you need.") @JvmName("setLensByElement") @Suppress("Deprecation") -fun MutableReactive>.lensByElement(identity: (E) -> ID, map: CoroutineScope.(MutableWithReactiveValue) -> W) = +public fun MutableReactive>.lensByElement(identity: (E) -> ID, map: CoroutineScope.(MutableWithReactiveValue) -> W): LensByElement = lens(get = { it.toList() }, set = { it.toSet() }).lensByElement(identity, map) @Deprecated("Be specific about what kind you need.") @JvmName("setLensByElement") @Suppress("Deprecation") -fun MutableReactive>.lensByElement(identity: (E) -> ID) = +public fun MutableReactive>.lensByElement(identity: (E) -> ID): LensElements = lens(get = { it.toList() }, set = { it.toSet() }).lensByElement(identity) diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/LensByElementAssumingSetNeverManipulates.kt b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/LensByElementAssumingSetNeverManipulates.kt index 58f8fe3..9ab154f 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/LensByElementAssumingSetNeverManipulates.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/LensByElementAssumingSetNeverManipulates.kt @@ -13,20 +13,20 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.cancel -interface MutableReactiveElement : MutableWithReactiveValue { - val index: ReactiveValue +public interface MutableReactiveElement : MutableWithReactiveValue { + public val index: ReactiveValue } /** * THIS ONLY WORKS IF THE `set` on the receiver *never* manipulates the input before notifying. */ -fun MutableReactive>.lensByElementAssumingSetNeverManipulates(): Reactive>> = +public fun MutableReactive>.lensByElementAssumingSetNeverManipulates(): Reactive>> = lensByElementAssumingSetNeverManipulates { it } /** * THIS ONLY WORKS IF THE `set` on the receiver *never* manipulates the input before notifying. */ -fun MutableReactive>.lensByElementAssumingSetNeverManipulates(map: CoroutineScope.(MutableReactiveElement) -> W): Reactive> = +public fun MutableReactive>.lensByElementAssumingSetNeverManipulates(map: CoroutineScope.(MutableReactiveElement) -> W): Reactive> = LensByElementAssumingSetNeverManipulates(this, map) diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/abstracts.kt b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/abstracts.kt index b489bae..a921c2a 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/abstracts.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/abstracts.kt @@ -10,7 +10,7 @@ import com.lightningkite.reactive.core.Reactive import com.lightningkite.reactive.core.ReactiveState import com.lightningkite.reactive.core.ReactiveValue -open class Lens, T, L>(val source: S, val get: (T) -> L) : BaseReactive() { +public open class Lens, T, L>(public val source: S, public val get: (T) -> L) : BaseReactive() { override var state: ReactiveState get() { if (myListen == null) super.state = source.state.map(get) @@ -40,9 +40,9 @@ open class Lens, T, L>(val source: S, val get: (T) -> L) : BaseR } } -open class SetLens( +public open class SetLens( source: MutableReactive, get: (O) -> T, - val set: (T) -> O + public val set: (T) -> O ) : Lens, O, T>(source, get), MutableReactive { override suspend fun set(value: T) { val transformed = set.invoke(value) @@ -51,10 +51,10 @@ open class SetLens( } } -open class ModifyLens( +public open class ModifyLens( source: MutableReactive, get: (O) -> T, - val modify: (O, T) -> O + public val modify: (O, T) -> O ) : Lens, O, T>(source, get), MutableReactive { override suspend fun set(value: T) { val transformed = modify(source.awaitOnce(), value) @@ -63,9 +63,9 @@ open class ModifyLens( } } -open class ValueLens, T, L>( - val source: S, - val get: (T) -> L +public open class ValueLens, T, L>( + public val source: S, + public val get: (T) -> L ) : BaseReactiveValue(source.value.let(get)) { override var value: L get() { @@ -94,7 +94,7 @@ open class ValueLens, T, L>( } } -open class SetValueLens(source: MutableReactiveValue, get: (O) -> T, val set: (T) -> O) : +public open class SetValueLens(source: MutableReactiveValue, get: (O) -> T, public val set: (T) -> O) : ValueLens, O, T>(source, get), MutableReactiveValue { override var value: T get() = super.value @@ -105,7 +105,7 @@ open class SetValueLens(source: MutableReactiveValue, get: (O) -> T, va } } -open class ModifyValueLens(source: MutableReactiveValue, get: (O) -> T, val modify: (O, T) -> O) : +public open class ModifyValueLens(source: MutableReactiveValue, get: (O) -> T, public val modify: (O, T) -> O) : ValueLens, O, T>(source, get), MutableReactiveValue { override var value: T get() = super.value @@ -116,10 +116,10 @@ open class ModifyValueLens(source: MutableReactiveValue, get: (O) -> T, } } -fun Reactive.lens(get: (T) -> L): Reactive = Lens(this, get) -fun ReactiveValue.lens(get: (T) -> L): ReactiveValue = ValueLens(this, get) +public fun Reactive.lens(get: (T) -> L): Reactive = Lens(this, get) +public fun ReactiveValue.lens(get: (T) -> L): ReactiveValue = ValueLens(this, get) -fun Listenable.lensListenable( +public fun Listenable.lensListenable( get: () -> T ): Reactive = ValueLens( object: ReactiveValue, Listenable by this { diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/IssueNode.kt b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/IssueNode.kt index b8d91ba..64a2e59 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/IssueNode.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/IssueNode.kt @@ -37,11 +37,11 @@ import com.lightningkite.reactive.core.remember * * @property parent The parent node in the validation tree, or null if this is the root. */ -class IssueNode(val parent: IssueNode? = null) : ResourceUse { +public class IssueNode(public val parent: IssueNode? = null) : ResourceUse { private val nodeIssue = Signal>(Constant(null)) - fun report(issue: Issue?) { nodeIssue.value = Constant(issue) } - fun reactiveReport(issue: ReactiveContext.() -> Issue?) { + public fun report(issue: Issue?) { nodeIssue.value = Constant(issue) } + public fun reactiveReport(issue: ReactiveContext.() -> Issue?) { nodeIssue.value = remember(action = issue) } @@ -58,7 +58,7 @@ class IssueNode(val parent: IssueNode? = null) : ResourceUse { * 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() } + public fun child(): IssueNode = IssueNode(this).apply { connect() } private var connected = false @@ -68,7 +68,7 @@ class IssueNode(val parent: IssueNode? = null) : ResourceUse { * * Useful for establishing validation dependencies once a set of data has become relevant. * */ - fun connect() { + public fun connect() { if (connected || parent == null) return connected = true parent.children.add(this) @@ -79,7 +79,7 @@ class IssueNode(val parent: IssueNode? = null) : ResourceUse { * * Useful for removing validation dependencies on data that is no longer relevant. * */ - fun disconnect() { + public fun disconnect() { if (!connected || parent == null) return connected = false parent.children.remove(this) @@ -90,7 +90,7 @@ class IssueNode(val parent: IssueNode? = null) : ResourceUse { return ::disconnect } - val issues : Reactive> = remember { + public val issues : Reactive> = remember { listOfNotNull(nodeIssue()()) + children().flatMap { it.issues() } } } @@ -98,16 +98,16 @@ class IssueNode(val parent: IssueNode? = null) : ResourceUse { /** * Represents a validation issue, which can be either a warning or an invalid state. */ -sealed interface Issue { - val summary: String - val description: String +public sealed interface Issue { + public val summary: String + public val description: String /** * Represents a warning issue. Does not necessarily prevent usage, but should be addressed. * * Values that result in an [Issue.Warning] being reported will still be used. */ - data class Warning( + public data class Warning( override val summary: String, override val description: String = summary ) : Issue @@ -118,7 +118,7 @@ sealed interface Issue { * Values that result in an [Issue.Invalid] being reported will be **discarded**. * I.e., if a lensed child of a [MutableValidated] reports [Issue.Invalid] on a value, it will not modify its parent. */ - data class Invalid( + public data class Invalid( override val summary : String, override val description: String = summary ) : Issue diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/Validated.kt b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/Validated.kt index a838710..48f0b4c 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/Validated.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/Validated.kt @@ -35,14 +35,14 @@ import com.lightningkite.reactive.core.ReactiveValue * * @see Issue */ -interface IssueTracking { - val node: IssueNode +public interface IssueTracking { + public val node: IssueNode } /** * Returns the current list of issues, this includes issues from this node and any child nodes. */ -val IssueTracking.issues get() = node.issues +public val IssueTracking.issues: Reactive> get() = node.issues /** * Reports a new issue to this node. @@ -51,7 +51,7 @@ val IssueTracking.issues get() = node.issues * * @param issue The issue to report, or null to clear this node's issue. */ -fun IssueTracking.report(issue: Issue?) = node.report(issue) +public fun IssueTracking.report(issue: Issue?): Unit = node.report(issue) /** * Represents a validated reactive value. See [IssueTracking] for more details about @@ -60,7 +60,7 @@ fun IssueTracking.report(issue: Issue?) = node.report(issue) * @see IssueTracking * @see Reactive */ -interface Validated : IssueTracking, Reactive +public interface Validated : IssueTracking, Reactive /** * Represents a validated reactive value with direct value access. See [IssueTracking] for more details about @@ -69,7 +69,7 @@ interface Validated : IssueTracking, Reactive * @see IssueTracking * @see ReactiveValue */ -interface ValidatedValue : IssueTracking, ReactiveValue, Validated +public interface ValidatedValue : IssueTracking, ReactiveValue, Validated /** * Represents a mutable validated reactive value. @@ -77,7 +77,7 @@ interface ValidatedValue : IssueTracking, ReactiveValue, Validated * Lensing a [MutableValidated] creates a child of this node in the validation tree. * See [IssueTracking] for more details about validation trees. */ -interface MutableValidated : IssueTracking, MutableReactive, Validated { +public interface MutableValidated : IssueTracking, MutableReactive, Validated { /** * Creates a transforming lens for type conversion. * Returns a [MutableValidated] for the sub-value, preserving validation. @@ -112,7 +112,7 @@ interface MutableValidated : IssueTracking, MutableReactive, Validated * @see IssueTracking * @see MutableReactiveValue */ -interface MutableValidatedValue : IssueTracking, MutableReactiveValue, ValidatedValue, MutableValidated { +public interface MutableValidatedValue : IssueTracking, MutableReactiveValue, ValidatedValue, MutableValidated { /** * Creates a transforming lens for type conversion. * Returns a [MutableValidatedValue] for the sub-value, preserving validation. diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/ValidatedDraft.kt b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/ValidatedDraft.kt index 8e82b35..88851a0 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/ValidatedDraft.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/ValidatedDraft.kt @@ -8,7 +8,7 @@ import com.lightningkite.reactive.core.Draft * @see Draft * @see MutableValidated * */ -interface ValidatedDraft : Draft, MutableValidated +public interface ValidatedDraft : Draft, MutableValidated private class RootValidatedDraft(val draft: Draft, reportTo: IssueNode? = null) : ValidatedDraft, Draft by draft { override val node: IssueNode = reportTo?.child() ?: IssueNode() @@ -17,4 +17,4 @@ private class RootValidatedDraft(val draft: Draft, reportTo: IssueNode? = override fun lens(get: (T) -> L, modify: (T, L) -> T): MutableValidated = ValidatedModifyLens(this, get, modify) } -fun Draft.validated(reportTo: IssueNode? = null): ValidatedDraft = RootValidatedDraft(this, reportTo) \ No newline at end of file +public fun Draft.validated(reportTo: IssueNode? = null): ValidatedDraft = RootValidatedDraft(this, reportTo) \ No newline at end of file diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/entrypoints.kt b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/entrypoints.kt index 494b900..736284b 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/entrypoints.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/entrypoints.kt @@ -48,7 +48,7 @@ private class RootMutableValidatedValue( * * @see IssueTracking */ -fun Reactive.validated(reportTo: IssueNode? = null): Validated = this as? Validated ?: RootValidated(this, reportTo) +public fun Reactive.validated(reportTo: IssueNode? = null): Validated = this as? Validated ?: RootValidated(this, reportTo) /** * Wraps a [MutableReactive] as a [MutableValidated]. @@ -61,7 +61,7 @@ fun Reactive.validated(reportTo: IssueNode? = null): Validated = this * * @see IssueTracking */ -fun MutableReactive.validated(reportTo: IssueNode? = null): MutableValidated = this as? MutableValidated ?: RootMutableValidated(this, reportTo) +public fun MutableReactive.validated(reportTo: IssueNode? = null): MutableValidated = this as? MutableValidated ?: RootMutableValidated(this, reportTo) /** * Wraps a [ReactiveValue] as a [ValidatedValue]. @@ -74,7 +74,7 @@ fun MutableReactive.validated(reportTo: IssueNode? = null): MutableValida * * @see IssueTracking */ -fun ReactiveValue.validated(reportTo: IssueNode? = null): ValidatedValue = this as? ValidatedValue ?: RootValidatedValue(this, reportTo) +public fun ReactiveValue.validated(reportTo: IssueNode? = null): ValidatedValue = this as? ValidatedValue ?: RootValidatedValue(this, reportTo) /** * Wraps a [MutableReactiveValue] as a [MutableValidatedValue]. @@ -87,4 +87,4 @@ fun ReactiveValue.validated(reportTo: IssueNode? = null): ValidatedValue< * * @see IssueTracking */ -fun MutableReactiveValue.validated(reportTo: IssueNode? = null): MutableValidatedValue = this as? MutableValidatedValue ?: RootMutableValidatedValue(this, reportTo) \ No newline at end of file +public fun MutableReactiveValue.validated(reportTo: IssueNode? = null): MutableValidatedValue = this as? MutableValidatedValue ?: RootMutableValidatedValue(this, reportTo) \ No newline at end of file diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/helpers.kt b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/helpers.kt index 1607315..c5322bf 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/helpers.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/helpers.kt @@ -14,10 +14,10 @@ import com.lightningkite.reactive.core.Signal * @param setOnIssue If true, issues are reported as [Issue.Warning]; if false, as [Issue.Invalid]. * @param validate Function that returns a string issue message or null for valid values. */ -fun MutableValidated.validate( +public fun MutableValidated.validate( setOnIssue: Boolean = true, validate: (T) -> String? -) = checkForIssue { value -> +): MutableValidated = checkForIssue { value -> validate(value)?.let { if (setOnIssue) Issue.Warning(it) else Issue.Invalid(it) @@ -33,10 +33,10 @@ fun MutableValidated.validate( * @param setOnIssue If true, issues are reported as [Issue.Warning]; if false, as [Issue.Invalid]. * @param validate Function that returns a string issue message or null for valid values. */ -fun MutableValidatedValue.validate( +public fun MutableValidatedValue.validate( setOnIssue: Boolean = true, validate: (T) -> String? -) = checkForIssue { value -> +): MutableValidatedValue = checkForIssue { value -> validate(value)?.let { if (setOnIssue) Issue.Warning(it) else Issue.Invalid(it) @@ -51,12 +51,12 @@ fun MutableValidatedValue.validate( * @param setOnIssue If true, issues are reported as [Issue.Warning]; if false, as [Issue.Invalid]. * @param condition Function that returns true if valid, false if invalid. */ -fun MutableValidated.assert( +public fun MutableValidated.assert( summary: String, description: String = summary, setOnIssue: Boolean = true, condition: (T) -> Boolean -) = checkForIssue { +): MutableValidated = checkForIssue { if (condition(it)) return@checkForIssue null if (setOnIssue) Issue.Warning(summary, description) @@ -71,12 +71,12 @@ fun MutableValidated.assert( * @param setOnIssue If true, issues are reported as [Issue.Warning]; if false, as [Issue.Invalid]. * @param condition Function that returns true if valid, false if invalid. */ -fun MutableValidatedValue.assert( +public fun MutableValidatedValue.assert( summary: String, description: String = summary, setOnIssue: Boolean = true, condition: (T) -> Boolean -) = checkForIssue { +): MutableValidatedValue = checkForIssue { if (condition(it)) return@checkForIssue null if (setOnIssue) Issue.Warning(summary, description) @@ -90,11 +90,11 @@ fun MutableValidatedValue.assert( * @param description Detailed description of the issue (defaults to [summary]). * @param setOnIssue If true, issues are reported as [Issue.Warning]; if false, as [Issue.Invalid]. */ -fun MutableValidated.assertNotNull( +public fun MutableValidated.assertNotNull( summary: String = "Cannot be blank", description: String = summary, setOnIssue: Boolean = true -) = assert(summary, description, setOnIssue) { it != null } +): MutableValidated = assert(summary, description, setOnIssue) { it != null } /** * Validates that the value of this [MutableValidatedValue] is not null. @@ -103,11 +103,11 @@ fun MutableValidated.assertNotNull( * @param description Detailed description of the issue (defaults to [summary]). * @param setOnIssue If true, issues are reported as [Issue.Warning]; if false, as [Issue.Invalid]. */ -fun MutableValidatedValue.assertNotNull( +public fun MutableValidatedValue.assertNotNull( summary: String = "Cannot be blank", description: String = summary, setOnIssue: Boolean = true -) = assert(summary, description, setOnIssue) { it != null } +): MutableValidatedValue = assert(summary, description, setOnIssue) { it != null } /** * Validates that the value of this [MutableValidated] is not blank. @@ -116,11 +116,11 @@ fun MutableValidatedValue.assertNotNull( * @param description Detailed description of the issue (defaults to [summary]). * @param setOnIssue If true, issues are reported as [Issue.Warning]; if false, as [Issue.Invalid]. */ -fun MutableValidated.assertNotBlank( +public fun MutableValidated.assertNotBlank( summary: String = "Cannot be blank", description: String = summary, setOnIssue: Boolean = true -) = assert(summary, description, setOnIssue) { it.isNotBlank() } +): MutableValidated = assert(summary, description, setOnIssue) { it.isNotBlank() } /** * Validates that the value of this [MutableValidatedValue] is not blank. @@ -129,11 +129,11 @@ fun MutableValidated.assertNotBlank( * @param description Detailed description of the issue (defaults to [summary]). * @param setOnIssue If true, issues are reported as [Issue.Warning]; if false, as [Issue.Invalid]. */ -fun MutableValidatedValue.assertNotBlank( +public fun MutableValidatedValue.assertNotBlank( summary: String = "Cannot be blank", description: String = summary, setOnIssue: Boolean = true -) = assert(summary, description, setOnIssue) { it.isNotBlank() } +): MutableValidatedValue = assert(summary, description, setOnIssue) { it.isNotBlank() } /** * Adds a validation check to this [MutableReactive] instance. @@ -144,10 +144,10 @@ fun MutableValidatedValue.assertNotBlank( * @param setOnIssue If true, issues are reported as [Issue.Warning]; if false, as [Issue.Invalid]. * @param validate Function that returns a string issue message or null for valid values. */ -fun MutableReactive.validate( +public fun MutableReactive.validate( setOnIssue: Boolean = true, validate: (T) -> String? -) = checkForIssue { value -> +): MutableValidated = checkForIssue { value -> validate(value)?.let { if (setOnIssue) Issue.Warning(it) else Issue.Invalid(it) @@ -163,10 +163,10 @@ fun MutableReactive.validate( * @param setOnIssue If true, issues are reported as [Issue.Warning]; if false, as [Issue.Invalid]. * @param validate Function that returns a string issue message or null for valid values. */ -fun MutableReactiveValue.validate( +public fun MutableReactiveValue.validate( setOnIssue: Boolean = true, validate: (T) -> String? -) = checkForIssue { value -> +): MutableValidatedValue = checkForIssue { value -> validate(value)?.let { if (setOnIssue) Issue.Warning(it) else Issue.Invalid(it) @@ -181,12 +181,12 @@ fun MutableReactiveValue.validate( * @param setOnIssue If true, issues are reported as [Issue.Warning]; if false, as [Issue.Invalid]. * @param condition Function that returns true if valid, false if invalid. */ -fun MutableReactive.assert( +public fun MutableReactive.assert( summary: String, description: String = summary, setOnIssue: Boolean = true, condition: (T) -> Boolean -) = checkForIssue { +): MutableValidated = checkForIssue { if (condition(it)) return@checkForIssue null if (setOnIssue) Issue.Warning(summary, description) @@ -201,12 +201,12 @@ fun MutableReactive.assert( * @param setOnIssue If true, issues are reported as [Issue.Warning]; if false, as [Issue.Invalid]. * @param condition Function that returns true if valid, false if invalid. */ -fun MutableReactiveValue.assert( +public fun MutableReactiveValue.assert( summary: String, description: String = summary, setOnIssue: Boolean = true, condition: (T) -> Boolean -) = checkForIssue { +): MutableValidatedValue = checkForIssue { if (condition(it)) return@checkForIssue null if (setOnIssue) Issue.Warning(summary, description) @@ -220,11 +220,11 @@ fun MutableReactiveValue.assert( * @param description Detailed description of the issue (defaults to [summary]). * @param setOnIssue If true, issues are reported as [Issue.Warning]; if false, as [Issue.Invalid]. */ -fun MutableReactive.assertNotNull( +public fun MutableReactive.assertNotNull( summary: String = "Cannot be blank", description: String = summary, setOnIssue: Boolean = true -) = assert(summary, description, setOnIssue) { it != null } +): MutableValidated = assert(summary, description, setOnIssue) { it != null } /** * Validates that the value of this [MutableReactiveValue] is not null. @@ -233,11 +233,11 @@ fun MutableReactive.assertNotNull( * @param description Detailed description of the issue (defaults to [summary]). * @param setOnIssue If true, issues are reported as [Issue.Warning]; if false, as [Issue.Invalid]. */ -fun MutableReactiveValue.assertNotNull( +public fun MutableReactiveValue.assertNotNull( summary: String = "Cannot be blank", description: String = summary, setOnIssue: Boolean = true -) = assert(summary, description, setOnIssue) { it != null } +): MutableValidatedValue = assert(summary, description, setOnIssue) { it != null } /** * Validates that the value of this [MutableReactive] is not blank. @@ -246,11 +246,11 @@ fun MutableReactiveValue.assertNotNull( * @param description Detailed description of the issue (defaults to [summary]). * @param setOnIssue If true, issues are reported as [Issue.Warning]; if false, as [Issue.Invalid]. */ -fun MutableReactive.assertNotBlank( +public fun MutableReactive.assertNotBlank( summary: String = "Cannot be blank", description: String = summary, setOnIssue: Boolean = true -) = assert(summary, description, setOnIssue) { it.isNotBlank() } +): MutableValidated = assert(summary, description, setOnIssue) { it.isNotBlank() } /** * Validates that the value of this [MutableReactiveValue] is not blank. @@ -259,16 +259,16 @@ fun MutableReactive.assertNotBlank( * @param description Detailed description of the issue (defaults to [summary]). * @param setOnIssue If true, issues are reported as [Issue.Warning]; if false, as [Issue.Invalid]. */ -fun MutableReactiveValue.assertNotBlank( +public fun MutableReactiveValue.assertNotBlank( summary: String = "Cannot be blank", description: String = summary, setOnIssue: Boolean = true -) = assert(summary, description, setOnIssue) { it.isNotBlank() } +): MutableValidatedValue = assert(summary, description, setOnIssue) { it.isNotBlank() } /** * Runs the provided validation condition reactively, reporting to a child of this [IssueTracking] node. * */ -fun IssueTracking.report(issue: ReactiveContext.() -> Issue?) { +public fun IssueTracking.report(issue: ReactiveContext.() -> Issue?) { val child = IssueNode(parent = node) child.connect() child.reactiveReport(issue) @@ -277,13 +277,13 @@ fun IssueTracking.report(issue: ReactiveContext.() -> Issue?) { /** * Runs the provided validation condition reactively, reporting to a child of this [IssueTracking] node. * */ -fun Validated.validateReactive(issue: ReactiveContext.(T) -> String?) = report { issue(this@validateReactive.invoke())?.let(Issue::Warning) } +public fun Validated.validateReactive(issue: ReactiveContext.(T) -> String?): Unit = report { issue(this@validateReactive.invoke())?.let(Issue::Warning) } /** * Asserts the provided condition reactively, constructing and reporting an [Issue.Warning] to a child of this [IssueTracking] node. * */ -fun Validated.assertReactive( +public fun Validated.assertReactive( summary: String, description: String = summary, condition: ReactiveContext.(T) -> Boolean -) = report { if (condition(this@assertReactive.invoke())) null else Issue.Warning(summary, description) } \ No newline at end of file +): Unit = report { if (condition(this@assertReactive.invoke())) null else Issue.Warning(summary, description) } \ No newline at end of file diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/validation.kt b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/validation.kt index 6f84f7e..b3a4ccd 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/validation.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/validation.kt @@ -129,9 +129,9 @@ private class ValidationValueLens( } } -fun Validated.checkForIssue(validate: (T) -> Issue?): Validated = ValidatedLens(this, validate) +public fun Validated.checkForIssue(validate: (T) -> Issue?): Validated = ValidatedLens(this, validate) -fun ValidatedValue.checkForIssue(validate: (T) -> Issue?): ValidatedValue = ValidatedValueLens(this, validate) +public fun ValidatedValue.checkForIssue(validate: (T) -> Issue?): ValidatedValue = ValidatedValueLens(this, validate) /** * Adds a validation check to a [MutableValidated] instance. @@ -142,7 +142,7 @@ fun ValidatedValue.checkForIssue(validate: (T) -> Issue?): ValidatedValue * @param validate Function that returns an [Issue] or null for valid values. * @return A [MutableValidated] that tracks issues according to [validate]. */ -fun MutableValidated.checkForIssue(validate: (T) -> Issue?): MutableValidated = MutableValidationLens(this, validate) +public fun MutableValidated.checkForIssue(validate: (T) -> Issue?): MutableValidated = MutableValidationLens(this, validate) /** * Adds a validation check to a [MutableReactive] instance, returning a [MutableValidated] that tracks issues. @@ -152,7 +152,7 @@ fun MutableValidated.checkForIssue(validate: (T) -> Issue?): MutableValid * @param validate Function that returns an [Issue] or null for valid values. * @return A [MutableValidated] that tracks issues according to [validate]. */ -fun MutableReactive.checkForIssue(validate: (T) -> Issue?): MutableValidated = MutableValidationLens(this.validated(), validate) +public fun MutableReactive.checkForIssue(validate: (T) -> Issue?): MutableValidated = MutableValidationLens(this.validated(), validate) /** @@ -164,7 +164,7 @@ fun MutableReactive.checkForIssue(validate: (T) -> Issue?): MutableValida * @param validate Function that returns an [Issue] or null for valid values. * @return A [MutableValidatedValue] that tracks issues according to [validate]. */ -fun MutableValidatedValue.checkForIssue(validate: (T) -> Issue?): MutableValidatedValue = ValidationValueLens(this, validate) +public fun MutableValidatedValue.checkForIssue(validate: (T) -> Issue?): MutableValidatedValue = ValidationValueLens(this, validate) /** * Adds a validation check to a [MutableReactiveValue] instance, returning a [MutableValidatedValue] that tracks issues. @@ -174,4 +174,4 @@ fun MutableValidatedValue.checkForIssue(validate: (T) -> Issue?): Mutable * @param validate Function that returns an [Issue] or null for valid values. * @return A [MutableValidatedValue] that tracks issues according to [validate]. */ -fun MutableReactiveValue.checkForIssue(validate: (T) -> Issue?): MutableValidatedValue = ValidationValueLens(this.validated(), validate) +public fun MutableReactiveValue.checkForIssue(validate: (T) -> Issue?): MutableValidatedValue = ValidationValueLens(this.validated(), validate) diff --git a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/validationLenses.kt b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/validationLenses.kt index 47d37e9..15df7e6 100644 --- a/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/validationLenses.kt +++ b/src/commonMain/kotlin/com/lightningkite/reactive/lensing/validation/validationLenses.kt @@ -8,7 +8,7 @@ import com.lightningkite.reactive.lensing.ModifyValueLens import com.lightningkite.reactive.lensing.SetLens import com.lightningkite.reactive.lensing.SetValueLens -class ValidatedSetLens( +public class ValidatedSetLens( source: MutableValidated, get: (T) -> L, set: (L) -> T @@ -24,7 +24,7 @@ class ValidatedSetLens( } } -class ValidatedModifyLens( +public class ValidatedModifyLens( source: MutableValidated, get: (T) -> L, modify: (T, L) -> T @@ -40,7 +40,7 @@ class ValidatedModifyLens( } } -class ValidatedSetValueLens( +public class ValidatedSetValueLens( source: MutableValidatedValue, get: (T) -> L, set: (L) -> T @@ -56,7 +56,7 @@ class ValidatedSetValueLens( } } -class ValidatedModifyValueLens( +public class ValidatedModifyValueLens( source: MutableValidatedValue, get: (T) -> L, modify: (T, L) -> T