diff --git a/CLAUDE.md b/CLAUDE.md index d66d70c..9f766b1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,11 +28,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - Use the @KomaStoreDsl annotation for builder APIs - Follow the state{} and action{} block pattern -- Handle errors in dedicated error{} blocks +- Handle recoverable exceptions in dedicated recover{} blocks ### Error Handling -- Use store.error{} for business logic errors +- Use store.recover{} for business logic exceptions - Use store.exceptionHandler() for system errors - Prefer modeling errors as state transitions diff --git a/README.md b/README.md index acd297c..fb27ee5 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ The architecture is inspired by [Flux](https://facebookarchive.github.io/flux/) ## When Koma Fits Best Koma works especially well when a feature has multiple explicit UI or business states and the transition rules between them are important. -By combining Kotlin `sealed class`/`sealed interface` with Koma's state machine DSL, you can keep each state's `enter{}`, `action{}`, `exit{}`, and `error{}` behavior close together and make the transition rules easy to follow. +By combining Kotlin `sealed class`/`sealed interface` with Koma's state machine DSL, you can keep each state's `enter{}`, `action{}`, `exit{}`, and `recover{}` behavior close together and make the transition rules easy to follow. ## Current Scope @@ -382,7 +382,7 @@ val store: Store = Store { } ``` -This works, but you can also handle exceptions with the `error{}` block. +This works, but you can also handle exceptions with the `recover{}` block. ```kt val store: Store = Store { @@ -397,13 +397,13 @@ val store: Store = Store { } // more specific exceptions should be placed first - error { + recover { // ... nextState { CounterState.Error(error = error) } } // more general exception handlers should come last - error { + recover { // ... nextState { CounterState.Error(error = error) } } @@ -412,7 +412,7 @@ val store: Store = Store { ``` Exceptions can be caught not only in the `enter{}` block but also in the `action{}` and `exit{}` blocks. -In other words, your business logic exceptions can be handled in the `error{}` block. +In other words, your business logic exceptions can be handled in the `recover{}` block. On the other hand, fatal errors and other uncaught non-`Exception` throwables in the entire Store can be handled with the `exceptionHandler()` specification: @@ -541,7 +541,7 @@ Then, processing of all Coroutines will stop. #### Specifying CoroutineDispatchers -You can specify the execution thread (CoroutineDispatchers) in `enter{}`, `exit{}`, `action{}`, `error{}`, and `launch{}` blocks, allowing you to locally control which thread each specific operation runs on. +You can specify the execution thread (CoroutineDispatchers) in `enter{}`, `exit{}`, `action{}`, `recover{}`, and `launch{}` blocks, allowing you to locally control which thread each specific operation runs on. If you omit the dispatcher parameter, Koma keeps using the Store's current execution context for that operation. ```kt @@ -605,7 +605,7 @@ val store: Store = Store { } ``` -Regardless of the configured `PendingActionPolicy`, you can still discard already queued actions at a specific point by calling `clearPendingActions()` inside `enter{}`, `action{}`, `exit{}`, `error{}`, or inside `transaction{}` from a launched coroutine. +Regardless of the configured `PendingActionPolicy`, you can still discard already queued actions at a specific point by calling `clearPendingActions()` inside `enter{}`, `action{}`, `exit{}`, `recover{}`, or inside `transaction{}` from a launched coroutine. ### Using Control Flow in `Store{}` diff --git a/doc/internal/adr/2026-04-26-non-launch-cancellation.md b/doc/internal/adr/2026-04-26-non-launch-cancellation.md index 44c9c60..979c804 100644 --- a/doc/internal/adr/2026-04-26-non-launch-cancellation.md +++ b/doc/internal/adr/2026-04-26-non-launch-cancellation.md @@ -6,7 +6,7 @@ `#190` では、`action { launch { ... } }` で開始した仕事を lane 単位で明示的に止める `cancelLaunch(lane)` を検討している。 -一方で、`launch` を使わない通常の `action {}`、`enter {}`、`exit {}`、`error {}`、および `transaction {}` には、いま実行中の処理を途中で止める API はない。 +一方で、`launch` を使わない通常の `action {}`、`enter {}`、`exit {}`、`recover {}`、および `transaction {}` には、いま実行中の処理を途中で止める API はない。 ここで判断したいのは、`#190` のような cancellation を非 `launch` の store work にも広げるべきかどうかである。 @@ -14,7 +14,7 @@ 非 `launch` の store work には、in-flight cancellation API を追加しない。 -- 通常の `action {}`、`enter {}`、`exit {}`、`error {}`、`transaction {}` は、短く終わる直列・原子的な store work として扱う。 +- 通常の `action {}`、`enter {}`、`exit {}`、`recover {}`、`transaction {}` は、短く終わる直列・原子的な store work として扱う。 - cancellation が必要な非同期処理や長く生きる仕事は、`launch {}` に出して扱う。 - `clearPendingActions()` は引き続き「後ろに積まれた pending action を捨てる API」として扱い、現在実行中の store work は止めない。 diff --git a/doc/internal/adr/2026-04-27-clear-pending-actions-scope.md b/doc/internal/adr/2026-04-27-clear-pending-actions-scope.md index 1b51a3f..952ba13 100644 --- a/doc/internal/adr/2026-04-27-clear-pending-actions-scope.md +++ b/doc/internal/adr/2026-04-27-clear-pending-actions-scope.md @@ -6,7 +6,7 @@ `clearPendingActions()` は、現在実行中の store work 自体を止める API ではなく、その後ろに待機している dispatch を捨てる API である。 -一方で、公開面としては `enter {}`、`action {}`、`exit {}`、`error {}`、および launched coroutine 内の `transaction {}` から呼べる。 +一方で、公開面としては `enter {}`、`action {}`、`exit {}`、`recover {}`、および launched coroutine 内の `transaction {}` から呼べる。 このため、次の 2 点を整理しておきたい。 - `clearPendingActions()` をさらに狭い場所に限定すべきか @@ -19,12 +19,12 @@ `clearPendingActions()` は、引き続き store の直列 pipeline 上で実行される scope と、そこへ明示的に戻る `transaction {}` からのみ呼べる API として扱う。 -- `enter {}`、`action {}`、`exit {}`、`error {}` では公開を維持する +- `enter {}`、`action {}`、`exit {}`、`recover {}` では公開を維持する - launched coroutine 内では `transaction {}` からのみ呼べるようにし、`launch {}` 本体には公開しない - middleware やその他の非 store-work 文脈には広げない また、利用上の重心は `action {}` と launched coroutine 内の `transaction {}` に置く。 -`enter {}`、`exit {}`、`error {}` での利用は escape hatch として許容するが、常用の中心には置かない。 +`enter {}`、`exit {}`、`recover {}` での利用は escape hatch として許容するが、常用の中心には置かない。 ## 補足 @@ -32,7 +32,7 @@ - `launch {}` 本体は state に所有される非同期処理であり、store の直列 pipeline そのものではない。ここで queue を直接掃除できるようにすると、遅延や I/O の後の任意の時点で pending action を破棄できてしまい、挙動を追いにくくなる。 - `launch {}` 本体で必要なのは queue 制御よりも、state-owned job の寿命制御である。そちらは `cancelLaunch(lane)` のような action-launch cancellation で扱う方が役割分離として自然である。 - launched coroutine から `transaction {}` に入った時点では、処理は再び store の直列 pipeline に戻る。そのため、その瞬間に「この結果を採用するなら、古い pending action は不要」と判断して `clearPendingActions()` を呼ぶのは意味が通る。 -- `enter {}`、`exit {}`、`error {}` も技術的には store work であり、そこで pending action を捨てる意味はある。したがって公開面から完全に外す必要まではない。 +- `enter {}`、`exit {}`、`recover {}` も技術的には store work であり、そこで pending action を捨てる意味はある。したがって公開面から完全に外す必要まではない。 - ただし、可読性の観点では `action {}` と `transaction {}` の方が「何を確定させた結果として queue を切るのか」を読み取りやすい。README や KDoc では、この利用の重心を明示した方がよい。 ## 関連 diff --git a/doc/internal/adr/2026-05-01-error-dsl-exception-boundary.md b/doc/internal/adr/2026-05-01-error-dsl-exception-boundary.md index a0f613e..64c271b 100644 --- a/doc/internal/adr/2026-05-01-error-dsl-exception-boundary.md +++ b/doc/internal/adr/2026-05-01-error-dsl-exception-boundary.md @@ -1,16 +1,16 @@ -# `error {}` DSL は `Exception` の回復経路に限定する +# `recover {}` DSL は `Exception` の回復経路に限定する - 更新日: 2026-05-01 ## 背景 -Koma の `error {}` DSL は、state machine の中で発生した失敗を state 遷移として扱うための入口である。 +Koma の `recover {}` DSL(deprecated な `error {}` alias を含む)は、state machine の中で発生した失敗を state 遷移として扱うための入口である。 一方で Kotlin の `Throwable` には、通常の業務例外として回復を試みるべき `Exception` だけでなく、`AssertionError` などの `Error` 系や、独自 `Throwable` のような非標準の失敗も含まれる。 -これまでの実装では、fatal として即再送出していたものを除き、広く `Throwable` を `error {}` 側へ流しうる形になっていた。 +これまでの実装では、fatal として即再送出していたものを除き、広く `Throwable` を `recover {}` 側へ流しうる形になっていた。 しかしこの形だと、次の境界が曖昧になる。 -- `error {}` が回復対象として扱う失敗 +- `recover {}` が回復対象として扱う失敗 - `exceptionHandler()` が最後の受け皿として扱う失敗 - coroutine / job として成功完了にしてよい失敗 - job failure として扱うべき失敗 @@ -19,30 +19,30 @@ Koma の `error {}` DSL は、state machine の中で発生した失敗を state ## 決定 -`error {}` DSL は、`Exception` を回復するための経路に限定する。 +`recover {}` DSL は、`Exception` を回復するための経路に限定する。 -- `error` の `T` は `Exception` のみを受け付ける +- `recover` の `T` は `Exception` のみを受け付ける - `ErrorScope.error` の型も `Exception` に限定する -- Store 内の recoverable path は `Exception` のみを `error {}` に流す +- Store 内の recoverable path は `Exception` のみを `recover {}` に流す - `Exception` ではない `Throwable` は recoverable とみなさず、job failure として扱う -- `Exception` ではない `Throwable` は `exceptionHandler()` に流れるが、`error {}` には入れない -- middleware / observer / persistence など framework boundary で発生した `Exception` も、`error {}` の回復対象には入れない +- `Exception` ではない `Throwable` は `exceptionHandler()` に流れるが、`recover {}` には入れない +- middleware / observer / persistence など framework boundary で発生した `Exception` も、`recover {}` の回復対象には入れない この判断により、意味づけは次のように固定する。 -- `error {}` は recovery path +- `recover {}` は recovery path - `exceptionHandler()` は last-resort path -- `error {}` で処理できた失敗は、Store work としては成功完了でよい -- `error {}` に乗らない失敗は、未回復のまま success 扱いにしない +- `recover {}` で処理できた失敗は、Store work としては成功完了でよい +- `recover {}` に乗らない失敗は、未回復のまま success 扱いにしない ## 補足 - `CancellationException` は `Exception` ではあるが、coroutine cancellation の制御信号でもあるため、通常の recovery 対象には入れない。 - したがって runtime 上は、「`Exception` なら常に recoverable」ではなく、「recoverable exception path へ流してよい `Exception` だけを対象にする」という整理になる。 - recoverable / non-recoverable の境界は型だけでは決まらない。state handler や launched `transaction {}` の中で投げられた `Exception` は recovery path に流すが、middleware hook、observer callback、`stateSaver.save()` など framework boundary で投げられた `Exception` は recovery path に再投入しない。 -- そのため実装では、framework boundary で起きた `Exception` を `InternalError` で包み、`error {}` に再突入しないようにしている。`exceptionHandler()` に渡す直前には unwrap して、利用者には元の `Exception` を見せる。 -- `action {}` / `enter {}` / `exit {}` / launched `transaction {}` の中では、利用者は Kotlin の言語仕様上 `throw Throwable(...)` を書ける。この点は API 上の違和感になりうるため、README / KDoc では `error {}` が `Exception` 専用の recovery path であることを明示する。 -- この判断は source-compatible ではない。既存の `error { ... }` や custom `Throwable` を回復対象にしていたコードは移行が必要になる。 +- そのため実装では、framework boundary で起きた `Exception` を `InternalError` で包み、`recover {}` に再突入しないようにしている。`exceptionHandler()` に渡す直前には unwrap して、利用者には元の `Exception` を見せる。 +- `action {}` / `enter {}` / `exit {}` / launched `transaction {}` の中では、利用者は Kotlin の言語仕様上 `throw Throwable(...)` を書ける。この点は API 上の違和感になりうるため、README / KDoc では `recover {}` が `Exception` 専用の recovery path であることを明示する。 +- この判断は source-compatible ではない。旧 `error { ... }` や custom `Throwable` を回復対象にしていたコードは移行が必要になる。 - 本メモは、Store の通常 runtime path における error handling 境界を対象とする。起動時の state restore など、`_state` 初期化まわりの個別事情はここでは扱わない。 ## 関連 diff --git a/doc/internal/adr/2026-05-07-framework-boundary-exception-handling.md b/doc/internal/adr/2026-05-07-framework-boundary-exception-handling.md index 061c169..2f77a70 100644 --- a/doc/internal/adr/2026-05-07-framework-boundary-exception-handling.md +++ b/doc/internal/adr/2026-05-07-framework-boundary-exception-handling.md @@ -1,18 +1,18 @@ -# `error {}` に流さない framework boundary の例外処理は当面現状維持とする +# `recover {}` に流さない framework boundary の例外処理は当面現状維持とする - 更新日: 2026-05-07 ## 背景 -既存の判断として、`error {}` DSL は Store の通常 runtime path における `Exception` の回復経路に限定している。 -そのため、plugin hook、observer callback、`StateSaver.save()` / `restore()` など、framework boundary で起きた例外は `error {}` に再投入しない。 +既存の判断として、`recover {}` DSL は Store の通常 runtime path における `Exception` の回復経路に限定している。 +そのため、plugin hook、observer callback、`StateSaver.save()` / `restore()` など、framework boundary で起きた例外は `recover {}` に再投入しない。 この整理は、state machine の回復責務と framework 側の失敗を分離するうえでは自然である。 一方で、plugin や saver の失敗の中には、state transition 自体の整合性を必ずしも壊さず、レポートしつつ続行できそうなものもある。 ただし、この種の例外を一律に「続行可能」とみなすのは粗すぎる。 起動前後の `restore()` や `Plugin.onStart()` のように、失敗時に Store の初期化状態へ直接影響するものもあり、同じ扱いにはできない。 -また、`error {}` に流さない例外は現在 `exceptionHandler()` 側で受けるため、設定次第では利用者が failure を見落としやすい、という別の論点もある。 +また、`recover {}` に流さない例外は現在 `exceptionHandler()` 側で受けるため、設定次第では利用者が failure を見落としやすい、という別の論点もある。 このため、今の時点で runtime 挙動や handler 境界を拡張するかどうかを決めておく必要がある。 @@ -20,14 +20,14 @@ framework boundary の例外処理は、当面は現状コードを維持する。 -- plugin、observer、persistence など `error {}` に流さない例外を、今すぐ一律に「report して続行」へ寄せる変更は採用しない +- plugin、observer、persistence など `recover {}` に流さない例外を、今すぐ一律に「report して続行」へ寄せる変更は採用しない - `exceptionHandler()` と別に、system-side の例外専用 handler を直ちに追加する変更も採用しない - 現時点では、「Store DSL 内の recovery path」と「framework boundary の last-resort path」を既存どおり分けたままにする ただし、将来の拡張候補として次は残す。 - 個々の失敗点ごとに、Store 整合性への影響と observability の要求を見極めたうえで、例外発生後も続行できる箇所だけを限定的に増やす -- `error {}` に流さない system-side の例外について、現行の `exceptionHandler()` とは別の handler を導入し、利用者が business error と framework error を分けて扱えるようにする +- `recover {}` に流さない system-side の例外について、現行の `exceptionHandler()` とは別の handler を導入し、利用者が business error と framework error を分けて扱えるようにする これらは方向性としては保持するが、具体的な API や runtime policy は、実例となるユースケースや運用上の不足が揃ってから判断する。 @@ -39,5 +39,5 @@ framework boundary の例外処理は、当面は現状コードを維持する ## 関連 -- [`error {}` DSL は `Exception` の回復経路に限定する](./2026-05-01-error-dsl-exception-boundary.md) +- [`recover {}` DSL は `Exception` の回復経路に限定する](./2026-05-01-error-dsl-exception-boundary.md) - [`Plugin` 設計メモ](../notes/2026-05-02-plugin-design.md) diff --git a/doc/internal/adr/2026-05-07-launch-job-return.md b/doc/internal/adr/2026-05-07-launch-job-return.md index 37febe4..9caab1b 100644 --- a/doc/internal/adr/2026-05-07-launch-job-return.md +++ b/doc/internal/adr/2026-05-07-launch-job-return.md @@ -27,5 +27,5 @@ Koma の `enter { launch { ... } }`、`action { launch { ... } }`、`PluginScope - `ActionScope.launch` にはすでに `LaunchControl.CancelPrevious(...)`、`LaunchControl.DropIfRunning(...)`、`cancelLaunch(lane)` があり、tracked launch の coordination は lane 単位の高水準 API として表現している。ここに生の `Job` を追加すると、「lane で止めるべきか」「保持していた job を直接止めるべきか」が二重化する。 - 特に `LaunchControl.DropIfRunning(...)` は、新しい launch 要求が無視される場合がある。このとき `launch()` の戻り値を `Job` にすると、「今回の呼び出しで何が返るのか」を別途決める必要があり、API 意味論が余計に重くなる。 - `EnterScope.launch` の仕事は state exit で自動停止し、`PluginScope.launch` の仕事は store root scope の終了で自動停止する。これらは Koma 側が lifecycle を所有しているため、公開 surface でもその ownership を保った方が自然である。 -- launched work 内の recoverable な `Exception` は `error {}` の回復経路に流す設計であり、利用者に見せたい境界は job failure そのものより state machine の recovery path である。`Job` を前面に出すと、失敗モデルも coroutine primitive 寄りに読まれやすくなる。 +- launched work 内の recoverable な `Exception` は `recover {}` の回復経路に流す設計であり、利用者に見せたい境界は job failure そのものより state machine の recovery path である。`Job` を前面に出すと、失敗モデルも coroutine primitive 寄りに読まれやすくなる。 - ただし、「Store は生かしたまま特定の background work だけを owner が明示停止したい」といった要件が将来増える可能性までは否定しない。その場合は `Job` をそのまま返すより、「何を止めるための handle なのか」が分かる専用型の方が Koma の API surface と整合しやすい。 diff --git a/doc/internal/design/2026-04-23-design-principles.md b/doc/internal/design/2026-04-23-design-principles.md index 8a31dab..9ccc185 100644 --- a/doc/internal/design/2026-04-23-design-principles.md +++ b/doc/internal/design/2026-04-23-design-principles.md @@ -27,7 +27,7 @@ Koma の設計上の基本方針は次のとおり。 - Store が lazy start で、start 前でも `currentState` や restore 済み state snapshot を読めるのは、「宣言としての Store」と「副作用が走り始めた Store」を分けるためである。 - middleware の default が並行実行なのは、middleware 同士の順序依存を強い設計前提にしないためである。 - `overrides` が state/action handler を触れず、設定だけを上書きできるのは、「その Store がどんな state machine か」は identity に近く、テストや debug 用に変える対象ではないという線引きによる。 -- `error{}` と `exceptionHandler` が分かれていて、`Error` や cancellation を state 遷移の材料にしないのは、業務エラーと実行系の失敗を分けるためである。 +- `recover{}` と `exceptionHandler` が分かれていて、`Error` や cancellation を state 遷移の材料にしないのは、業務エラーと実行系の失敗を分けるためである。 ## 関連 diff --git a/doc/internal/notes/2026-04-25-unhandled-action-behavior.md b/doc/internal/notes/2026-04-25-unhandled-action-behavior.md index 4731bc1..88a38a8 100644 --- a/doc/internal/notes/2026-04-25-unhandled-action-behavior.md +++ b/doc/internal/notes/2026-04-25-unhandled-action-behavior.md @@ -207,7 +207,7 @@ fun StoreOverridesBuilder.unhandledA - `ActionMatchDiagnostics` / `ActionDispatchDiagnostics` にどこまで情報を載せるかは未決定。少なくとも `matchedHandlerCount` と `selectedHandlerIndex` は必要になりやすい。 - `dispatchAndWait(expectedMatchCount)` のような assert API を最初から同時に入れるか、routing diagnostics の上に後から載せるかは未決定。 - README / KDoc では `first match wins` をそのまま用語として出すか、`registration order` 中心に説明するかは未決定。 -- core reporter を入れる場合、reporter が例外を投げたときに `error{}` ではなく `ExceptionHandler` 側へ流す整理でよいかは確認が必要。 +- core reporter を入れる場合、reporter が例外を投げたときに `recover{}` ではなく `ExceptionHandler` 側へ流す整理でよいかは確認が必要。 ## 関連 diff --git a/doc/internal/notes/2026-05-02-plugin-design.md b/doc/internal/notes/2026-05-02-plugin-design.md index e72dbba..f804f5a 100644 --- a/doc/internal/notes/2026-05-02-plugin-design.md +++ b/doc/internal/notes/2026-05-02-plugin-design.md @@ -184,4 +184,4 @@ background work の完了順や、そこで起こした `dispatch()` の interle ## 関連 - [Middleware 実行ポリシーは並行を標準にする](../adr/2026-04-23-middleware-execution-policy.md) -- [`error {}` に流さない framework boundary の例外処理は当面現状維持とする](../adr/2026-05-07-framework-boundary-exception-handling.md) +- [`recover {}` に流さない framework boundary の例外処理は当面現状維持とする](../adr/2026-05-07-framework-boundary-exception-handling.md) diff --git a/koma-core/src/commonMain/kotlin/io/github/komakt/koma/core/StoreBuilder.kt b/koma-core/src/commonMain/kotlin/io/github/komakt/koma/core/StoreBuilder.kt index 3177b7f..75e58aa 100644 --- a/koma-core/src/commonMain/kotlin/io/github/komakt/koma/core/StoreBuilder.kt +++ b/koma-core/src/commonMain/kotlin/io/github/komakt/koma/core/StoreBuilder.kt @@ -210,16 +210,16 @@ class StoreBuilder internal constructor() { } /** - * Registers a handler for a specific exception type in the current state configuration + * Registers a recovery handler for a specific exception type in the current state configuration * with an optional CoroutineDispatcher. - * If multiple `error {}` handlers can match the current state and error, + * If multiple `recover {}` handlers can match the current state and error, * the first registered handler is used. * Supplying a dispatcher changes where the handler runs, but the Store still waits for it to finish. * - * @param dispatcher Optional CoroutineDispatcher override for executing the error handler - * @param block The handler function that processes the error and updates the state + * @param dispatcher Optional CoroutineDispatcher override for executing the recover handler + * @param block The handler function that processes the exception and updates the state */ - inline fun error(dispatcher: CoroutineDispatcher? = null, noinline block: suspend ErrorScope.() -> Unit) { + inline fun recover(dispatcher: CoroutineDispatcher? = null, noinline block: suspend ErrorScope.() -> Unit) { stateErrorHandlers.add( ThreadedHandler( dispatcher = dispatcher, @@ -231,12 +231,23 @@ class StoreBuilder internal constructor() { ), ) } + + /** + * @deprecated Use `recover {}`. + */ + @Deprecated( + message = "Use recover(dispatcher, block)", + replaceWith = ReplaceWith("recover(dispatcher, block)"), + ) + inline fun error(dispatcher: CoroutineDispatcher? = null, noinline block: suspend ErrorScope.() -> Unit) { + recover(dispatcher, block) + } } /** * Configures handlers for a specific state subtype. * - * Inside the block, you can register `enter {}`, `action {}`, `exit {}`, and `error {}` + * Inside the block, you can register `enter {}`, `action {}`, `exit {}`, and `recover {}` * handlers narrowed to [S2]. * Handler selection is first-match in registration order. * If both broad and specific handlers can match, place broader handlers later. diff --git a/koma-core/src/commonMain/kotlin/io/github/komakt/koma/core/StoreScope.kt b/koma-core/src/commonMain/kotlin/io/github/komakt/koma/core/StoreScope.kt index 6be0043..bfbded7 100644 --- a/koma-core/src/commonMain/kotlin/io/github/komakt/koma/core/StoreScope.kt +++ b/koma-core/src/commonMain/kotlin/io/github/komakt/koma/core/StoreScope.kt @@ -409,7 +409,7 @@ typealias ActionLaunchScope = ActionScope.LaunchScope typealias ActionTransactionScope = ActionScope.LaunchScope.TransactionScope /** - * Scope available to an `error {}` handler after a non-fatal exception is caught. + * Scope available to a `recover {}` handler after a non-fatal exception is caught. * * Use this to recover from exceptions or update state accordingly. */ @@ -427,7 +427,7 @@ interface ErrorScope : StoreScope { val error: T /** - * Registers the next state to apply after the current error handler finishes + * Registers the next state to apply after the current recover handler finishes * by computing it in the given block. * This does not update [state] immediately. * If called multiple times in the same handler, the last specified state is used. @@ -467,7 +467,7 @@ interface ErrorScope : StoreScope { fun clearPendingActions() /** - * Emits an event immediately from the current error handler. + * Emits an event immediately from the current recover handler. * Emission is not deferred until after a next state registered in this handler is applied. * * @param event The event to emit diff --git a/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/CounterUseCaseTest.kt b/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/CounterUseCaseTest.kt index bedcd16..3efe4ed 100644 --- a/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/CounterUseCaseTest.kt +++ b/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/CounterUseCaseTest.kt @@ -124,7 +124,7 @@ class CounterUseCaseTest { action { throw RuntimeException(action.message) } - error { + recover { event(AppEvent.ErrorOccurred(error.message ?: "Unknown error")) nextState { AppState.Error(error.message ?: "Unknown error") } } diff --git a/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/LoginUseCaseTest.kt b/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/LoginUseCaseTest.kt index 0e63bd4..068b5c1 100644 --- a/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/LoginUseCaseTest.kt +++ b/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/LoginUseCaseTest.kt @@ -119,7 +119,7 @@ class LoginUseCaseTest { } // Error handling for all states state { - error { + recover { nextState { AppState.Error(error.message ?: "Unknown error") } } } diff --git a/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StoreActionCoroutineScopeTest.kt b/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StoreActionCoroutineScopeTest.kt index 0f31373..1bcc5a2 100644 --- a/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StoreActionCoroutineScopeTest.kt +++ b/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StoreActionCoroutineScopeTest.kt @@ -124,7 +124,7 @@ class StoreActionCoroutineScopeTest { } state { - error { + recover { nextState { AppState.Failed(error.message ?: "unknown") } } } diff --git a/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StoreExceptionTest.kt b/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StoreExceptionTest.kt index 5a24650..0ccfe6e 100644 --- a/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StoreExceptionTest.kt +++ b/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StoreExceptionTest.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.test.assertNotNull @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) @@ -43,4 +44,24 @@ class StoreExceptionTest { assertNotNull(handledException) } + + @Suppress("DEPRECATION") + @Test + fun deprecated_errorDslAlias_shouldStillRecover() = runTest(testDispatcher) { + val store: Store = Store(AppState(0)) { + this.coroutineContext(Dispatchers.Unconfined) + state { + enter { + throw RuntimeException("error") + } + error { + nextState { AppState(1) } + } + } + } + + store.collectState { } // start Store + + assertEquals(AppState(1), store.currentState) + } } diff --git a/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StorePluginExceptionTest.kt b/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StorePluginExceptionTest.kt index f7891c6..72b0f1d 100644 --- a/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StorePluginExceptionTest.kt +++ b/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StorePluginExceptionTest.kt @@ -45,7 +45,7 @@ class StorePluginExceptionTest { } state { - error { + recover { nextState { AppState.Failed(error.message ?: "unknown") } } } diff --git a/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StorePluginTest.kt b/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StorePluginTest.kt index c3b5f79..09300d4 100644 --- a/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StorePluginTest.kt +++ b/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StorePluginTest.kt @@ -88,7 +88,7 @@ class StorePluginTest { } state { - error { + recover { nextState { AppState.Failed(error.message ?: "unknown") } } } diff --git a/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StoreSaverTest.kt b/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StoreSaverTest.kt index 350dd8b..c8a8a6e 100644 --- a/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StoreSaverTest.kt +++ b/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StoreSaverTest.kt @@ -33,7 +33,7 @@ class StoreSaverTest { nextState { state.copy(value = action.value) } } if (errorStateOnException != null) { - error { + recover { nextState { errorStateOnException } } } diff --git a/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StoreStateCoroutineScopeTest.kt b/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StoreStateCoroutineScopeTest.kt index 0fa25a1..5a102d0 100644 --- a/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StoreStateCoroutineScopeTest.kt +++ b/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/StoreStateCoroutineScopeTest.kt @@ -197,7 +197,7 @@ class StoreStateCoroutineScopeTest { } state { - error { + recover { nextState { AppState.Failed(error.message ?: "unknown") } } } diff --git a/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/TodoUseCaseTest.kt b/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/TodoUseCaseTest.kt index 6074454..57a04fb 100644 --- a/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/TodoUseCaseTest.kt +++ b/koma-core/src/commonTest/kotlin/io/github/komakt/koma/core/TodoUseCaseTest.kt @@ -222,7 +222,7 @@ class TodoUseCaseTest { // Global error handling state { - error { + recover { nextState { AppState.Error(error.message ?: "Unknown error") } } } diff --git a/koma-message/src/commonMain/kotlin/io/github/komakt/koma/message/Message.kt b/koma-message/src/commonMain/kotlin/io/github/komakt/koma/message/Message.kt index 93df6e5..2cb835b 100644 --- a/koma-message/src/commonMain/kotlin/io/github/komakt/koma/message/Message.kt +++ b/koma-message/src/commonMain/kotlin/io/github/komakt/koma/message/Message.kt @@ -21,7 +21,7 @@ internal object MessageHub { /** * Sends a [Message] to Koma's process-wide shared message bus. * - * Any DSL scope that implements [StoreScope] can call this, including enter, action, exit, error, + * Any DSL scope that implements [StoreScope] can call this, including enter, action, exit, recover, * launch, and transaction scopes. * Messages are not replayed, so receivers that are not actively collecting when a message is sent * will not receive that past message.