cmdx 1.1.0 → 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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +13 -12
  4. data/.cursor/prompts/yardoc.md +11 -6
  5. data/CHANGELOG.md +13 -2
  6. data/README.md +1 -0
  7. data/docs/ai_prompts.md +269 -195
  8. data/docs/basics/call.md +124 -58
  9. data/docs/basics/chain.md +190 -160
  10. data/docs/basics/context.md +242 -154
  11. data/docs/basics/setup.md +302 -32
  12. data/docs/callbacks.md +390 -94
  13. data/docs/configuration.md +181 -65
  14. data/docs/deprecation.md +245 -0
  15. data/docs/getting_started.md +161 -39
  16. data/docs/internationalization.md +590 -70
  17. data/docs/interruptions/exceptions.md +135 -118
  18. data/docs/interruptions/faults.md +150 -125
  19. data/docs/interruptions/halt.md +134 -80
  20. data/docs/logging.md +181 -118
  21. data/docs/middlewares.md +150 -377
  22. data/docs/outcomes/result.md +140 -112
  23. data/docs/outcomes/states.md +134 -99
  24. data/docs/outcomes/statuses.md +204 -146
  25. data/docs/parameters/coercions.md +232 -281
  26. data/docs/parameters/defaults.md +224 -169
  27. data/docs/parameters/definitions.md +289 -141
  28. data/docs/parameters/namespacing.md +250 -161
  29. data/docs/parameters/validations.md +260 -133
  30. data/docs/testing.md +191 -197
  31. data/docs/workflows.md +143 -98
  32. data/lib/cmdx/callback.rb +23 -19
  33. data/lib/cmdx/callback_registry.rb +1 -3
  34. data/lib/cmdx/chain_inspector.rb +23 -23
  35. data/lib/cmdx/chain_serializer.rb +38 -19
  36. data/lib/cmdx/coercion.rb +20 -12
  37. data/lib/cmdx/coercion_registry.rb +51 -32
  38. data/lib/cmdx/configuration.rb +84 -31
  39. data/lib/cmdx/context.rb +32 -21
  40. data/lib/cmdx/core_ext/hash.rb +13 -13
  41. data/lib/cmdx/core_ext/module.rb +1 -1
  42. data/lib/cmdx/core_ext/object.rb +12 -12
  43. data/lib/cmdx/correlator.rb +60 -39
  44. data/lib/cmdx/errors.rb +105 -131
  45. data/lib/cmdx/fault.rb +66 -45
  46. data/lib/cmdx/immutator.rb +20 -21
  47. data/lib/cmdx/lazy_struct.rb +78 -70
  48. data/lib/cmdx/log_formatters/json.rb +1 -1
  49. data/lib/cmdx/log_formatters/key_value.rb +1 -1
  50. data/lib/cmdx/log_formatters/line.rb +1 -1
  51. data/lib/cmdx/log_formatters/logstash.rb +1 -1
  52. data/lib/cmdx/log_formatters/pretty_json.rb +1 -1
  53. data/lib/cmdx/log_formatters/pretty_key_value.rb +1 -1
  54. data/lib/cmdx/log_formatters/pretty_line.rb +1 -1
  55. data/lib/cmdx/log_formatters/raw.rb +2 -2
  56. data/lib/cmdx/logger.rb +19 -14
  57. data/lib/cmdx/logger_ansi.rb +33 -17
  58. data/lib/cmdx/logger_serializer.rb +85 -24
  59. data/lib/cmdx/middleware.rb +39 -21
  60. data/lib/cmdx/middleware_registry.rb +4 -3
  61. data/lib/cmdx/parameter.rb +151 -89
  62. data/lib/cmdx/parameter_inspector.rb +34 -21
  63. data/lib/cmdx/parameter_registry.rb +36 -30
  64. data/lib/cmdx/parameter_serializer.rb +21 -14
  65. data/lib/cmdx/result.rb +136 -135
  66. data/lib/cmdx/result_ansi.rb +31 -17
  67. data/lib/cmdx/result_inspector.rb +32 -27
  68. data/lib/cmdx/result_logger.rb +23 -14
  69. data/lib/cmdx/result_serializer.rb +65 -27
  70. data/lib/cmdx/task.rb +234 -113
  71. data/lib/cmdx/task_deprecator.rb +22 -25
  72. data/lib/cmdx/task_processor.rb +89 -88
  73. data/lib/cmdx/task_serializer.rb +27 -14
  74. data/lib/cmdx/utils/monotonic_runtime.rb +2 -4
  75. data/lib/cmdx/validator.rb +25 -16
  76. data/lib/cmdx/validator_registry.rb +53 -31
  77. data/lib/cmdx/validators/exclusion.rb +1 -1
  78. data/lib/cmdx/validators/format.rb +2 -2
  79. data/lib/cmdx/validators/inclusion.rb +2 -2
  80. data/lib/cmdx/validators/length.rb +2 -2
  81. data/lib/cmdx/validators/numeric.rb +3 -3
  82. data/lib/cmdx/validators/presence.rb +2 -2
  83. data/lib/cmdx/version.rb +1 -1
  84. data/lib/cmdx/workflow.rb +54 -33
  85. data/lib/generators/cmdx/task_generator.rb +6 -6
  86. data/lib/generators/cmdx/workflow_generator.rb +6 -6
  87. metadata +3 -1
