From dce45604d65c8a53fff94eb14e25b3856d55c9b5 Mon Sep 17 00:00:00 2001 From: David Denton <daviddenton@users.noreply.github.com> Date: Mon, 18 Mar 2024 17:58:39 +0000 Subject: [PATCH] Rework state4k to have enter commands (#57) * upgrade versions data4k - rename content() to unwrap() data4k - add ability to set raw child data nodes * upgrade dependencies new version of state4k which defines commands that are triggered on entering a state * Release 2.15.0.0 --- CHANGELOG.md | 4 ++ gradle/wrapper/gradle-wrapper.properties | 2 +- state4k/README.md | 7 +-- .../forkhandles/state4k/EntityStateLens.kt | 22 --------- .../dev/forkhandles/state4k/StateBuilder.kt | 17 ++++--- .../dev/forkhandles/state4k/StateIdLens.kt | 22 +++++++++ .../dev/forkhandles/state4k/StateMachine.kt | 46 +++++++++++++------ .../state4k/StateMachineRenderer.kt | 2 +- .../forkhandles/state4k/StateTransition.kt | 8 ++-- .../state4k/StateTransitionResult.kt | 12 ++--- .../dev/forkhandles/state4k/render/Puml.kt | 29 ++++++------ .../forkhandles/state4k/StateMachineTest.kt | 13 ++++-- .../kotlin/dev/forkhandles/state4k/example.kt | 28 ++++++----- .../render/PumlRendererTest.asPuml.approved | 17 ++++--- .../dev/forkhandles/state4k/simpleExample.kt | 9 ++-- version.json | 2 +- versions.properties | 10 +--- 17 files changed, 136 insertions(+), 114 deletions(-) delete mode 100644 state4k/src/main/kotlin/dev/forkhandles/state4k/EntityStateLens.kt create mode 100644 state4k/src/main/kotlin/dev/forkhandles/state4k/StateIdLens.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 85e5938..5a7f4bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ This list is not intended to be all-encompassing - it will document major and breaking API changes with their rationale when appropriate: +### v2.15.0.0 +- **all** : Upgrade of dependencies and gradle. +- **state4k** : [Breaking change] Migrated to new construction mechanic. We now define sent commands with `onEnter(commands)` and these are defined on the state itself. This allows for a more consistent way of defining state transitions. + ### v2.14.1.0 - **result4k** : Add `resultFromCatching` to catch some (but not all!) exceptions and turn them into a `Result4k` H/T @MarcusDunn diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e09..a80b22c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/state4k/README.md b/state4k/README.md index b75e2f7..89f3d4a 100644 --- a/state4k/README.md +++ b/state4k/README.md @@ -74,12 +74,13 @@ val simpleStateMachine = StateMachine<SimpleState, SimpleEntity, SimpleEvent, Si lens, // define the state transitions for state one StateBuilder<SimpleState, SimpleEntity, SimpleCommand>(one) - .transition<SimpleEvent1>(two, { e, o -> o.copy(lastAction = "received $e") }, aCommand), + .transition<SimpleEvent1>(two) { e, o -> o.copy(lastAction = "received $e") }, // define the state transitions for state two StateBuilder<SimpleState, SimpleEntity, SimpleCommand>(two) - .transition<SimpleEvent2>(three, { e, o -> o.copy(lastAction = "received $e") }) - .transition<SimpleEvent3>(four, { e, o -> o.copy(lastAction = "received $e") }) + .onEnter(aCommand) + .transition<SimpleEvent2>(three) { e, o -> o.copy(lastAction = "received $e") } + .transition<SimpleEvent3>(four) { e, o -> o.copy(lastAction = "received $e") } ) ``` diff --git a/state4k/src/main/kotlin/dev/forkhandles/state4k/EntityStateLens.kt b/state4k/src/main/kotlin/dev/forkhandles/state4k/EntityStateLens.kt deleted file mode 100644 index df9811a..0000000 --- a/state4k/src/main/kotlin/dev/forkhandles/state4k/EntityStateLens.kt +++ /dev/null @@ -1,22 +0,0 @@ -package dev.forkhandles.state4k - -/** - * Responsible for retrieving and setting the state on an entity - */ -interface EntityStateLens<Entity, State> { - operator fun invoke(entity: Entity): State - operator fun invoke(entity: Entity, newState: State): Entity - - companion object { - /** - * Convenience function for creating an EntityStateLens - */ - operator fun <Entity, State> invoke( - get: (Entity) -> State, - set: (Entity, State) -> Entity, - ) = object : EntityStateLens<Entity, State> { - override fun invoke(entity: Entity) = get(entity) - override fun invoke(entity: Entity, newState: State) = set(entity, newState) - } - } -} diff --git a/state4k/src/main/kotlin/dev/forkhandles/state4k/StateBuilder.kt b/state4k/src/main/kotlin/dev/forkhandles/state4k/StateBuilder.kt index 0dcaa69..e31cc14 100644 --- a/state4k/src/main/kotlin/dev/forkhandles/state4k/StateBuilder.kt +++ b/state4k/src/main/kotlin/dev/forkhandles/state4k/StateBuilder.kt @@ -3,19 +3,22 @@ package dev.forkhandles.state4k /** * Builder for the mechanics of how to transition out of a particular state */ -class StateBuilder<State, Entity, Command>( - val start: State, - val transitions: List<StateTransition<State, Entity, *, Command>> = emptyList() +class StateBuilder<StateId, Entity, Command>( + val start: StateId, + val onEnter: Command? = null, + val transitions: List<StateTransition<StateId, Entity, *>> = emptyList() ) { + fun onEnter(nextCommand: Command) = StateBuilder(start, nextCommand, transitions) + /** * Define a state and the transitions out of that state via events, with an optional command to send next */ inline fun <reified Event : Any> transition( - end: State, - noinline applyTo: (Event, Entity) -> Entity = { _, entity -> entity }, - nextCommand: Command? = null + end: StateId, + noinline applyTo: (Event, Entity) -> Entity = { _, entity -> entity } ) = StateBuilder( start, - transitions + StateTransition(start, Event::class, end, applyTo, nextCommand) + onEnter, + transitions + StateTransition(Event::class, end, applyTo) ) } diff --git a/state4k/src/main/kotlin/dev/forkhandles/state4k/StateIdLens.kt b/state4k/src/main/kotlin/dev/forkhandles/state4k/StateIdLens.kt new file mode 100644 index 0000000..aa6cecc --- /dev/null +++ b/state4k/src/main/kotlin/dev/forkhandles/state4k/StateIdLens.kt @@ -0,0 +1,22 @@ +package dev.forkhandles.state4k + +/** + * Responsible for retrieving and setting the state on an entity + */ +interface StateIdLens<Entity, StateId> { + operator fun invoke(entity: Entity): StateId + operator fun invoke(entity: Entity, newState: StateId): Entity + + companion object { + /** + * Convenience function for creating an EntityStateLens + */ + operator fun <Entity, StateId> invoke( + get: (Entity) -> StateId, + set: (Entity, StateId) -> Entity, + ) = object : StateIdLens<Entity, StateId> { + override fun invoke(entity: Entity) = get(entity) + override fun invoke(entity: Entity, newState: StateId) = set(entity, newState) + } + } +} diff --git a/state4k/src/main/kotlin/dev/forkhandles/state4k/StateMachine.kt b/state4k/src/main/kotlin/dev/forkhandles/state4k/StateMachine.kt index e028fdd..a5f85da 100644 --- a/state4k/src/main/kotlin/dev/forkhandles/state4k/StateMachine.kt +++ b/state4k/src/main/kotlin/dev/forkhandles/state4k/StateMachine.kt @@ -12,24 +12,27 @@ import dev.forkhandles.state4k.StateTransitionResult.OK * Standard state machine pattern. Build with a list of state definitions and transition over them with * events. Each transition can create a new command to be issued. */ -class StateMachine<State, Entity, Event : Any, Command, Error>( +class StateMachine<StateId, Entity, Event : Any, Command, Error>( private val commands: Commands<Entity, Command, Error>, - private val stateLens: EntityStateLens<Entity, State>, - private val stateTransitions: List<StateTransition<State, Entity, *, Command>> = emptyList() + private val stateLens: StateIdLens<Entity, StateId>, + private val states: List<State<StateId, Entity, Command>>, ) { constructor( commands: Commands<Entity, Command, Error>, - entityStateLens: EntityStateLens<Entity, State>, - vararg stateBuilders: StateBuilder<State, Entity, Command> - ) : this(commands, entityStateLens, stateBuilders.flatMap { state -> state.transitions }) + stateIdLens: StateIdLens<Entity, StateId>, + vararg stateBuilders: StateBuilder<StateId, Entity, Command> + ) : this(commands, + stateIdLens, + stateBuilders.map { State(it.start, it.onEnter, it.transitions) } + ) /** * Transition the entity by checking then running a command, and applying the resultant event to the entity */ fun <Next : Event> transition(entity: Entity, command: Command, toEvent: (Entity) -> Result<Next, Error>) - : Result<StateTransitionResult<State, Entity, Command>, Error> = + : Result<StateTransitionResult<StateId, Entity, Command>, Error> = when { - stateTransitions.any { it.end == stateLens(entity) && it.nextCommand == command } -> + states.any { it.id == stateLens(entity) && it.onEnter == command } -> toEvent(entity).flatMap { transition(entity, it) } else -> Success(IllegalCommand(entity, command)) @@ -40,16 +43,31 @@ class StateMachine<State, Entity, Event : Any, Command, Error>( */ @Suppress("UNCHECKED_CAST") fun <Next : Event> transition(entity: Entity, event: Next) - : Result<StateTransitionResult<State, Entity, Command>, Error> = - stateTransitions.firstOrNull { stateLens(entity) == it.start && event::class == it.event } - ?.let { transition -> - transition as? StateTransition<State, Entity, Next, Command> ?: error("Illegal state transition") - (transition.nextCommand?.let { commands(entity, it) } ?: Success(Unit)) + : Result<StateTransitionResult<StateId, Entity, Command>, Error> { + val transition = states + .firstOrNull { it.id == stateLens(entity) } + ?.let { state -> + state.transitions.firstOrNull { event::class == it.event } + ?.let { it as? StateTransition<StateId, Entity, Next> ?: error("Illegal state transition") } + } + + return transition + ?.let { + (states.firstOrNull { state -> state.id == it.end } + ?.onEnter?.let { commands(entity, it) } ?: Success(Unit)) + .also { println(it) } .map { OK(stateLens(transition.applyTo(event, entity), transition.end)) } } ?: Success(IllegalEvent(entity, event)) + } /** * Render the state machine using the passed renderer */ - fun renderUsing(renderer: StateMachineRenderer) = renderer(stateTransitions) + fun renderUsing(renderer: StateMachineRenderer) = renderer(states) } + +data class State<StateId, Entity, Command>( + val id: StateId, + val onEnter: Command?, + val transitions: List<StateTransition<StateId, Entity, *>> +) diff --git a/state4k/src/main/kotlin/dev/forkhandles/state4k/StateMachineRenderer.kt b/state4k/src/main/kotlin/dev/forkhandles/state4k/StateMachineRenderer.kt index c81eeb9..4416928 100644 --- a/state4k/src/main/kotlin/dev/forkhandles/state4k/StateMachineRenderer.kt +++ b/state4k/src/main/kotlin/dev/forkhandles/state4k/StateMachineRenderer.kt @@ -1,5 +1,5 @@ package dev.forkhandles.state4k fun interface StateMachineRenderer { - operator fun invoke(transitions: List<StateTransition<*, *, *, *>>): String + operator fun invoke(states: List<State<*, *, *>>): String } diff --git a/state4k/src/main/kotlin/dev/forkhandles/state4k/StateTransition.kt b/state4k/src/main/kotlin/dev/forkhandles/state4k/StateTransition.kt index 02e1418..5e9300d 100644 --- a/state4k/src/main/kotlin/dev/forkhandles/state4k/StateTransition.kt +++ b/state4k/src/main/kotlin/dev/forkhandles/state4k/StateTransition.kt @@ -2,10 +2,8 @@ package dev.forkhandles.state4k import kotlin.reflect.KClass -data class StateTransition<State, Entity, Event : Any, Command>( - val start: State, +data class StateTransition<StateId, Entity, Event : Any>( val event: KClass<Event>, - val end: State, - val applyTo: (Event, Entity) -> Entity, - val nextCommand: Command? + val end: StateId, + val applyTo: (Event, Entity) -> Entity ) diff --git a/state4k/src/main/kotlin/dev/forkhandles/state4k/StateTransitionResult.kt b/state4k/src/main/kotlin/dev/forkhandles/state4k/StateTransitionResult.kt index 02a0cce..4c6e8a9 100644 --- a/state4k/src/main/kotlin/dev/forkhandles/state4k/StateTransitionResult.kt +++ b/state4k/src/main/kotlin/dev/forkhandles/state4k/StateTransitionResult.kt @@ -3,30 +3,30 @@ package dev.forkhandles.state4k /** * The various outcomes which can happen out of a state machine transition */ -sealed interface StateTransitionResult<State, Entity, CommandType> { +sealed interface StateTransitionResult<StateId, Entity, CommandType> { val entity: Entity /** * Apply a transformation to update the entity inside the result. Useful for * manipulating the result. */ - fun map(fn: (Entity) -> Entity): StateTransitionResult<State, Entity, CommandType> + fun map(fn: (Entity) -> Entity): StateTransitionResult<StateId, Entity, CommandType> /** * This transition was valid and the updated entity is enclosed in the field */ - data class OK<State, Entity, CommandType>(override val entity: Entity) : - StateTransitionResult<State, Entity, CommandType> { + data class OK<EntityState, Entity, CommandType>(override val entity: Entity) : + StateTransitionResult<EntityState, Entity, CommandType> { override fun map(fn: (Entity) -> Entity) = copy(entity = fn(entity)) } /** * This transition via an event was not valid. The unmodified entity is enclosed in the field. */ - data class IllegalEvent<State, Entity, CommandType>( + data class IllegalEvent<EntityState, Entity, CommandType>( override val entity: Entity, val event: Any - ) : StateTransitionResult<State, Entity, CommandType> { + ) : StateTransitionResult<EntityState, Entity, CommandType> { override fun map(fn: (Entity) -> Entity) = copy(entity = fn(entity)) } diff --git a/state4k/src/main/kotlin/dev/forkhandles/state4k/render/Puml.kt b/state4k/src/main/kotlin/dev/forkhandles/state4k/render/Puml.kt index 3ada776..a204066 100644 --- a/state4k/src/main/kotlin/dev/forkhandles/state4k/render/Puml.kt +++ b/state4k/src/main/kotlin/dev/forkhandles/state4k/render/Puml.kt @@ -5,21 +5,19 @@ import dev.forkhandles.state4k.StateMachineRenderer /** * Standard PUML diagram generator for a StateMachine */ -fun Puml(title: String, commandColour: String = "PaleGoldenRod") = StateMachineRenderer { transitions -> - val commands = transitions - .groupBy { it.end.toString() } - .mapValues { it.value.mapNotNull { it.nextCommand?.toString() } } - .map { - val stateName = it.key - """ - |state $stateName { +fun Puml(title: String, commandColour: String = "PaleGoldenRod") = StateMachineRenderer { states -> + + val stateDefinitions = states.joinToString("\n") { + val stateId = it.id + """ + |state $stateId { |${ - it.value.sortedBy { it } - .joinToString("\n") { command -> " state \"$command\" as ${stateName}_$command <<Command>>" } - } + it.onEnter?.let { " state \"$it\" as ${stateId}_$it <<Command>>" } ?: "" + } |} """.trimMargin() - }.joinToString("\n") + } + """ |@startuml @@ -28,12 +26,13 @@ fun Puml(title: String, commandColour: String = "PaleGoldenRod") = StateMachineR | BorderColor<<Command>> $commandColour |} | - |$commands + |$stateDefinitions | |title $title |${ - transitions - .joinToString("\n") { " ${it.start} --> ${it.end} : ${it.event.simpleName}" } + states.joinToString("\n") { state -> + state.transitions.joinToString("\n") { " ${state.id} --> ${it.end} : ${it.event.simpleName}" } + } } |@enduml""".trimMargin() } diff --git a/state4k/src/test/kotlin/dev/forkhandles/state4k/StateMachineTest.kt b/state4k/src/test/kotlin/dev/forkhandles/state4k/StateMachineTest.kt index 5554769..f34df89 100644 --- a/state4k/src/test/kotlin/dev/forkhandles/state4k/StateMachineTest.kt +++ b/state4k/src/test/kotlin/dev/forkhandles/state4k/StateMachineTest.kt @@ -10,7 +10,7 @@ import dev.forkhandles.result4k.Result4k import dev.forkhandles.result4k.Success import dev.forkhandles.result4k.failureOrNull import dev.forkhandles.result4k.valueOrNull -import dev.forkhandles.state4k.EntityStateLens +import dev.forkhandles.state4k.StateIdLens import dev.forkhandles.state4k.StateMachine import dev.forkhandles.state4k.StateTransitionResult import dev.forkhandles.state4k.StateTransitionResult.IllegalCommand @@ -81,15 +81,18 @@ class StateMachineTest { (valueOrNull()!! as OK).entity @Test - fun `failure during sending of next event`() { + fun `failure during sending of next command`() { val stateMachine = StateMachine<MyState, MyEntity, MyEvent, MyCommandType, String>( { _, _ -> Failure("foo") }, - EntityStateLens(MyEntity::state, MyEntity::withState), + StateIdLens(MyEntity::state, MyEntity::withState), buildState(one) - .transition<OneToTwoEvent>(two, { e, o -> o.copy(data = e.data) }, firedOnTwo) + .transition<OneToTwoEvent>(two) { e, o -> o.copy(data = e.data) }, + buildState(two) + .onEnter(firedOnTwo) ) - expectThat(stateMachine.transition(MyEntity(one, 0.0), OneToTwoEvent).failureOrNull()) + val transition = stateMachine.transition(MyEntity(one, 0.0), OneToTwoEvent) + expectThat(transition.failureOrNull()) .isEqualTo(("foo")) } diff --git a/state4k/src/test/kotlin/dev/forkhandles/state4k/example.kt b/state4k/src/test/kotlin/dev/forkhandles/state4k/example.kt index 64ed5c1..1d38eaf 100644 --- a/state4k/src/test/kotlin/dev/forkhandles/state4k/example.kt +++ b/state4k/src/test/kotlin/dev/forkhandles/state4k/example.kt @@ -22,25 +22,23 @@ import dev.forkhandles.result4k.Success val exampleStateMachine = StateMachine<MyState, MyEntity, MyEvent, MyCommandType, String>( { _, _ -> Success(Unit) }, - EntityStateLens(MyEntity::state) { entity, state -> entity.copy(state = state) }, + StateIdLens(MyEntity::state) { entity, state -> entity.copy(state = state) }, buildState(one) - .transition<OneToTwoEvent>(two, { _, o -> o.copy(data = OneToTwoEvent.data) }, firedOnTwo) - .transition<OneToFourEvent>(four, { _, o -> o.copy(data = OneToFourEvent.data) }) - .transition<OneToSixEvent>(six, { _, o -> o.copy(data = OneToSixEvent.data) }, eject), + .transition<OneToTwoEvent>(two) { _, o -> o.copy(data = OneToTwoEvent.data) } + .transition<OneToFourEvent>(four) { _, o -> o.copy(data = OneToFourEvent.data) } + .transition<OneToSixEvent>(six) { _, o -> o.copy(data = OneToSixEvent.data) }, buildState(two) - .transition<TwoToThreeEvent>( - three, - { _, o -> o.copy(data = TwoToThreeEvent.data) }, - firedOnThree - ), + .onEnter(firedOnTwo) + .transition<TwoToThreeEvent>(three) { _, o -> o.copy(data = TwoToThreeEvent.data) }, buildState(three) - .transition<ThreeToFourEvent>( - four, - { _, o -> o.copy(data = ThreeToFourEvent.data) }, - eject - ), + .onEnter(firedOnThree) + .transition<ThreeToFourEvent>(four) { _, o -> o.copy(data = ThreeToFourEvent.data) }, buildState(four) - .transition<ThreeToFourEvent>(five, { _, o -> o.copy(data = ThreeToFourEvent.data) }) + .onEnter(eject) + .transition<ThreeToFourEvent>(five) { _, o -> o.copy(data = ThreeToFourEvent.data) }, + buildState(five), + buildState(six) + .onEnter(eject) ) fun buildState(start: MyState) = StateBuilder<MyState, MyEntity, MyCommandType>(start) diff --git a/state4k/src/test/kotlin/dev/forkhandles/state4k/render/PumlRendererTest.asPuml.approved b/state4k/src/test/kotlin/dev/forkhandles/state4k/render/PumlRendererTest.asPuml.approved index e09c565..9b12495 100644 --- a/state4k/src/test/kotlin/dev/forkhandles/state4k/render/PumlRendererTest.asPuml.approved +++ b/state4k/src/test/kotlin/dev/forkhandles/state4k/render/PumlRendererTest.asPuml.approved @@ -4,21 +4,24 @@ skinparam state { BorderColor<<Command>> PaleGoldenRod } +state one { + +} state two { state "firedOnTwo" as two_firedOnTwo <<Command>> } -state four { - state "eject" as four_eject <<Command>> -} -state six { - state "eject" as six_eject <<Command>> -} state three { state "firedOnThree" as three_firedOnThree <<Command>> } +state four { + state "eject" as four_eject <<Command>> +} state five { } +state six { + state "eject" as six_eject <<Command>> +} title helloworld one --> two : OneToTwoEvent @@ -27,4 +30,6 @@ title helloworld two --> three : TwoToThreeEvent three --> four : ThreeToFourEvent four --> five : ThreeToFourEvent + + @enduml \ No newline at end of file diff --git a/state4k/src/test/kotlin/dev/forkhandles/state4k/simpleExample.kt b/state4k/src/test/kotlin/dev/forkhandles/state4k/simpleExample.kt index 27d6fa1..d7c2323 100644 --- a/state4k/src/test/kotlin/dev/forkhandles/state4k/simpleExample.kt +++ b/state4k/src/test/kotlin/dev/forkhandles/state4k/simpleExample.kt @@ -10,7 +10,7 @@ import dev.forkhandles.state4k.render.Puml import kotlin.random.Random // the lens gets and sets the state on the Entity -val lens = EntityStateLens(SimpleEntity::state) { entity, state -> entity.copy(state = state) } +val lens = StateIdLens(SimpleEntity::state) { entity, state -> entity.copy(state = state) } // the commands is responsible for issuing new commands to process the machine val commands = Commands<SimpleEntity, SimpleCommand, String> { _: SimpleEntity, _ -> Success(Unit) } @@ -21,12 +21,13 @@ val simpleStateMachine = StateMachine<SimpleState, SimpleEntity, SimpleEvent, Si lens, // define the state transitions for state one StateBuilder<SimpleState, SimpleEntity, SimpleCommand>(one) - .transition<SimpleEvent1>(two, { e, o -> o.copy(lastAction = "received $e") }, aCommand), + .transition<SimpleEvent1>(two) { e, o -> o.copy(lastAction = "received $e") }, // define the state transitions for state two StateBuilder<SimpleState, SimpleEntity, SimpleCommand>(two) - .transition<SimpleEvent2>(three, { e, o -> o.copy(lastAction = "received $e") }) - .transition<SimpleEvent3>(four, { e, o -> o.copy(lastAction = "received $e") }) + .onEnter(aCommand) + .transition<SimpleEvent2>(three) { e, o -> o.copy(lastAction = "received $e") } + .transition<SimpleEvent3>(four) { e, o -> o.copy(lastAction = "received $e") } ) data class SimpleEntity(val state: SimpleState, val lastAction: String) diff --git a/version.json b/version.json index 70f8596..74740d6 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "forkhandles": { - "version": "2.14.0.0" + "version": "2.15.0.0" } } diff --git a/versions.properties b/versions.properties index ba93f92..7a05e91 100644 --- a/versions.properties +++ b/versions.properties @@ -13,24 +13,16 @@ plugin.io.codearte.nexus-staging=0.30.0 plugin.me.champeau.jmh=0.7.2 -version.com.fasterxml.jackson.core..jackson-databind=2.16.1 -## # available=2.17.0-rc1 +version.com.fasterxml.jackson.core..jackson-databind=2.17.0 version.com.natpryce..hamkrest=1.8.0.1 version.junit.jupiter=5.10.2 version.kotest=5.6.2 -## # available=5.7.0 -## # available=5.7.1 -## # available=5.7.2 ## # available=5.8.0 version.kotlin=1.9.23 -## # available=2.0.0-Beta1 -## # available=2.0.0-Beta2 -## # available=2.0.0-Beta3 -## # available=2.0.0-Beta4 version.kotlinx.coroutines=1.8.0