cmdx 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +21 -0
  4. data/.cursor/prompts/yardoc.md +13 -0
  5. data/.rubocop.yml +2 -0
  6. data/CHANGELOG.md +29 -3
  7. data/README.md +2 -1
  8. data/docs/ai_prompts.md +269 -195
  9. data/docs/basics/call.md +126 -60
  10. data/docs/basics/chain.md +190 -160
  11. data/docs/basics/context.md +242 -154
  12. data/docs/basics/setup.md +302 -32
  13. data/docs/callbacks.md +382 -119
  14. data/docs/configuration.md +211 -49
  15. data/docs/deprecation.md +245 -0
  16. data/docs/getting_started.md +161 -39
  17. data/docs/internationalization.md +590 -70
  18. data/docs/interruptions/exceptions.md +135 -118
  19. data/docs/interruptions/faults.md +152 -127
  20. data/docs/interruptions/halt.md +134 -80
  21. data/docs/logging.md +183 -120
  22. data/docs/middlewares.md +165 -392
  23. data/docs/outcomes/result.md +140 -112
  24. data/docs/outcomes/states.md +134 -99
  25. data/docs/outcomes/statuses.md +204 -146
  26. data/docs/parameters/coercions.md +251 -289
  27. data/docs/parameters/defaults.md +224 -169
  28. data/docs/parameters/definitions.md +289 -141
  29. data/docs/parameters/namespacing.md +250 -161
  30. data/docs/parameters/validations.md +247 -159
  31. data/docs/testing.md +196 -203
  32. data/docs/workflows.md +146 -101
  33. data/lib/cmdx/.DS_Store +0 -0
  34. data/lib/cmdx/callback.rb +39 -55
  35. data/lib/cmdx/callback_registry.rb +80 -73
  36. data/lib/cmdx/chain.rb +65 -122
  37. data/lib/cmdx/chain_inspector.rb +23 -116
  38. data/lib/cmdx/chain_serializer.rb +34 -146
  39. data/lib/cmdx/coercion.rb +57 -0
  40. data/lib/cmdx/coercion_registry.rb +113 -0
  41. data/lib/cmdx/coercions/array.rb +18 -36
  42. data/lib/cmdx/coercions/big_decimal.rb +21 -33
  43. data/lib/cmdx/coercions/boolean.rb +21 -40
  44. data/lib/cmdx/coercions/complex.rb +18 -31
  45. data/lib/cmdx/coercions/date.rb +20 -39
  46. data/lib/cmdx/coercions/date_time.rb +22 -39
  47. data/lib/cmdx/coercions/float.rb +19 -32
  48. data/lib/cmdx/coercions/hash.rb +22 -41
  49. data/lib/cmdx/coercions/integer.rb +20 -33
  50. data/lib/cmdx/coercions/rational.rb +20 -32
  51. data/lib/cmdx/coercions/string.rb +23 -31
  52. data/lib/cmdx/coercions/time.rb +24 -40
  53. data/lib/cmdx/coercions/virtual.rb +14 -31
  54. data/lib/cmdx/configuration.rb +101 -162
  55. data/lib/cmdx/context.rb +34 -166
  56. data/lib/cmdx/core_ext/hash.rb +42 -67
  57. data/lib/cmdx/core_ext/module.rb +35 -79
  58. data/lib/cmdx/core_ext/object.rb +63 -98
  59. data/lib/cmdx/correlator.rb +59 -154
  60. data/lib/cmdx/error.rb +37 -202
  61. data/lib/cmdx/errors.rb +153 -216
  62. data/lib/cmdx/fault.rb +68 -150
  63. data/lib/cmdx/faults.rb +26 -137
  64. data/lib/cmdx/immutator.rb +22 -110
  65. data/lib/cmdx/lazy_struct.rb +110 -186
  66. data/lib/cmdx/log_formatters/json.rb +14 -40
  67. data/lib/cmdx/log_formatters/key_value.rb +14 -40
  68. data/lib/cmdx/log_formatters/line.rb +14 -48
  69. data/lib/cmdx/log_formatters/logstash.rb +14 -57
  70. data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
  71. data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
  72. data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
  73. data/lib/cmdx/log_formatters/raw.rb +19 -49
  74. data/lib/cmdx/logger.rb +22 -79
  75. data/lib/cmdx/logger_ansi.rb +31 -72
  76. data/lib/cmdx/logger_serializer.rb +74 -103
  77. data/lib/cmdx/middleware.rb +56 -60
  78. data/lib/cmdx/middleware_registry.rb +82 -77
  79. data/lib/cmdx/middlewares/correlate.rb +41 -226
  80. data/lib/cmdx/middlewares/timeout.rb +46 -185
  81. data/lib/cmdx/parameter.rb +167 -183
  82. data/lib/cmdx/parameter_evaluator.rb +231 -0
  83. data/lib/cmdx/parameter_inspector.rb +37 -55
  84. data/lib/cmdx/parameter_registry.rb +65 -84
  85. data/lib/cmdx/parameter_serializer.rb +32 -76
  86. data/lib/cmdx/railtie.rb +24 -107
  87. data/lib/cmdx/result.rb +254 -259
  88. data/lib/cmdx/result_ansi.rb +28 -80
  89. data/lib/cmdx/result_inspector.rb +34 -70
  90. data/lib/cmdx/result_logger.rb +23 -77
  91. data/lib/cmdx/result_serializer.rb +59 -125
  92. data/lib/cmdx/rspec/matchers.rb +28 -0
  93. data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
  94. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
  95. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
  96. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
  97. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
  98. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
  99. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
  100. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
  101. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
  102. data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
  103. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
  104. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
  105. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
  106. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
  107. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
  108. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
  109. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
  110. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
  111. data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
  112. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
  113. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
  114. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
  115. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
  116. data/lib/cmdx/task.rb +336 -427
  117. data/lib/cmdx/task_deprecator.rb +52 -0
  118. data/lib/cmdx/task_processor.rb +246 -0
  119. data/lib/cmdx/task_serializer.rb +34 -69
  120. data/lib/cmdx/utils/ansi_color.rb +13 -89
  121. data/lib/cmdx/utils/log_timestamp.rb +13 -42
  122. data/lib/cmdx/utils/monotonic_runtime.rb +11 -63
  123. data/lib/cmdx/utils/name_affix.rb +21 -71
  124. data/lib/cmdx/validator.rb +57 -0
  125. data/lib/cmdx/validator_registry.rb +108 -0
  126. data/lib/cmdx/validators/exclusion.rb +55 -94
  127. data/lib/cmdx/validators/format.rb +31 -85
  128. data/lib/cmdx/validators/inclusion.rb +65 -110
  129. data/lib/cmdx/validators/length.rb +117 -133
  130. data/lib/cmdx/validators/numeric.rb +123 -130
  131. data/lib/cmdx/validators/presence.rb +38 -79
  132. data/lib/cmdx/version.rb +1 -7
  133. data/lib/cmdx/workflow.rb +58 -330
  134. data/lib/cmdx.rb +1 -1
  135. data/lib/generators/cmdx/install_generator.rb +14 -31
  136. data/lib/generators/cmdx/task_generator.rb +39 -55
  137. data/lib/generators/cmdx/templates/install.rb +24 -6
  138. data/lib/generators/cmdx/workflow_generator.rb +41 -66
  139. data/lib/locales/ar.yml +0 -1
  140. data/lib/locales/cs.yml +0 -1
  141. data/lib/locales/da.yml +0 -1
  142. data/lib/locales/de.yml +0 -1
  143. data/lib/locales/el.yml +0 -1
  144. data/lib/locales/en.yml +0 -1
  145. data/lib/locales/es.yml +0 -1
  146. data/lib/locales/fi.yml +0 -1
  147. data/lib/locales/fr.yml +0 -1
  148. data/lib/locales/he.yml +0 -1
  149. data/lib/locales/hi.yml +0 -1
  150. data/lib/locales/it.yml +0 -1
  151. data/lib/locales/ja.yml +0 -1
  152. data/lib/locales/ko.yml +0 -1
  153. data/lib/locales/nl.yml +0 -1
  154. data/lib/locales/no.yml +0 -1
  155. data/lib/locales/pl.yml +0 -1
  156. data/lib/locales/pt.yml +0 -1
  157. data/lib/locales/ru.yml +0 -1
  158. data/lib/locales/sv.yml +0 -1
  159. data/lib/locales/th.yml +0 -1
  160. data/lib/locales/tr.yml +0 -1
  161. data/lib/locales/vi.yml +0 -1
  162. data/lib/locales/zh.yml +0 -1
  163. metadata +36 -8
  164. data/lib/cmdx/parameter_validator.rb +0 -81
  165. data/lib/cmdx/parameter_value.rb +0 -244
  166. data/lib/cmdx/parameters_inspector.rb +0 -72
  167. data/lib/cmdx/parameters_serializer.rb +0 -115
  168. data/lib/cmdx/rspec/result_matchers.rb +0 -917
  169. data/lib/cmdx/rspec/task_matchers.rb +0 -570
  170. data/lib/cmdx/validators/custom.rb +0 -102
