Skip to content

Commit 6ea1de0

Browse files
Merge pull request #13 from ViewFeature/docs/improve-core-documentation
docs: Improve core documentation
2 parents 397afbc + 8fb17a8 commit 6ea1de0

3 files changed

Lines changed: 108 additions & 9 deletions

File tree

Sources/Flow/ActionHandler/ActionHandler.swift

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,36 @@
11
import Foundation
22

3+
/// Action execution closure that mutates state and returns a task.
4+
///
5+
/// This typealias defines the signature for action processing logic used in ``ActionHandler``.
6+
/// The closure receives an action and state, performs any necessary state mutations,
7+
/// and returns an ``ActionTask`` for asynchronous side effects.
8+
///
9+
/// All execution occurs on the **MainActor**, ensuring thread-safe state mutations.
10+
///
11+
/// ## Example
12+
/// ```swift
13+
/// let execution: ActionExecution<MyAction, MyState, Void> = { action, state in
14+
/// switch action {
15+
/// case .increment:
16+
/// state.count += 1
17+
/// return .none
18+
/// }
19+
/// }
20+
/// ```
21+
///
22+
/// ## Type Parameters
23+
/// - `Action`: The action type to process (must be Sendable)
24+
/// - `State`: The state type to mutate (must be AnyObject/reference type)
25+
/// - `ActionResult`: The result type returned from action processing (must be Sendable)
26+
///
27+
/// ## See Also
28+
/// - ``ActionHandler/init(_:)``
29+
///
30+
/// - Note: Type constraints match Feature protocol requirements to ensure consistency.
31+
public typealias ActionExecution<Action: Sendable, State: AnyObject, ActionResult: Sendable> =
32+
@MainActor (Action, State) async -> ActionTask<Action, State, ActionResult>
33+
334
/// A facade for action processing with fluent method chaining capabilities that can return typed results.
435
///
536
/// `ActionHandler` provides a clean, composable API for defining how your feature
@@ -195,8 +226,25 @@ public final class ActionHandler<Action: Sendable, State: AnyObject, ActionResul
195226
extension ActionHandler {
196227
/// Adds error handling to the action processing pipeline.
197228
///
229+
/// The error handler receives any errors thrown during action processing
230+
/// and can update state accordingly (e.g., setting error messages, resetting loading flags).
231+
///
198232
/// - Parameter errorHandler: A closure that handles errors
199233
/// - Returns: A new ActionHandler with error handling
234+
///
235+
/// - Note: If you call `onError` multiple times, only the **last** handler will be used.
236+
/// Each call replaces the previous error handler.
237+
///
238+
/// ## Example
239+
/// ```swift
240+
/// ActionHandler { action, state in
241+
/// // action processing
242+
/// }
243+
/// .onError { error, state in
244+
/// state.errorMessage = error.localizedDescription
245+
/// state.isLoading = false
246+
/// }
247+
/// ```
200248
public func onError(_ errorHandler: @escaping (Error, State) -> Void) -> ActionHandler<
201249
Action, State, ActionResult
202250
> {
@@ -205,8 +253,34 @@ extension ActionHandler {
205253

206254
/// Transforms the task returned by action processing.
207255
///
256+
/// Use this to add cross-cutting concerns like logging, analytics, or monitoring
257+
/// to all tasks without modifying individual action handlers.
258+
///
208259
/// - Parameter taskTransform: A closure that transforms the task
209260
/// - Returns: A new ActionHandler with task transformation
261+
///
262+
/// ## Example: Add Logging to All Tasks
263+
/// ```swift
264+
/// ActionHandler { action, state in
265+
/// // action processing
266+
/// }
267+
/// .transform { task in
268+
/// switch task.operation {
269+
/// case .run(let id, let name, let operation, let onError, let cancelInFlight, let priority):
270+
/// return .run(id: id, name: name, priority: priority) { state in
271+
/// print("Task '\(name ?? id)' starting")
272+
/// let result = try await operation(state)
273+
/// print("Task '\(name ?? id)' completed")
274+
/// return result
275+
/// } onError: { error, state in
276+
/// print("Task '\(name ?? id)' failed: \(error)")
277+
/// onError?(error, state)
278+
/// }
279+
/// default:
280+
/// return task
281+
/// }
282+
/// }
283+
/// ```
210284
public func transform(
211285
_ taskTransform: @escaping (ActionTask<Action, State, ActionResult>)
212286
-> ActionTask<Action, State, ActionResult>
@@ -216,8 +290,26 @@ extension ActionHandler {
216290

217291
/// Adds custom middleware to the action processing pipeline.
218292
///
293+
/// Middleware is executed in the order it's added. Call `use` multiple times
294+
/// to add multiple middlewares, and they will execute sequentially.
295+
///
219296
/// - Parameter middleware: The middleware to add
220297
/// - Returns: A new ActionHandler with the middleware added
298+
///
299+
/// ## Example: Add Multiple Middlewares
300+
/// ```swift
301+
/// ActionHandler { action, state in
302+
/// // action processing
303+
/// }
304+
/// .use(LoggingMiddleware()) // Executes first
305+
/// .use(AnalyticsMiddleware()) // Executes second
306+
/// .use(TimingMiddleware()) // Executes third
307+
/// ```
308+
///
309+
/// - Note: Middleware executes in **registration order**:
310+
/// - `beforeAction` hooks run in order (first → last)
311+
/// - Action logic executes
312+
/// - `afterAction` hooks run in order (first → last)
221313
public func use(_ middleware: some BaseActionMiddleware) -> ActionHandler<
222314
Action, State, ActionResult
223315
> {

Sources/Flow/ActionHandler/ActionProcessor.swift

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
import Foundation
22

3-
/// Action execution closure that mutates state and returns a task.
4-
///
5-
/// - Note: Type constraints match Feature protocol requirements to ensure consistency.
6-
public typealias ActionExecution<Action: Sendable, State: AnyObject, ActionResult: Sendable> =
7-
@MainActor (Action, State) async ->
8-
ActionTask<Action, State, ActionResult>
9-
103
/// Error handler closure that can mutate state in response to errors.
114
public typealias StateErrorHandler<State> = (Error, State) -> Void
125

Sources/Flow/Store/Feature.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ import Foundation
4848
/// switch action {
4949
/// case .login(let credentials):
5050
/// state.isLoading = true // ← Direct state mutation
51-
/// return .run { state in // ← Async task
51+
/// return .run { state in // ← Same state instance (reference type)
5252
/// let user = try await authService.login(credentials)
53-
/// state.user = user
53+
/// state.user = user // ← Mutations visible to outer scope
5454
/// state.isAuthenticated = true
5555
/// state.isLoading = false
5656
/// }
@@ -69,6 +69,11 @@ import Foundation
6969
/// }
7070
/// ```
7171
///
72+
/// - Note: In the `.run` closure, the `state` parameter refers to the same instance as the outer
73+
/// `state` parameter (State is a reference type). All mutations inside `.run` are immediately
74+
/// visible to the outer scope. This allows you to update state both before and during async
75+
/// operations while maintaining a single source of truth.
76+
///
7277
/// ## Task Management
7378
/// Your action handlers can return different task types:
7479
///
@@ -138,6 +143,10 @@ public protocol Feature: Sendable {
138143
/// ```
139144
///
140145
/// - Note: @Observable requires class types for SwiftUI observation
146+
/// - Warning: Your State class **must** use the `@Observable` macro for SwiftUI integration.
147+
/// The type system cannot enforce this requirement. Forgetting `@Observable` will cause
148+
/// SwiftUI views to not update automatically when state changes, and the compiler will
149+
/// not warn you. Always verify your State class has the `@Observable` annotation.
141150
associatedtype State: AnyObject
142151

143152
/// The type representing the result returned from action processing.
@@ -223,6 +232,11 @@ public protocol Feature: Sendable {
223232
/// ensuring thread-safe UI updates. It returns a ``ActionTask`` to handle
224233
/// any asynchronous side effects.
225234
///
235+
/// - Note: The `handle()` method is called **once** during Store initialization.
236+
/// The returned ``ActionHandler`` instance is reused for all subsequent actions.
237+
/// Do not call `handle()` multiple times or store it separately - let the Store
238+
/// manage the ActionHandler lifecycle.
239+
///
226240
/// ## Example
227241
/// ```swift
228242
/// func handle() -> ActionHandler<Action, State, ActionResult> {

0 commit comments

Comments
 (0)