@@ -1,239 +1,264 @@
1
1
  # Interruptions - Faults
2
2
 
3
- Faults are the exception mechanisms by which CMDx halts task execution via the
4
- `skip!` and `fail!` methods. When tasks are executed with the bang `call!` method,
5
- fault exceptions matching the task's interruption status are raised, enabling
6
- sophisticated exception handling and control flow patterns.
3
+ Faults are exception mechanisms that halt task execution via `skip!` and `fail!` methods. When tasks execute with the `call!` method, fault exceptions matching the task's interruption status are raised, enabling sophisticated exception handling and control flow patterns.
7
4
 
8
5
  ## Table of Contents
9
6
 
10
7
  - [TLDR](#tldr)
11
8
  - [Fault Types](#fault-types)
12
- - [Basic Exception Handling](#basic-exception-handling)
9
+ - [Exception Handling](#exception-handling)
13
10
  - [Fault Context Access](#fault-context-access)
14
- - [Advanced Fault Matching](#advanced-fault-matching)
15
- - [Fault Propagation (`throw!`)](#fault-propagation-throw)
16
- - [Fault Chain Analysis](#fault-chain-analysis)
17
- - [Task Halt Configuration](#task-halt-configuration)
11
+ - [Advanced Matching](#advanced-matching)
12
+ - [Fault Propagation](#fault-propagation)
13
+ - [Chain Analysis](#chain-analysis)
14
+ - [Configuration](#configuration)
18
15
 
19
16
  ## TLDR
20
17
 
21
- - **Fault types** - `CMDx::Skipped` (from `skip!`) and `CMDx::Failed` (from `fail!`)
22
- - **Exception handling** - Use `rescue CMDx::Fault` to catch both types
23
- - **Full context** - Faults provide access to `result`, `task`, `context`, and `chain`
24
- - **Advanced matching** - Use `for?(TaskClass)` and `matches? { |f| condition }` for specific fault handling
25
- - **Propagation** - Use `throw!(result)` to bubble up failures while preserving fault context
18
+ ```ruby
19
+ # Basic exception handling
20
+ begin
21
+ PaymentProcessor.call!(amount: 100)
22
+ rescue CMDx::Skipped => e
23
+ handle_skipped_payment(e.result.metadata[:reason])
24
+ rescue CMDx::Failed => e
25
+ handle_failed_payment(e.result.metadata[:error])
26
+ rescue CMDx::Fault => e
27
+ handle_any_interruption(e)
28
+ end
26
29
 
27
- ## Fault Types
30
+ # Advanced matching
31
+ rescue CMDx::Failed.for?(PaymentProcessor, CardValidator) => e
32
+ rescue CMDx::Fault.matches? { |f| f.context.amount > 1000 } => e
28
33
 
29
- CMDx provides two primary fault types that inherit from the base `CMDx::Fault` class:
34
+ # Fault propagation
35
+ throw!(validation_result) if validation_result.failed?
36
+ ```
30
37
 
31
- - **`CMDx::Skipped`** - Raised when a task is skipped via `skip!`
32
- - **`CMDx::Failed`** - Raised when a task fails via `fail!`
38
+ ## Fault Types
33
39
 
34
- Both fault types provide full access to the task execution context, including
35
- the result object, task instance, context data, and chain information.
40
+ | Type | Triggered By | Use Case |
41
+ |------|--------------|----------|
42
+ | `CMDx::Skipped` | `skip!` method | Optional processing, early returns |
43
+ | `CMDx::Failed` | `fail!` method | Validation errors, processing failures |
44
+ | `CMDx::Fault` | Base class | Catch-all for any interruption |
36
45
 
37
46
  > [!NOTE]
38
- > All fault exceptions (`CMDx::Skipped` and `CMDx::Failed`) inherit from the base `CMDx::Fault` class and provide access to the complete task execution context.
47
+ > All fault exceptions inherit from `CMDx::Fault` and provide access to the complete task execution context including result, task, context, and chain information.
39
48
 
40
- ## Basic Exception Handling
49
+ ## Exception Handling
41
50
 
42
- Use standard Ruby `rescue` blocks to handle faults with custom logic:
51
+ ### Basic Rescue Patterns
43
52
 
44
53
  ```ruby
45
54
  begin
46
- ProcessUserOrderTask.call!(order_id: 123)
55
+ ProcessOrderTask.call!(order_id: 123)
47
56
  rescue CMDx::Skipped => e
48
- # Handle skipped tasks
49
- logger.info "Task skipped: #{e.message}"
50
- e.result.metadata[:reason] #=> "Order already processed"
57
+ logger.info "Order processing skipped: #{e.message}"
58
+ schedule_retry(e.context.order_id)
51
59
  rescue CMDx::Failed => e
52
- # Handle failed tasks
53
- logger.error "Task failed: #{e.message}"
54
- e.result.metadata[:error_code] #=> "PAYMENT_DECLINED"
60
+ logger.error "Order processing failed: #{e.message}"
61
+ notify_customer(e.context.customer_email, e.result.metadata[:error])
55
62
  rescue CMDx::Fault => e
56
- # Handle any fault (skipped or failed)
57
- logger.warn "Task interrupted: #{e.message}"
63
+ logger.warn "Order processing interrupted: #{e.message}"
64
+ rollback_transaction
65
+ end
66
+ ```
67
+
68
+ ### Error-Specific Handling
69
+
70
+ ```ruby
71
+ begin
72
+ PaymentProcessor.call!(card_token: token, amount: amount)
73
+ rescue CMDx::Failed => e
74
+ case e.result.metadata[:error_code]
75
+ when "INSUFFICIENT_FUNDS"
76
+ suggest_different_payment_method
77
+ when "CARD_DECLINED"
78
+ request_card_verification
79
+ when "NETWORK_ERROR"
80
+ retry_payment_later
81
+ else
82
+ escalate_to_support(e)
83
+ end
58
84
  end
59
85
  ```
60
86
 
61
87
  ## Fault Context Access
62
88
 
63
- Faults provide comprehensive access to task execution context:
89
+ Faults provide comprehensive access to execution context:
64
90
 
65
91
  ```ruby
66
92
  begin
67
- ProcessUserOrderTask.call!(order_id: 123)
93
+ UserRegistration.call!(email: email, password: password)
68
94
  rescue CMDx::Fault => e
69
95
  # Result information
70
96
  e.result.status #=> "failed" or "skipped"
71
- e.result.metadata[:reason] #=> "Insufficient inventory"
97
+ e.result.metadata[:reason] #=> "Email already exists"
72
98
  e.result.runtime #=> 0.05
73
99
 
74
100
  # Task information
75
- e.task.class.name #=> "ProcessUserOrderTask"
101
+ e.task.class.name #=> "UserRegistration"
76
102
  e.task.id #=> "abc123..."
77
103
 
78
104
  # Context data
79
- e.context.order_id #=> 123
80
- e.context.customer_email #=> "user@example.com"
105
+ e.context.email #=> "user@example.com"
106
+ e.context.password #=> "[FILTERED]"
81
107
 
82
- # Chain information
83
- e.chain.id #=> "def456..."
84
- e.chain.results.size #=> 3
108
+ # Chain information (for workflows)
109
+ e.chain&.id #=> "def456..."
110
+ e.chain&.results&.size #=> 3
85
111
  end
86
112
  ```
87
113
 
88
- ## Advanced Fault Matching
114
+ ## Advanced Matching
89
115
 
90
- ### Task-Specific Matching (`for?`)
116
+ ### Task-Specific Matching
91
117
 
92
- Match faults only from specific task classes using the `for?` method:
118
+ > [!TIP]
119
+ > Use `for?` to handle faults only from specific task classes, enabling targeted exception handling in complex workflows.
93
120
 
94
121
  ```ruby
95
122
  begin
96
- WorkflowProcessUserOrdersTask.call!(orders: orders)
97
- rescue CMDx::Skipped.for?(ProcessUserOrderTask, ValidateUserOrderTask) => e
98
- # Handle skips only from specific task types
99
- logger.info "Order processing skipped: #{e.task.class.name}"
100
- reschedule_order_processing(e.context.order_id)
101
- rescue CMDx::Failed.for?(ProcessOrderPaymentTask, ProcessCardChargeTask) => e
102
- # Handle failures only from payment-related tasks
103
- logger.error "Payment processing failed: #{e.message}"
104
- retry_with_backup_payment_method(e.context)
123
+ PaymentWorkflow.call!(payment_data: data)
124
+ rescue CMDx::Failed.for?(CardValidator, PaymentProcessor) => e
125
+ # Handle only payment-related failures
126
+ retry_with_backup_method(e.context)
127
+ rescue CMDx::Skipped.for?(FraudCheck, RiskAssessment) => e
128
+ # Handle security-related skips
129
+ flag_for_manual_review(e.context.transaction_id)
105
130
  end
106
131
  ```
107
132
 
108
- ### Custom Matching Logic (`matches?`)
109
-
110
- Use the `matches?` method with blocks for sophisticated fault matching:
133
+ ### Custom Logic Matching
111
134
 
112
135
  ```ruby
113
136
  begin
114
- ProcessUserOrderTask.call!(order_id: 123)
115
- rescue CMDx::Fault.matches? { |f| f.result.metadata[:error_code] == "PAYMENT_DECLINED" } => e
116
- # Handle specific payment errors
117
- retry_with_different_payment_method(e.context)
137
+ OrderProcessor.call!(order: order_data)
118
138
  rescue CMDx::Fault.matches? { |f| f.context.order_value > 1000 } => e
119
- # Handle high-value order failures differently
120
- escalate_to_manager(e)
121
- rescue CMDx::Failed.matches? { |f| f.result.metadata[:reason]&.include?("timeout") } => e
122
- # Handle timeout-specific failures
123
- retry_with_longer_timeout(e)
139
+ escalate_high_value_failure(e)
140
+ rescue CMDx::Failed.matches? { |f| f.result.metadata[:retry_count] > 3 } => e
141
+ abandon_processing(e)
142
+ rescue CMDx::Fault.matches? { |f| f.result.metadata[:error_type] == "timeout" } => e
143
+ increase_timeout_and_retry(e)
124
144
  end
125
145
  ```
126
146
 
127
- > [!TIP]
128
- > Use `for?` and `matches?` methods for advanced exception matching. The `for?` method is ideal for task-specific handling, while `matches?` enables custom logic-based fault filtering.
129
-
130
- ## Fault Propagation (`throw!`)
147
+ ## Fault Propagation
131
148
 
132
- The `throw!` method enables fault propagation, allowing parent tasks to bubble up
133
- failures from subtasks while preserving the original fault information:
149
+ > [!IMPORTANT]
150
+ > Use `throw!` to propagate failures while preserving fault context and maintaining the error chain for debugging.
134
151
 
135
152
  ### Basic Propagation
136
153
 
137
154
  ```ruby
138
- class ProcessUserOrderTask < CMDx::Task
139
-
155
+ class OrderProcessor < CMDx::Task
140
156
  def call
141
- # Execute subtask and propagate its failure
142
- validation_result = ValidateUserOrderTask.call(context)
157
+ # Validate order data
158
+ validation_result = OrderValidator.call(context)
143
159
  throw!(validation_result) if validation_result.failed?
144
160
 
145
- payment_result = ProcessOrderPaymentTask.call(context)
146
- throw!(payment_result) # failed or skipped
161
+ # Process payment
162
+ payment_result = PaymentProcessor.call(context)
163
+ throw!(payment_result) if payment_result.failed?
147
164
 
148
- # Continue with main logic
149
- finalize_order
165
+ # Continue processing
166
+ complete_order
150
167
  end
151
-
152
168
  end
153
169
  ```
154
170
 
155
- ### Propagation with Additional Context
171
+ ### Propagation with Context
156
172
 
157
173
  ```ruby
158
- class ProcessOrderWorkflowTask < CMDx::Task
159
-
174
+ class WorkflowProcessor < CMDx::Task
160
175
  def call
161
- step1_result = ValidateOrderDataTask.call(context)
162
-
163
- if step1_result.failed?
164
- # Propagate with additional context
165
- throw!(step1_result, {
166
- workflow_stage: "initial_validation",
167
- attempted_at: Time.now,
168
- can_retry: true
176
+ step_result = DataValidation.call(context)
177
+
178
+ if step_result.failed?
179
+ throw!(step_result, {
180
+ workflow_stage: "validation",
181
+ can_retry: true,
182
+ next_step: "data_cleanup"
169
183
  })
170
184
  end
171
185
 
172
186
  continue_workflow
173
187
  end
174
-
175
188
  end
176
189
  ```
177
190
 
178
- > [!IMPORTANT]
179
- > Use `throw!` to propagate failures while preserving the original fault context. This maintains the fault chain for debugging and provides better error traceability.
180
-
181
- ## Fault Chain Analysis
191
+ ## Chain Analysis
182
192
 
183
- Results provide methods for analyzing fault propagation chains:
193
+ > [!NOTE]
194
+ > Results provide methods to analyze fault propagation and identify original failure sources in complex execution chains.
184
195
 
185
196
  ```ruby
186
- result = ProcessOrderWorkflowTask.call(data: invalid_data)
197
+ result = PaymentWorkflow.call(invalid_data)
187
198
 
188
199
  if result.failed?
189
- # Find the original cause of failure
190
- original_failure = result.caused_failure
191
- puts "Original failure: #{original_failure.task.class.name}"
192
- puts "Reason: #{original_failure.metadata[:reason]}"
193
-
194
- # Find what threw the failure to this result
195
- throwing_task = result.threw_failure
196
- puts "Failure thrown by: #{throwing_task.task.class.name}" if throwing_task
197
-
198
- # Check if this result caused or threw the failure
199
- if result.caused_failure?
200
- puts "This task was the original cause"
201
- elsif result.threw_failure?
202
- puts "This task threw a failure from another task"
203
- elsif result.thrown_failure?
204
- puts "This task failed due to a thrown failure"
200
+ # Trace the original failure
201
+ original = result.caused_failure
202
+ if original
203
+ puts "Original failure: #{original.task.class.name}"
204
+ puts "Reason: #{original.metadata[:reason]}"
205
+ end
206
+
207
+ # Find what propagated the failure
208
+ thrower = result.threw_failure
209
+ puts "Propagated by: #{thrower.task.class.name}" if thrower
210
+
211
+ # Analyze failure type
212
+ case
213
+ when result.caused_failure?
214
+ puts "This task was the original source"
215
+ when result.threw_failure?
216
+ puts "This task propagated a failure"
217
+ when result.thrown_failure?
218
+ puts "This task failed due to propagation"
205
219
  end
206
220
  end
207
221
  ```
208
222
 
209
- ## Task Halt Configuration
223
+ ## Configuration
210
224
 
211
- Control which statuses raise exceptions using the `task_halt` setting:
225
+ ### Task Halt Settings
226
+
227
+ Control which statuses raise exceptions using `task_halt`:
212
228
 
213
229
  ```ruby
214
- class ProcessUserOrderTask < CMDx::Task
215
- # Only failed tasks raise exceptions on call!
230
+ class DataProcessor < CMDx::Task
231
+ # Only failures raise exceptions
216
232
  cmd_settings!(task_halt: [CMDx::Result::FAILED])
217
233
 
218
234
  def call
219
- skip!(reason: "Order already processed") if already_processed?
220
- # This will NOT raise an exception on call!
235
+ skip!(reason: "No data to process") if data.empty?
236
+ # Skip will NOT raise exception on call!
221
237
  end
222
238
  end
223
239
 
224
- class ValidateUserDataTask < CMDx::Task
225
- # Both failed and skipped tasks raise exceptions
240
+ class CriticalValidator < CMDx::Task
241
+ # Both failures and skips raise exceptions
226
242
  cmd_settings!(task_halt: [CMDx::Result::FAILED, CMDx::Result::SKIPPED])
227
243
 
228
244
  def call
229
- skip!(reason: "Validation not required") if skip_validation?
230
- # This WILL raise an exception on call!
245
+ skip!(reason: "Validation bypassed") if bypass_mode?
246
+ # Skip WILL raise exception on call!
231
247
  end
232
248
  end
233
249
  ```
234
250
 
235
251
  > [!WARNING]
236
- > Task halt configuration only affects the `call!` method. The `call` method always captures all exceptions and converts them to result objects regardless of halt settings.
252
+ > Task halt configuration only affects the `call!` method. The `call` method always captures exceptions and converts them to result objects regardless of halt settings.
253
+
254
+ ### Global Configuration
255
+
256
+ ```ruby
257
+ # Configure default halt behavior
258
+ CMDx.configure do |config|
259
+ config.task_halt = [CMDx::Result::FAILED] # Default: only failures halt
260
+ end
261
+ ```
237
262
 
238
263
  ---
239
264