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.
- checksums.yaml +4 -4
- data/.cursor/prompts/docs.md +9 -0
- data/.cursor/prompts/rspec.md +13 -12
- data/.cursor/prompts/yardoc.md +11 -6
- data/CHANGELOG.md +13 -2
- data/README.md +1 -0
- data/docs/ai_prompts.md +269 -195
- data/docs/basics/call.md +124 -58
- data/docs/basics/chain.md +190 -160
- data/docs/basics/context.md +242 -154
- data/docs/basics/setup.md +302 -32
- data/docs/callbacks.md +390 -94
- data/docs/configuration.md +181 -65
- data/docs/deprecation.md +245 -0
- data/docs/getting_started.md +161 -39
- data/docs/internationalization.md +590 -70
- data/docs/interruptions/exceptions.md +135 -118
- data/docs/interruptions/faults.md +150 -125
- data/docs/interruptions/halt.md +134 -80
- data/docs/logging.md +181 -118
- data/docs/middlewares.md +150 -377
- data/docs/outcomes/result.md +140 -112
- data/docs/outcomes/states.md +134 -99
- data/docs/outcomes/statuses.md +204 -146
- data/docs/parameters/coercions.md +232 -281
- data/docs/parameters/defaults.md +224 -169
- data/docs/parameters/definitions.md +289 -141
- data/docs/parameters/namespacing.md +250 -161
- data/docs/parameters/validations.md +260 -133
- data/docs/testing.md +191 -197
- data/docs/workflows.md +143 -98
- data/lib/cmdx/callback.rb +23 -19
- data/lib/cmdx/callback_registry.rb +1 -3
- data/lib/cmdx/chain_inspector.rb +23 -23
- data/lib/cmdx/chain_serializer.rb +38 -19
- data/lib/cmdx/coercion.rb +20 -12
- data/lib/cmdx/coercion_registry.rb +51 -32
- data/lib/cmdx/configuration.rb +84 -31
- data/lib/cmdx/context.rb +32 -21
- data/lib/cmdx/core_ext/hash.rb +13 -13
- data/lib/cmdx/core_ext/module.rb +1 -1
- data/lib/cmdx/core_ext/object.rb +12 -12
- data/lib/cmdx/correlator.rb +60 -39
- data/lib/cmdx/errors.rb +105 -131
- data/lib/cmdx/fault.rb +66 -45
- data/lib/cmdx/immutator.rb +20 -21
- data/lib/cmdx/lazy_struct.rb +78 -70
- data/lib/cmdx/log_formatters/json.rb +1 -1
- data/lib/cmdx/log_formatters/key_value.rb +1 -1
- data/lib/cmdx/log_formatters/line.rb +1 -1
- data/lib/cmdx/log_formatters/logstash.rb +1 -1
- data/lib/cmdx/log_formatters/pretty_json.rb +1 -1
- data/lib/cmdx/log_formatters/pretty_key_value.rb +1 -1
- data/lib/cmdx/log_formatters/pretty_line.rb +1 -1
- data/lib/cmdx/log_formatters/raw.rb +2 -2
- data/lib/cmdx/logger.rb +19 -14
- data/lib/cmdx/logger_ansi.rb +33 -17
- data/lib/cmdx/logger_serializer.rb +85 -24
- data/lib/cmdx/middleware.rb +39 -21
- data/lib/cmdx/middleware_registry.rb +4 -3
- data/lib/cmdx/parameter.rb +151 -89
- data/lib/cmdx/parameter_inspector.rb +34 -21
- data/lib/cmdx/parameter_registry.rb +36 -30
- data/lib/cmdx/parameter_serializer.rb +21 -14
- data/lib/cmdx/result.rb +136 -135
- data/lib/cmdx/result_ansi.rb +31 -17
- data/lib/cmdx/result_inspector.rb +32 -27
- data/lib/cmdx/result_logger.rb +23 -14
- data/lib/cmdx/result_serializer.rb +65 -27
- data/lib/cmdx/task.rb +234 -113
- data/lib/cmdx/task_deprecator.rb +22 -25
- data/lib/cmdx/task_processor.rb +89 -88
- data/lib/cmdx/task_serializer.rb +27 -14
- data/lib/cmdx/utils/monotonic_runtime.rb +2 -4
- data/lib/cmdx/validator.rb +25 -16
- data/lib/cmdx/validator_registry.rb +53 -31
- data/lib/cmdx/validators/exclusion.rb +1 -1
- data/lib/cmdx/validators/format.rb +2 -2
- data/lib/cmdx/validators/inclusion.rb +2 -2
- data/lib/cmdx/validators/length.rb +2 -2
- data/lib/cmdx/validators/numeric.rb +3 -3
- data/lib/cmdx/validators/presence.rb +2 -2
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx/workflow.rb +54 -33
- data/lib/generators/cmdx/task_generator.rb +6 -6
- data/lib/generators/cmdx/workflow_generator.rb +6 -6
- metadata +3 -1
data/docs/basics/call.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Basics - Call
|
2
2
|
|
3
|
-
|
3
|
+
Task execution in CMDx provides two distinct methods that handle success and failure scenarios differently. Understanding when to use each method is crucial for proper error handling and control flow in your application workflows.
|
4
4
|
|
5
5
|
## Table of Contents
|
6
6
|
|
@@ -13,18 +13,38 @@ Calling a task executes the business logic within it. Tasks provide two executio
|
|
13
13
|
- [Result Propagation (`throw!`)](#result-propagation-throw)
|
14
14
|
- [Result Callbacks](#result-callbacks)
|
15
15
|
- [Task State Lifecycle](#task-state-lifecycle)
|
16
|
+
- [Error Handling](#error-handling)
|
16
17
|
- [Return Value Details](#return-value-details)
|
17
18
|
|
18
19
|
## TLDR
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
```ruby
|
22
|
+
# Standard execution (preferred)
|
23
|
+
result = ProcessOrderTask.call(order_id: 12345)
|
24
|
+
result.success? # → true/false
|
25
|
+
|
26
|
+
# Exception-based execution
|
27
|
+
begin
|
28
|
+
result = ProcessOrderTask.call!(order_id: 12345)
|
29
|
+
# Handle success
|
30
|
+
rescue CMDx::Failed => e
|
31
|
+
# Handle failure
|
32
|
+
end
|
33
|
+
|
34
|
+
# Result callbacks
|
35
|
+
ProcessOrderTask.call(order_id: 12345)
|
36
|
+
.on_success { |result| notify_customer(result) }
|
37
|
+
.on_failed { |result| handle_error(result) }
|
38
|
+
|
39
|
+
# Propagate failures
|
40
|
+
throw!(validation_result) if validation_result.failed?
|
41
|
+
```
|
25
42
|
|
26
43
|
## Execution Methods Overview
|
27
44
|
|
45
|
+
> [!NOTE]
|
46
|
+
> Tasks are single-use objects. Once executed, they are frozen and cannot be called again. Create a new instance for subsequent executions.
|
47
|
+
|
28
48
|
| Method | Returns | Exceptions | Use Case |
|
29
49
|
|--------|---------|------------|----------|
|
30
50
|
| `call` | Always returns `CMDx::Result` | Never raises | Predictable result handling |
|
@@ -38,15 +58,15 @@ The `call` method always returns a `CMDx::Result` object regardless of execution
|
|
38
58
|
result = ProcessOrderTask.call(order_id: 12345)
|
39
59
|
|
40
60
|
# Check execution state
|
41
|
-
result.success?
|
42
|
-
result.failed?
|
43
|
-
result.skipped?
|
61
|
+
result.success? # → true/false
|
62
|
+
result.failed? # → true/false
|
63
|
+
result.skipped? # → true/false
|
44
64
|
|
45
65
|
# Access result data
|
46
|
-
result.context.order_id
|
47
|
-
result.runtime
|
48
|
-
result.state
|
49
|
-
result.status
|
66
|
+
result.context.order_id # → 12345
|
67
|
+
result.runtime # → 0.05 (seconds)
|
68
|
+
result.state # → "complete"
|
69
|
+
result.status # → "success"
|
50
70
|
```
|
51
71
|
|
52
72
|
### Handling Different Outcomes
|
@@ -56,37 +76,38 @@ result = ProcessOrderTask.call(order_id: 12345)
|
|
56
76
|
|
57
77
|
case result.status
|
58
78
|
when "success"
|
59
|
-
|
79
|
+
SendConfirmationTask.call(result.context)
|
60
80
|
when "skipped"
|
61
|
-
|
81
|
+
Rails.logger.info("Order skipped: #{result.metadata[:reason]}")
|
62
82
|
when "failed"
|
63
|
-
|
83
|
+
RetryOrderJob.perform_later(result.context.order_id)
|
64
84
|
end
|
65
85
|
```
|
66
86
|
|
67
87
|
## Bang Call (`call!`)
|
68
88
|
|
69
|
-
The bang `call!` method raises a `CMDx::Fault` exception when tasks fail or are skipped
|
89
|
+
The bang `call!` method raises a `CMDx::Fault` exception when tasks fail or are skipped. It returns a `CMDx::Result` object only on success.
|
90
|
+
|
91
|
+
> [!WARNING]
|
92
|
+
> `call!` behavior depends on the `task_halt` configuration. By default, it raises exceptions for both failures and skips.
|
70
93
|
|
71
94
|
```ruby
|
72
95
|
begin
|
73
96
|
result = ProcessOrderTask.call!(order_id: 12345)
|
74
|
-
|
97
|
+
SendConfirmationTask.call(result.context)
|
75
98
|
rescue CMDx::Failed => e
|
76
|
-
# Handle failure
|
77
99
|
RetryOrderJob.perform_later(e.result.context.order_id)
|
78
100
|
rescue CMDx::Skipped => e
|
79
|
-
# Handle skip
|
80
101
|
Rails.logger.info("Order skipped: #{e.result.metadata[:reason]}")
|
81
102
|
end
|
82
103
|
```
|
83
104
|
|
84
105
|
### Exception Types
|
85
106
|
|
86
|
-
| Exception | Raised When |
|
87
|
-
|
88
|
-
| `CMDx::Failed` | Task execution fails |
|
89
|
-
| `CMDx::Skipped` | Task execution is skipped |
|
107
|
+
| Exception | Raised When | Access Result |
|
108
|
+
|-----------|-------------|---------------|
|
109
|
+
| `CMDx::Failed` | Task execution fails | `exception.result` |
|
110
|
+
| `CMDx::Skipped` | Task execution is skipped | `exception.result` |
|
90
111
|
|
91
112
|
## Direct Instantiation
|
92
113
|
|
@@ -97,14 +118,14 @@ Tasks can be instantiated directly for advanced use cases, testing, and custom e
|
|
97
118
|
task = ProcessOrderTask.new(order_id: 12345, notify_customer: true)
|
98
119
|
|
99
120
|
# Access properties before execution
|
100
|
-
task.id
|
101
|
-
task.context.order_id
|
102
|
-
task.context.notify_customer
|
103
|
-
task.result.state
|
121
|
+
task.id # → "abc123..." (unique task ID)
|
122
|
+
task.context.order_id # → 12345
|
123
|
+
task.context.notify_customer # → true
|
124
|
+
task.result.state # → "initialized"
|
104
125
|
|
105
126
|
# Manual execution
|
106
127
|
task.process
|
107
|
-
task.result.success?
|
128
|
+
task.result.success? # → true/false
|
108
129
|
```
|
109
130
|
|
110
131
|
### Execution Approaches
|
@@ -115,9 +136,6 @@ task.result.success? #=> true/false
|
|
115
136
|
| `TaskClass.call!(...)` | Exception-based flow | Automatic fault raising |
|
116
137
|
| `TaskClass.new(...).process` | Advanced scenarios | Full control, testing flexibility |
|
117
138
|
|
118
|
-
> [!NOTE]
|
119
|
-
> Direct instantiation gives you access to the task instance before and after execution, but you must call the execution method manually.
|
120
|
-
|
121
139
|
## Parameter Passing
|
122
140
|
|
123
141
|
All methods accept parameters that become available in the task context:
|
@@ -133,31 +151,36 @@ result = ProcessOrderTask.call(
|
|
133
151
|
# From another task result
|
134
152
|
validation_result = ValidateOrderTask.call(order_id: 12345)
|
135
153
|
|
136
|
-
#
|
137
|
-
result = ProcessOrderTask.call
|
154
|
+
# Pass Result object directly
|
155
|
+
result = ProcessOrderTask.call(validation_result)
|
138
156
|
|
139
|
-
#
|
140
|
-
result = ProcessOrderTask.
|
157
|
+
# Pass context from previous result
|
158
|
+
result = ProcessOrderTask.call(validation_result.context)
|
141
159
|
```
|
142
160
|
|
143
161
|
## Result Propagation (`throw!`)
|
144
162
|
|
145
163
|
The `throw!` method enables result propagation, allowing tasks to bubble up failures from subtasks while preserving the original fault information:
|
146
164
|
|
165
|
+
> [!IMPORTANT]
|
166
|
+
> Use `throw!` to maintain failure context and prevent nested error handling in complex workflows.
|
167
|
+
|
147
168
|
```ruby
|
148
169
|
class ProcessOrderTask < CMDx::Task
|
149
170
|
def call
|
171
|
+
# Validate order
|
150
172
|
validation_result = ValidateOrderTask.call(context)
|
151
173
|
throw!(validation_result) if validation_result.failed?
|
152
174
|
|
175
|
+
# Process payment
|
153
176
|
payment_result = ProcessPaymentTask.call(context)
|
154
|
-
throw!(payment_result) if payment_result.
|
177
|
+
throw!(payment_result) if payment_result.failed?
|
155
178
|
|
179
|
+
# Schedule delivery
|
156
180
|
delivery_result = ScheduleDeliveryTask.call(context)
|
157
|
-
throw!(delivery_result)
|
181
|
+
throw!(delivery_result) unless delivery_result.success?
|
158
182
|
|
159
183
|
# Continue with main logic
|
160
|
-
context.order = Order.find(context.order_id)
|
161
184
|
finalize_order_processing
|
162
185
|
end
|
163
186
|
end
|
@@ -174,15 +197,18 @@ ProcessOrderTask
|
|
174
197
|
SendOrderConfirmationTask.call(result.context)
|
175
198
|
}
|
176
199
|
.on_failed { |result|
|
177
|
-
|
200
|
+
ErrorReportingService.notify(result.metadata[:error])
|
178
201
|
}
|
179
202
|
.on_executed { |result|
|
180
|
-
|
203
|
+
MetricsService.timing('order.processing_time', result.runtime)
|
181
204
|
}
|
182
205
|
```
|
183
206
|
|
184
207
|
### Available Callbacks
|
185
208
|
|
209
|
+
> [!TIP]
|
210
|
+
> Callbacks return the result object, enabling method chaining for complex conditional logic.
|
211
|
+
|
186
212
|
```ruby
|
187
213
|
result = ProcessOrderTask.call(order_id: 12345)
|
188
214
|
|
@@ -200,8 +226,8 @@ result
|
|
200
226
|
|
201
227
|
# Outcome-based callbacks
|
202
228
|
result
|
203
|
-
.on_good { |r| log_positive_outcome(r) }
|
204
|
-
.on_bad { |r| log_negative_outcome(r) }
|
229
|
+
.on_good { |r| log_positive_outcome(r) } # success or skipped
|
230
|
+
.on_bad { |r| log_negative_outcome(r) } # failed only
|
205
231
|
```
|
206
232
|
|
207
233
|
## Task State Lifecycle
|
@@ -212,10 +238,52 @@ Tasks progress through defined states during execution:
|
|
212
238
|
result = ProcessOrderTask.call(order_id: 12345)
|
213
239
|
|
214
240
|
# Execution states
|
215
|
-
result.state
|
241
|
+
result.state # → "initialized" → "executing" → "complete"/"interrupted"
|
216
242
|
|
217
243
|
# Outcome statuses
|
218
|
-
result.status
|
244
|
+
result.status # → "success"/"failed"/"skipped"
|
245
|
+
```
|
246
|
+
|
247
|
+
## Error Handling
|
248
|
+
|
249
|
+
### Common Error Scenarios
|
250
|
+
|
251
|
+
```ruby
|
252
|
+
# Parameter validation failure
|
253
|
+
result = ProcessOrderTask.call(order_id: nil)
|
254
|
+
result.failed? # → true
|
255
|
+
result.metadata[:reason] # → "order_id is required"
|
256
|
+
|
257
|
+
# Business logic failure
|
258
|
+
result = ProcessOrderTask.call(order_id: 99999)
|
259
|
+
result.failed? # → true
|
260
|
+
result.metadata[:error].class # → ActiveRecord::RecordNotFound
|
261
|
+
|
262
|
+
# Task skipped due to conditions
|
263
|
+
result = ProcessOrderTask.call(order_id: 12345, force: false)
|
264
|
+
result.skipped? # → true (if order already processed)
|
265
|
+
result.metadata[:reason] # → "Order already processed"
|
266
|
+
```
|
267
|
+
|
268
|
+
### Exception Handling with `call!`
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
begin
|
272
|
+
result = ProcessOrderTask.call!(order_id: 12345)
|
273
|
+
rescue CMDx::Failed => e
|
274
|
+
# Access original error details
|
275
|
+
error_type = e.result.metadata[:error].class
|
276
|
+
error_message = e.result.metadata[:reason]
|
277
|
+
|
278
|
+
case error_type
|
279
|
+
when ActiveRecord::RecordNotFound
|
280
|
+
render json: { error: "Order not found" }, status: 404
|
281
|
+
when PaymentError
|
282
|
+
render json: { error: "Payment failed" }, status: 402
|
283
|
+
else
|
284
|
+
render json: { error: "Processing failed" }, status: 500
|
285
|
+
end
|
286
|
+
end
|
219
287
|
```
|
220
288
|
|
221
289
|
## Return Value Details
|
@@ -226,25 +294,23 @@ The `Result` object provides comprehensive execution information:
|
|
226
294
|
result = ProcessOrderTask.call(order_id: 12345)
|
227
295
|
|
228
296
|
# Execution metadata
|
229
|
-
result.id
|
230
|
-
result.runtime
|
231
|
-
result.task
|
232
|
-
result.chain
|
297
|
+
result.id # → "abc123..." (unique execution ID)
|
298
|
+
result.runtime # → 0.05 (execution time in seconds)
|
299
|
+
result.task # → ProcessOrderTask instance
|
300
|
+
result.chain # → Chain object for tracking executions
|
233
301
|
|
234
302
|
# Context and metadata
|
235
|
-
result.context
|
236
|
-
result.metadata
|
303
|
+
result.context # → Context with all task data
|
304
|
+
result.metadata # → Hash with execution metadata
|
237
305
|
|
238
306
|
# State checking methods
|
239
|
-
result.good?
|
240
|
-
result.bad?
|
241
|
-
result.complete?
|
242
|
-
result.interrupted?
|
307
|
+
result.good? # → true for success/skipped
|
308
|
+
result.bad? # → true for failed only
|
309
|
+
result.complete? # → true when execution finished normally
|
310
|
+
result.interrupted? # → true for failed/skipped
|
311
|
+
result.executed? # → true for any completed execution
|
243
312
|
```
|
244
313
|
|
245
|
-
> [!IMPORTANT]
|
246
|
-
> Tasks are single-use objects. Once executed, they are frozen and cannot be called again. Create a new task instance to execute the same task again.
|
247
|
-
|
248
314
|
---
|
249
315
|
|
250
316
|
- **Prev:** [Basics - Setup](setup.md)
|