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