@@ -1,99 +1,76 @@
1
1
  # Outcomes - Statuses
2
2
 
3
- Statuses represent the outcome of task execution logic, indicating how the task's business logic concluded. Statuses differ from execution states by focusing on the business outcome rather than the technical execution lifecycle. Understanding statuses is crucial for implementing proper business logic branching and error handling.
3
+ Statuses represent the business outcome of task execution logic, indicating how the task's business logic concluded. Statuses differ from execution states by focusing on the business outcome rather than the technical execution lifecycle. Understanding statuses is crucial for implementing proper business logic branching and error handling.
4
4
 
5
5
  ## Table of Contents
6
6
 
7
7
  - [TLDR](#tldr)
8
8
  - [Status Definitions](#status-definitions)
9
- - [Status Characteristics](#status-characteristics)
10
- - [Status Predicates](#status-predicates)
11
9
  - [Status Transitions](#status-transitions)
10
+ - [Status Predicates](#status-predicates)
12
11
  - [Status-Based Callbacks](#status-based-callbacks)
13
12
  - [Status Metadata](#status-metadata)
14
13
  - [Outcome-Based Logic](#outcome-based-logic)
15
- - [Status Serialization and Inspection](#status-serialization-and-inspection)
16
14
  - [Status vs State vs Outcome](#status-vs-state-vs-outcome)
15
+ - [Status Serialization and Inspection](#status-serialization-and-inspection)
17
16
 
18
17
  ## TLDR
19
18
 
20
- - **Statuses** - Business outcome of execution: `success` (default), `skipped` (via `skip!`), `failed` (via `fail!`)
21
- - **One-way transitions** - Only `success` → `skipped`/`failed`, never reverse
22
- - **Predicates** - Check with `result.success?`, `result.skipped?`, `result.failed?`
23
- - **Outcomes** - `result.good?` = success OR skipped, `result.bad?` = skipped OR failed
24
- - **Rich metadata** - Both `skip!()` and `fail!()` accept metadata for context
25
-
26
- ## Status Definitions
27
-
28
- | Status | Description |
29
- | --------- | ----------- |
30
- | `success` | Task execution completed successfully with expected business outcome |
31
- | `skipped` | Task intentionally stopped execution because conditions weren't met or continuation was unnecessary |
32
- | `failed` | Task stopped execution due to business rule violations, validation errors, or exceptions |
33
-
34
- > [!NOTE]
35
- > Statuses focus on business outcomes, not technical execution states. A task can be technically "complete" but have a "failed" status if business logic determined the operation could not succeed.
36
-
37
- ## Status Characteristics
38
-
39
- ### Success
40
- - **Default status** for all newly created tasks
41
- - Indicates business logic completed as expected
42
- - Remains even if no actual execution occurred (e.g., cached results)
43
- - Compatible with both `complete` and `interrupted` states
44
-
45
- ### Skipped
46
- - Indicates intentional early termination
47
- - Business logic determined execution was unnecessary
48
- - Often used for conditional workflows and guard clauses
49
- - Triggered by `skip!` method with contextual metadata
19
+ ```ruby
20
+ # Check business outcomes
21
+ result.success? # true (default outcome)
22
+ result.skipped? # false (via skip!)
23
+ result.failed? # false (via fail!)
50
24
 
51
- ### Failed
52
- - Indicates business logic could not complete successfully
53
- - Can result from explicit failures or caught exceptions
54
- - Contains detailed error information in metadata
55
- - Triggered by `fail!` method or automatic exception handling
25
+ # Outcome-based logic
26
+ result.good? # true (success OR skipped)
27
+ result.bad? # false (skipped OR failed)
56
28
 
57
- ## Status Predicates
29
+ # Status-based callbacks
30
+ result
31
+ .on_success { |r| process_success(r) }
32
+ .on_skipped { |r| handle_skip_condition(r) }
33
+ .on_failed { |r| handle_business_failure(r) }
58
34
 
59
- Use status predicates to check execution outcomes:
35
+ # Statuses: HOW it ended, States: WHERE in lifecycle
36
+ result.status #=> "success" (business outcome)
37
+ result.state #=> "complete" (execution lifecycle)
38
+ ```
60
39
 
61
- ```ruby
62
- result = ProcessUserOrderTask.call
40
+ ## Status Definitions
63
41
 
64
- # Individual status checks
65
- result.success? #=> true/false
66
- result.skipped? #=> true/false
67
- result.failed? #=> true/false
42
+ > [!IMPORTANT]
43
+ > Statuses represent business outcomes, not technical execution states. A task can be technically "complete" but have a "failed" status if business logic determined the operation could not succeed.
68
44
 
69
- # Outcome categorization
70
- result.good? #=> true if success OR skipped
71
- result.bad? #=> true if skipped OR failed (not success)
72
- ```
45
+ | Status | Description |
46
+ | ------ | ----------- |
47
+ | `success` | Task execution completed successfully with expected business outcome. Default status for all tasks. |
48
+ | `skipped` | Task intentionally stopped execution because conditions weren't met or continuation was unnecessary. |
49
+ | `failed` | Task stopped execution due to business rule violations, validation errors, or exceptions. |
73
50
 
74
51
  ## Status Transitions
75
52
 
53
+ > [!WARNING]
54
+ > Status transitions are unidirectional and final. Once a task is marked as skipped or failed, it cannot return to success status. Design your business logic accordingly.
55
+
76
56
  Unlike states, statuses can only transition from success to skipped/failed:
77
57
 
78
58
  ```ruby
79
59
  # Valid status transitions
80
- success -> skipped # (via skip!)
81
- success -> failed # (via fail! or exception)
60
+ success skipped # via skip!
61
+ success failed # via fail! or exception
82
62
 
83
63
  # Invalid transitions (will raise errors)
84
- skipped -> success # ❌ Cannot transition
85
- skipped -> failed # ❌ Cannot transition
86
- failed -> success # ❌ Cannot transition
87
- failed -> skipped # ❌ Cannot transition
64
+ skipped success # ❌ Cannot transition
65
+ skipped failed # ❌ Cannot transition
66
+ failed success # ❌ Cannot transition
67
+ failed skipped # ❌ Cannot transition
88
68
  ```
89
69
 
90
- > [!IMPORTANT]
91
- > Status transitions are unidirectional and final. Once a task is marked as skipped or failed, it cannot return to success status. Design your business logic accordingly.
92
-
93
70
  ### Status Transition Examples
94
71
 
95
72
  ```ruby
96
- class ProcessUserOrderTask < CMDx::Task
73
+ class ProcessOrderTask < CMDx::Task
97
74
  def call
98
75
  # Task starts with success status
99
76
  context.result.success? #=> true
@@ -105,7 +82,7 @@ class ProcessUserOrderTask < CMDx::Task
105
82
  end
106
83
 
107
84
  # Conditional failure
108
- unless context.user.has_permission?
85
+ unless context.user.authorized?
109
86
  fail!(reason: "Insufficient permissions")
110
87
  # Status is now failed, execution halts
111
88
  end
@@ -117,112 +94,149 @@ class ProcessUserOrderTask < CMDx::Task
117
94
  end
118
95
  ```
119
96
 
97
+ ## Status Predicates
98
+
99
+ Use status predicates to check execution outcomes:
100
+
101
+ ```ruby
102
+ class PaymentProcessingTask < CMDx::Task
103
+ def call
104
+ charge_customer
105
+ send_receipt
106
+ end
107
+ end
108
+
109
+ result = PaymentProcessingTask.call
110
+
111
+ # Individual status checks
112
+ result.success? #=> true/false
113
+ result.skipped? #=> true/false
114
+ result.failed? #=> true/false
115
+
116
+ # Outcome categorization
117
+ result.good? #=> true if success OR skipped
118
+ result.bad? #=> true if skipped OR failed (not success)
119
+ ```
120
+
121
+ ### Status Checking in Business Logic
122
+
123
+ ```ruby
124
+ def handle_payment_result(result)
125
+ if result.success?
126
+ send_confirmation_email(result.context.customer)
127
+ elsif result.skipped?
128
+ log_skip_reason(result.metadata[:reason])
129
+ elsif result.failed?
130
+ handle_payment_failure(result.metadata)
131
+ end
132
+ end
133
+ ```
134
+
120
135
  ## Status-Based Callbacks
121
136
 
122
- Results provide comprehensive callback methods for status-based logic:
137
+ > [!TIP]
138
+ > Use status-based callbacks for business logic branching. The `on_good` and `on_bad` callbacks are particularly useful for handling success/skip vs failed outcomes respectively.
123
139
 
124
140
  ```ruby
125
- result = ProcessUserOrderTask.call
141
+ class OrderFulfillmentTask < CMDx::Task
142
+ def call
143
+ validate_inventory
144
+ process_payment
145
+ schedule_shipping
146
+ end
147
+ end
148
+
149
+ result = OrderFulfillmentTask.call
126
150
 
127
151
  # Individual status callbacks
128
152
  result
129
- .on_success { |r| handle_success(r) }
130
- .on_skipped { |r| handle_skip(r) }
131
- .on_failed { |r| handle_failure(r) }
153
+ .on_success { |r| schedule_delivery(r.context.order) }
154
+ .on_skipped { |r| notify_backorder(r.context.customer) }
155
+ .on_failed { |r| refund_payment(r.context.payment_id) }
132
156
 
133
157
  # Outcome-based callbacks
134
158
  result
135
- .on_good { |r| log_positive_outcome(r) }
136
- .on_bad { |r| log_negative_outcome(r) }
159
+ .on_good { |r| update_inventory(r.context.items) }
160
+ .on_bad { |r| log_negative_outcome(r.metadata) }
137
161
  ```
138
162
 
139
- > [!TIP]
140
- > Use status-based callbacks for business logic branching. The `on_good` and `on_bad` callbacks are particularly useful for handling success/skip vs failed outcomes respectively.
141
-
142
163
  ## Status Metadata
143
164
 
144
- Statuses carry rich metadata providing context about execution outcomes:
165
+ > [!NOTE]
166
+ > Always include rich metadata with skip and fail operations. This information is invaluable for debugging, user feedback, and automated error handling.
145
167
 
146
168
  ### Success Metadata
147
169
 
148
170
  ```ruby
149
- class ProcessUserOrderTask < CMDx::Task
171
+ class ProcessRefundTask < CMDx::Task
150
172
  def call
151
- # Success metadata can include business context
152
- context.order = Order.find(context.order_id)
153
- context.order.process!
154
-
155
- # Success status typically has empty metadata
156
- # but can include business-relevant information
157
- context.processing_time = Time.now - context.start_time
158
- context.confirmation_number = generate_confirmation
173
+ refund = create_refund(context.payment_id)
174
+ context.refund_id = refund.id
175
+ context.processed_at = Time.now
159
176
  end
160
177
  end
161
178
 
162
- result = ProcessUserOrderTask.call(order_id: 123)
179
+ result = ProcessRefundTask.call(payment_id: "pay_123")
163
180
  result.success? #=> true
164
- result.metadata #=> {} (usually empty for success)
181
+ result.metadata #=> {} (typically empty for success)
165
182
  ```
166
183
 
167
184
  ### Skip Metadata
168
185
 
169
186
  ```ruby
170
- class ProcessUserOrderTask < CMDx::Task
187
+ class ProcessSubscriptionTask < CMDx::Task
171
188
  def call
172
- order = Order.find(context.order_id)
189
+ subscription = Subscription.find(context.subscription_id)
173
190
 
174
- if order.already_processed?
191
+ if subscription.cancelled?
175
192
  skip!(
176
- reason: "Order already processed",
177
- processed_at: order.processed_at,
178
- original_processor: order.processor_id,
179
- skip_code: "DUPLICATE_PROCESSING"
193
+ reason: "Subscription already cancelled",
194
+ cancelled_at: subscription.cancelled_at,
195
+ skip_code: "ALREADY_CANCELLED"
180
196
  )
181
197
  end
182
198
 
183
- # Continue processing...
199
+ process_subscription(subscription)
184
200
  end
185
201
  end
186
202
 
187
- result = ProcessUserOrderTask.call(order_id: 123)
203
+ result = ProcessSubscriptionTask.call(subscription_id: 123)
188
204
  if result.skipped?
189
- result.metadata[:reason] #=> "Order already processed"
190
- result.metadata[:processed_at] #=> 2023-10-01 10:30:00 UTC
191
- result.metadata[:original_processor] #=> "user-456"
192
- result.metadata[:skip_code] #=> "DUPLICATE_PROCESSING"
205
+ result.metadata[:reason] #=> "Subscription already cancelled"
206
+ result.metadata[:cancelled_at] #=> 2023-10-01 10:30:00 UTC
207
+ result.metadata[:skip_code] #=> "ALREADY_CANCELLED"
193
208
  end
194
209
  ```
195
210
 
196
211
  ### Failure Metadata
197
212
 
198
213
  ```ruby
199
- class ValidateOrderDataTask < CMDx::Task
214
+ class ValidateUserDataTask < CMDx::Task
200
215
  def call
201
- unless context.order.valid?
216
+ user = User.find(context.user_id)
217
+
218
+ unless user.valid?
202
219
  fail!(
203
- reason: "Order validation failed",
204
- errors: context.order.errors.full_messages,
220
+ reason: "User validation failed",
221
+ errors: user.errors.full_messages,
205
222
  error_code: "VALIDATION_FAILED",
206
- retryable: false,
207
- failed_at: Time.now
223
+ retryable: false
208
224
  )
209
225
  end
226
+
227
+ context.validated_user = user
210
228
  end
211
229
  end
212
230
 
213
- result = ValidateOrderDataTask.call(order_id: 123)
231
+ result = ValidateUserDataTask.call(user_id: 123)
214
232
  if result.failed?
215
- result.metadata[:reason] #=> "Order validation failed"
216
- result.metadata[:errors] #=> ["Name can't be blank", "Email is invalid"]
233
+ result.metadata[:reason] #=> "User validation failed"
234
+ result.metadata[:errors] #=> ["Email is invalid", "Name can't be blank"]
217
235
  result.metadata[:error_code] #=> "VALIDATION_FAILED"
218
236
  result.metadata[:retryable] #=> false
219
- result.metadata[:failed_at] #=> 2023-10-01 10:30:00 UTC
220
237
  end
221
238
  ```
222
239
 
223
- > [!TIP]
224
- > Always try to include rich metadata with skip and fail operations. This information is invaluable for debugging, user feedback, and automated error handling.
225
-
226
240
  ## Outcome-Based Logic
227
241
 
228
242
  Statuses enable sophisticated outcome-based decision making:
@@ -230,53 +244,51 @@ Statuses enable sophisticated outcome-based decision making:
230
244
  ### Good vs Bad Outcomes
231
245
 
232
246
  ```ruby
233
- # Good outcomes (success OR skipped)
234
- result.good? #=> true if success? || skipped?
235
- result.bad? #=> true if !success? (skipped OR failed)
247
+ class EmailDeliveryTask < CMDx::Task
248
+ def call
249
+ # Business logic here
250
+ send_email
251
+ end
252
+ end
236
253
 
237
- # Usage patterns
254
+ result = EmailDeliveryTask.call
255
+
256
+ # Good outcomes (success OR skipped)
238
257
  if result.good?
239
258
  # Both success and skipped are "good" outcomes
240
259
  update_user_interface(result)
241
- log_completed_action(result)
260
+ track_completion_metrics(result)
242
261
  end
243
262
 
263
+ # Bad outcomes (skipped OR failed, excluding success)
244
264
  if result.bad?
245
- # Handle any non-success outcome (skipped or failed)
265
+ # Handle any non-success outcome
246
266
  show_error_message(result.metadata[:reason])
247
- track_negative_outcome(result)
267
+ track_failure_metrics(result)
248
268
  end
249
269
  ```
250
270
 
251
- ## Status Serialization and Inspection
252
-
253
- Statuses are fully captured in result serialization:
271
+ ### Conditional Processing
254
272
 
255
273
  ```ruby
256
- result = ProcessUserOrderTask.call
257
-
258
- # Hash representation
259
- result.to_h[:status] #=> "success"
260
-
261
- # Full serialization includes status
262
- result.to_h
263
- #=> {
264
- # class: "ProcessUserOrderTask",
265
- # index: 0,
266
- # state: "complete",
267
- # status: "success",
268
- # outcome: "success",
269
- # metadata: {},
270
- # # ... other attributes
271
- # }
272
-
273
- # Human-readable inspection
274
- result.to_s
275
- #=> "ProcessUserOrderTask: type=Task index=0 state=complete status=success outcome=success..."
274
+ def process_batch_results(results)
275
+ successful_count = results.count(&:success?)
276
+ skipped_count = results.count(&:skipped?)
277
+ failed_count = results.count(&:failed?)
278
+
279
+ if results.all?(&:good?)
280
+ mark_batch_complete
281
+ elsif results.any?(&:failed?)
282
+ schedule_batch_retry(results.select(&:failed?))
283
+ end
284
+ end
276
285
  ```
277
286
 
278
287
  ## Status vs State vs Outcome
279
288
 
289
+ > [!NOTE]
290
+ > Status tracks the business outcome (how the task ended), while state tracks the execution lifecycle (where the task is). Both provide valuable but different information about task execution.
291
+
280
292
  Understanding the relationship between these concepts:
281
293
 
282
294
  - **Status**: Business execution outcome (`success`, `skipped`, `failed`)
@@ -284,24 +296,70 @@ Understanding the relationship between these concepts:
284
296
  - **Outcome**: Combined representation for unified logic
285
297
 
286
298
  ```ruby
287
- result = ProcessUserOrderTask.call
299
+ class DataImportTask < CMDx::Task
300
+ def call
301
+ import_data
302
+ validate_data
303
+ end
304
+ end
288
305
 
289
- # Different scenarios
290
- result.state #=> "complete"
291
- result.status #=> "success"
306
+ result = DataImportTask.call
307
+
308
+ # Successful execution
309
+ result.state #=> "complete" (execution finished)
310
+ result.status #=> "success" (business outcome)
292
311
  result.outcome #=> "success" (same as status when complete)
293
312
 
294
- # Skipped task
313
+ # Skipped execution
314
+ skipped_result = DataImportTask.call(skip_import: true)
295
315
  skipped_result.state #=> "complete" (execution finished)
296
316
  skipped_result.status #=> "skipped" (business outcome)
297
317
  skipped_result.outcome #=> "skipped" (same as status)
298
318
 
299
- # Failed task
319
+ # Failed execution
320
+ failed_result = DataImportTask.call(invalid_data: true)
300
321
  failed_result.state #=> "interrupted" (execution stopped)
301
322
  failed_result.status #=> "failed" (business outcome)
302
323
  failed_result.outcome #=> "interrupted" (reflects state for interrupted tasks)
303
324
  ```
304
325
 
326
+ ## Status Serialization and Inspection
327
+
328
+ > [!IMPORTANT]
329
+ > Statuses are automatically captured in result serialization and logging. All status information persists through the complete task execution lifecycle.
330
+
331
+ ```ruby
332
+ result = ProcessOrderTask.call
333
+
334
+ # Hash representation includes status
335
+ result.to_h
336
+ #=> {
337
+ # class: "ProcessOrderTask",
338
+ # index: 0,
339
+ # state: "complete",
340
+ # status: "success",
341
+ # outcome: "success",
342
+ # runtime: 0.045,
343
+ # metadata: {},
344
+ # context: { order_id: 123 }
345
+ # }
346
+
347
+ # Human-readable inspection
348
+ result.to_s
349
+ #=> "ProcessOrderTask: type=Task index=0 state=complete status=success outcome=success runtime=0.045s"
350
+
351
+ # Chain-level status aggregation
352
+ result.chain.to_h
353
+ #=> {
354
+ # id: "chain-550e8400-e29b-41d4-a716-446655440000",
355
+ # state: "complete",
356
+ # status: "success",
357
+ # results: [
358
+ # { state: "complete", status: "success", ... }
359
+ # ]
360
+ # }
361
+ ```
362
+
305
363
  ---
306
364
 
307
365
  - **Prev:** [Outcomes - Result](result.md)