Stub Generator¶
The stub generator reads .yaml files produced by the YAML generator and
emits four Kotlin source files per machine:
| File | Contents |
|---|---|
{Name}State.kt |
Sealed interface hierarchy for all states. |
{Name}Action.kt |
Sealed interface listing all action types. |
{Name}Effect.kt |
Sealed interface listing all effect types. |
{Name}StateMachine.kt |
stateMachine { } DSL wired to the generated types. |
This is a scaffolding step — the generated files are starting points that you own and edit freely.
Re-running with --replace overwrites them; the default is to skip files that already exist.
Setup¶
Apply the Monaka Gradle plugin and configure the monakaStubGenerator extension:
// build.gradle.kts
plugins {
id("dev.gmvalentino.monaka")
}
monakaStubGenerator {
// Path to a single .yaml file or a directory to scan recursively.
// Default: project root directory (scans for any .yaml file).
input.set("${layout.buildDirectory.get()}/monaka-yaml")
// Where to write the generated .kt files.
// Default: same directory as each source YAML file.
outputDir.set(layout.projectDirectory.dir("src/commonMain/kotlin/com/example/auth"))
// Generation style: CLASS (default) or FACTORY.
style.set(com.example.gradle.StubStyle.CLASS)
// Overwrite existing files. Default: false.
replace.set(false)
// Emit @SelfTransition / @Transition annotations on the state sealed interface.
// Default: true. Set to false if you are not using monaka-transitions / KSP.
useTransitionAnnotation.set(true)
}
Running¶
# Use extension defaults
./gradlew generateMonakaStubs
# Override options at the command line
./gradlew generateMonakaStubs \
--input=build/monaka-yaml/Login.yaml \
--output=src/commonMain/kotlin/com/example/auth \
--style=class \
--replace=true \
--use-transition-annotation=true
CLI options¶
| Option | Default | Description |
|---|---|---|
--input |
extension input |
Path to a .yaml file or directory. |
--output |
extension outputDir, or YAML file's directory |
Directory to write generated .kt files. |
--style |
class |
Generation style: class or factory (see below). |
--replace |
false |
When true, overwrite files that already exist. |
--use-transition-annotation |
true |
Emit @SelfTransition / @Transition on the state sealed interface. |
CLI options take precedence over the extension when both are set.
Generation styles¶
CLASS (default)¶
Generates a concrete class that delegates to stateMachine { }. Use this style when you need to
inject dependencies (repositories, use-cases) as constructor parameters:
class LoginStateMachine(
private val loginRepository: LoginRepository,
) : StateMachine<LoginState, LoginAction, LoginEffect> by stateMachine(
builder = {
initialState(LoginState.Idle)
state<LoginState> {
on<LoginAction.Logout> {
transition(LoginState.Idle)
sideEffect(LoginEffect.NavigateToLogin)
}
}
state<LoginState.Idle> {
on<LoginAction.Submit> {
task("login", autoCancel = true) {
dispatch(LoginAction.LoginSucceeded)
}
transition(LoginState.Submitting)
}
}
// … remaining states
}
)
FACTORY¶
Generates a top-level val holding a StateMachine instance. Use this style for machines with
no external dependencies, or when you prefer a functional, closure-captured approach:
val loginStateMachine = stateMachine<LoginState, LoginAction, LoginEffect> {
initialState(LoginState.Idle)
state<LoginState.Idle> {
on<LoginAction.Submit> {
transition(LoginState.Submitting)
}
}
// … remaining states
}
Generated state types¶
Simple machine (single state)¶
A machine whose YAML has no substate hierarchy generates a single data object:
Hierarchical machine¶
A machine with multiple states generates a full sealed interface hierarchy. If
useTransitionAnnotation is true, @SelfTransition is placed on the root sealed interface and
@Transition(…) is placed on each substate that transitions to other states — these annotations
drive the monaka-transitions KSP processor to generate toXxx() extension functions:
@SelfTransition
sealed interface LoginState : State {
data object Idle : LoginState
@Transition(LoginState.Submitting::class)
data object Submitting : LoginState
data object Authenticated : LoginState
}
To disable the annotations (e.g. if you are not using KSP), set useTransitionAnnotation = false
or pass --use-transition-annotation=false. The state hierarchy is still generated; only the
annotation lines are omitted.
Package inference¶
When no explicit --output directory is specified, the generator writes files next to the source
YAML. It also attempts to infer the Kotlin package declaration by scanning parent directories for
a known source-set root (src/commonMain/kotlin/, src/main/kotlin/, etc.). If inference
succeeds, package com.example.auth is prepended to each generated file automatically.
If inference fails (e.g. the YAML is in build/monaka-yaml/ with no source-set structure above
it), the generated files have no package declaration. Move them into a source directory
afterwards, or set an explicit --output path under your source tree so inference can resolve the
package.
Typical workflow¶
1. Write state machines using the Monaka DSL.
2. Run ./gradlew generateMonakaYaml → produces build/monaka-yaml/Login.yaml
3. Run ./gradlew generateMonakaStubs → produces LoginState.kt, LoginAction.kt, …
4. Move / copy generated files into your source tree (if outputDir is not already there).
5. Edit the generated StateMachine file to fill in real business logic.
6. Re-generate only the YAML and PlantUML as the machine evolves — do not re-generate stubs
unless you want to reset to a clean scaffold (use --replace to overwrite).