cmdx 0.5.0 → 1.0.0

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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/rules/cursor-instructions.mdc +6 -0
  4. data/.rubocop.yml +16 -1
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +31 -1
  7. data/README.md +72 -25
  8. data/docs/ai_prompts.md +309 -0
  9. data/docs/basics/call.md +225 -14
  10. data/docs/basics/chain.md +271 -0
  11. data/docs/basics/context.md +232 -33
  12. data/docs/basics/setup.md +76 -12
  13. data/docs/callbacks.md +273 -0
  14. data/docs/configuration.md +158 -28
  15. data/docs/getting_started.md +134 -22
  16. data/docs/interruptions/exceptions.md +189 -11
  17. data/docs/interruptions/faults.md +187 -44
  18. data/docs/interruptions/halt.md +179 -35
  19. data/docs/logging.md +194 -53
  20. data/docs/middlewares.md +735 -0
  21. data/docs/outcomes/result.md +296 -10
  22. data/docs/outcomes/states.md +203 -31
  23. data/docs/outcomes/statuses.md +275 -30
  24. data/docs/parameters/coercions.md +402 -29
  25. data/docs/parameters/defaults.md +249 -25
  26. data/docs/parameters/definitions.md +238 -72
  27. data/docs/parameters/namespacing.md +250 -27
  28. data/docs/parameters/validations.md +193 -168
  29. data/docs/testing.md +550 -0
  30. data/docs/tips_and_tricks.md +95 -43
  31. data/docs/workflows.md +319 -0
  32. data/lib/cmdx/.DS_Store +0 -0
  33. data/lib/cmdx/callback.rb +69 -0
  34. data/lib/cmdx/callback_registry.rb +106 -0
  35. data/lib/cmdx/chain.rb +190 -0
  36. data/lib/cmdx/chain_inspector.rb +149 -0
  37. data/lib/cmdx/chain_serializer.rb +175 -0
  38. data/lib/cmdx/coercions/array.rb +37 -0
  39. data/lib/cmdx/coercions/big_decimal.rb +33 -0
  40. data/lib/cmdx/coercions/boolean.rb +41 -1
  41. data/lib/cmdx/coercions/complex.rb +31 -0
  42. data/lib/cmdx/coercions/date.rb +39 -0
  43. data/lib/cmdx/coercions/date_time.rb +39 -0
  44. data/lib/cmdx/coercions/float.rb +31 -0
  45. data/lib/cmdx/coercions/hash.rb +42 -0
  46. data/lib/cmdx/coercions/integer.rb +32 -0
  47. data/lib/cmdx/coercions/rational.rb +31 -0
  48. data/lib/cmdx/coercions/string.rb +31 -0
  49. data/lib/cmdx/coercions/time.rb +39 -0
  50. data/lib/cmdx/coercions/virtual.rb +31 -0
  51. data/lib/cmdx/configuration.rb +217 -9
  52. data/lib/cmdx/context.rb +173 -2
  53. data/lib/cmdx/core_ext/hash.rb +72 -0
  54. data/lib/cmdx/core_ext/module.rb +94 -0
  55. data/lib/cmdx/core_ext/object.rb +105 -0
  56. data/lib/cmdx/correlator.rb +217 -0
  57. data/lib/cmdx/error.rb +210 -8
  58. data/lib/cmdx/errors.rb +256 -1
  59. data/lib/cmdx/fault.rb +177 -2
  60. data/lib/cmdx/faults.rb +158 -2
  61. data/lib/cmdx/immutator.rb +121 -2
  62. data/lib/cmdx/lazy_struct.rb +261 -18
  63. data/lib/cmdx/log_formatters/json.rb +46 -0
  64. data/lib/cmdx/log_formatters/key_value.rb +46 -0
  65. data/lib/cmdx/log_formatters/line.rb +54 -0
  66. data/lib/cmdx/log_formatters/logstash.rb +64 -0
  67. data/lib/cmdx/log_formatters/pretty_json.rb +57 -0
  68. data/lib/cmdx/log_formatters/pretty_key_value.rb +51 -0
  69. data/lib/cmdx/log_formatters/pretty_line.rb +60 -0
  70. data/lib/cmdx/log_formatters/raw.rb +54 -0
  71. data/lib/cmdx/logger.rb +85 -0
  72. data/lib/cmdx/logger_ansi.rb +93 -7
  73. data/lib/cmdx/logger_serializer.rb +116 -0
  74. data/lib/cmdx/middleware.rb +74 -0
  75. data/lib/cmdx/middleware_registry.rb +106 -0
  76. data/lib/cmdx/middlewares/correlate.rb +266 -0
  77. data/lib/cmdx/middlewares/timeout.rb +232 -0
  78. data/lib/cmdx/parameter.rb +228 -1
  79. data/lib/cmdx/parameter_inspector.rb +61 -0
  80. data/lib/cmdx/parameter_registry.rb +125 -0
  81. data/lib/cmdx/parameter_serializer.rb +83 -0
  82. data/lib/cmdx/parameter_validator.rb +62 -0
  83. data/lib/cmdx/parameter_value.rb +109 -1
  84. data/lib/cmdx/parameters_inspector.rb +59 -0
  85. data/lib/cmdx/parameters_serializer.rb +102 -0
  86. data/lib/cmdx/railtie.rb +123 -3
  87. data/lib/cmdx/result.rb +367 -25
  88. data/lib/cmdx/result_ansi.rb +105 -9
  89. data/lib/cmdx/result_inspector.rb +76 -0
  90. data/lib/cmdx/result_logger.rb +90 -3
  91. data/lib/cmdx/result_serializer.rb +137 -0
  92. data/lib/cmdx/rspec/result_matchers.rb +917 -0
  93. data/lib/cmdx/rspec/task_matchers.rb +570 -0
  94. data/lib/cmdx/task.rb +405 -37
  95. data/lib/cmdx/task_serializer.rb +74 -2
  96. data/lib/cmdx/utils/ansi_color.rb +95 -0
  97. data/lib/cmdx/utils/log_timestamp.rb +48 -0
  98. data/lib/cmdx/utils/monotonic_runtime.rb +71 -4
  99. data/lib/cmdx/utils/name_affix.rb +78 -0
  100. data/lib/cmdx/validators/custom.rb +82 -0
  101. data/lib/cmdx/validators/exclusion.rb +94 -0
  102. data/lib/cmdx/validators/format.rb +102 -8
  103. data/lib/cmdx/validators/inclusion.rb +104 -0
  104. data/lib/cmdx/validators/length.rb +128 -0
  105. data/lib/cmdx/validators/numeric.rb +128 -0
  106. data/lib/cmdx/validators/presence.rb +93 -7
  107. data/lib/cmdx/version.rb +7 -1
  108. data/lib/cmdx/workflow.rb +394 -0
  109. data/lib/cmdx.rb +25 -64
  110. data/lib/generators/cmdx/install_generator.rb +37 -1
  111. data/lib/generators/cmdx/task_generator.rb +69 -1
  112. data/lib/generators/cmdx/templates/install.rb +8 -12
  113. data/lib/generators/cmdx/workflow_generator.rb +109 -0
  114. metadata +54 -15
  115. data/docs/basics/run.md +0 -34
  116. data/docs/batch.md +0 -53
  117. data/docs/example.md +0 -82
  118. data/docs/hooks.md +0 -62
  119. data/lib/cmdx/batch.rb +0 -43
  120. data/lib/cmdx/parameters.rb +0 -35
  121. data/lib/cmdx/run.rb +0 -39
  122. data/lib/cmdx/run_inspector.rb +0 -26
  123. data/lib/cmdx/run_serializer.rb +0 -20
  124. data/lib/cmdx/task_hook.rb +0 -18
  125. data/lib/generators/cmdx/batch_generator.rb +0 -30
  126. /data/lib/generators/cmdx/templates/{batch.rb.tt → workflow.rb.tt} +0 -0
@@ -1,54 +1,299 @@
1
- # Outcomes - Statuses
1
+ # Outcomes - Statuses
2
2
 
3
- Status represents the state of the task logic executed after its called.
4
- A status of `success` is returned even if the task has **NOT** been executed.
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.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Status Definitions](#status-definitions)
8
+ - [Status Characteristics](#status-characteristics)
9
+ - [Status Predicates](#status-predicates)
10
+ - [Status Transitions](#status-transitions)
11
+ - [Status-Based Callbacks](#status-based-callbacks)
12
+ - [Status Metadata](#status-metadata)
13
+ - [Outcome-Based Logic](#outcome-based-logic)
14
+ - [Status Serialization and Inspection](#status-serialization-and-inspection)
15
+ - [Status vs State vs Outcome](#status-vs-state-vs-outcome)
16
+
17
+ ## Status Definitions
5
18
 
6
19
  | Status | Description |
7
20
  | --------- | ----------- |
8
- | `success` | Call execution completed without fault/exception. |
9
- | `skipped` | Task stopped completion of call execution early where proceeding is pointless. |
10
- | `failed` | Task stopped completion of call execution due to an unsatisfied/invalid condition or a `StandardError`. |
21
+ | `success` | Task execution completed successfully with expected business outcome |
22
+ | `skipped` | Task intentionally stopped execution because conditions weren't met or continuation was unnecessary |
23
+ | `failed` | Task stopped execution due to business rule violations, validation errors, or exceptions |
11
24
 
12
25
  > [!NOTE]
13
- > Statuses (except success) are paired with halt methods used to stop call execution.
26
+ > 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.
27
+
28
+ ## Status Characteristics
29
+
30
+ ### Success
31
+ - **Default status** for all newly created tasks
32
+ - Indicates business logic completed as expected
33
+ - Remains even if no actual execution occurred (e.g., cached results)
34
+ - Compatible with both `complete` and `interrupted` states
35
+
36
+ ### Skipped
37
+ - Indicates intentional early termination
38
+ - Business logic determined execution was unnecessary
39
+ - Often used for conditional workflows and guard clauses
40
+ - Triggered by `skip!` method with contextual metadata
41
+
42
+ ### Failed
43
+ - Indicates business logic could not complete successfully
44
+ - Can result from explicit failures or caught exceptions
45
+ - Contains detailed error information in metadata
46
+ - Triggered by `fail!` method or automatic exception handling
47
+
48
+ ## Status Predicates
49
+
50
+ Use status predicates to check execution outcomes:
14
51
 
15
52
  ```ruby
16
- result = ProcessOrderTask.call
17
- result.status #=> "skipped"
53
+ result = ProcessUserOrderTask.call
18
54
 
19
- result.success? #=> false
20
- result.skipped? #=> true
21
- result.failed? #=> false
55
+ # Individual status checks
56
+ result.success? #=> true/false
57
+ result.skipped? #=> true/false
58
+ result.failed? #=> true/false
22
59
 
23
- # `success` or `skipped`
24
- result.good? #=> true
60
+ # Outcome categorization
61
+ result.good? #=> true if success OR skipped
62
+ result.bad? #=> true if skipped OR failed (not success)
63
+ ```
64
+
65
+ ## Status Transitions
66
+
67
+ Unlike states, statuses can only transition from success to skipped/failed:
68
+
69
+ ```ruby
70
+ # Valid status transitions
71
+ success -> skipped # (via skip!)
72
+ success -> failed # (via fail! or exception)
25
73
 
26
- # `skipped` or `failed`
27
- result.bad? #=> true
74
+ # Invalid transitions (will raise errors)
75
+ skipped -> success # ❌ Cannot transition
76
+ skipped -> failed # ❌ Cannot transition
77
+ failed -> success # ❌ Cannot transition
78
+ failed -> skipped # ❌ Cannot transition
28
79
  ```
29
80
 
30
- ## Handlers
81
+ > [!IMPORTANT]
82
+ > 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.
31
83
 
32
- Results can be used to trigger status based callbacks. Handlers require a block
33
- and will have the result available as local variable. Callback handlers can be
34
- chained and repeated.
84
+ ### Status Transition Examples
35
85
 
36
86
  ```ruby
37
- result = ProcessOrderTask.call
38
- result.on_success { do_work }
87
+ class ProcessUserOrderTask < CMDx::Task
88
+ def call
89
+ # Task starts with success status
90
+ context.result.success? #=> true
39
91
 
40
- # - or -
92
+ # Conditional skip
93
+ if context.order.already_processed?
94
+ skip!(reason: "Order already processed")
95
+ # Status is now skipped, execution halts
96
+ end
41
97
 
42
- ProcessOrderTask
43
- .call(...)
44
- .on_success { do_work }
45
- .on_bad { |result| $statsd.increment(result.state) }
98
+ # Conditional failure
99
+ unless context.user.has_permission?
100
+ fail!(reason: "Insufficient permissions")
101
+ # Status is now failed, execution halts
102
+ end
103
+
104
+ # Continue with business logic
105
+ process_order
106
+ # Status remains success
107
+ end
108
+ end
109
+ ```
110
+
111
+ ## Status-Based Callbacks
112
+
113
+ Results provide comprehensive callback methods for status-based logic:
114
+
115
+ ```ruby
116
+ result = ProcessUserOrderTask.call
117
+
118
+ # Individual status callbacks
119
+ result
120
+ .on_success { |r| handle_success(r) }
121
+ .on_skipped { |r| handle_skip(r) }
122
+ .on_failed { |r| handle_failure(r) }
123
+
124
+ # Outcome-based callbacks
125
+ result
126
+ .on_good { |r| log_positive_outcome(r) }
127
+ .on_bad { |r| log_negative_outcome(r) }
46
128
  ```
47
129
 
48
130
  > [!TIP]
49
- > Handlers help execute you logical branches without `if/else` blocks.
131
+ > 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.
132
+
133
+ ## Status Metadata
134
+
135
+ Statuses carry rich metadata providing context about execution outcomes:
136
+
137
+ ### Success Metadata
138
+
139
+ ```ruby
140
+ class ProcessUserOrderTask < CMDx::Task
141
+ def call
142
+ # Success metadata can include business context
143
+ context.order = Order.find(context.order_id)
144
+ context.order.process!
145
+
146
+ # Success status typically has empty metadata
147
+ # but can include business-relevant information
148
+ context.processing_time = Time.now - context.start_time
149
+ context.confirmation_number = generate_confirmation
150
+ end
151
+ end
152
+
153
+ result = ProcessUserOrderTask.call(order_id: 123)
154
+ result.success? #=> true
155
+ result.metadata #=> {} (usually empty for success)
156
+ ```
157
+
158
+ ### Skip Metadata
159
+
160
+ ```ruby
161
+ class ProcessUserOrderTask < CMDx::Task
162
+ def call
163
+ order = Order.find(context.order_id)
164
+
165
+ if order.already_processed?
166
+ skip!(
167
+ reason: "Order already processed",
168
+ processed_at: order.processed_at,
169
+ original_processor: order.processor_id,
170
+ skip_code: "DUPLICATE_PROCESSING"
171
+ )
172
+ end
173
+
174
+ # Continue processing...
175
+ end
176
+ end
177
+
178
+ result = ProcessUserOrderTask.call(order_id: 123)
179
+ if result.skipped?
180
+ result.metadata[:reason] #=> "Order already processed"
181
+ result.metadata[:processed_at] #=> 2023-10-01 10:30:00 UTC
182
+ result.metadata[:original_processor] #=> "user-456"
183
+ result.metadata[:skip_code] #=> "DUPLICATE_PROCESSING"
184
+ end
185
+ ```
186
+
187
+ ### Failure Metadata
188
+
189
+ ```ruby
190
+ class ValidateOrderDataTask < CMDx::Task
191
+ def call
192
+ unless context.order.valid?
193
+ fail!(
194
+ reason: "Order validation failed",
195
+ errors: context.order.errors.full_messages,
196
+ error_code: "VALIDATION_FAILED",
197
+ retryable: false,
198
+ failed_at: Time.now
199
+ )
200
+ end
201
+ end
202
+ end
203
+
204
+ result = ValidateOrderDataTask.call(order_id: 123)
205
+ if result.failed?
206
+ result.metadata[:reason] #=> "Order validation failed"
207
+ result.metadata[:errors] #=> ["Name can't be blank", "Email is invalid"]
208
+ result.metadata[:error_code] #=> "VALIDATION_FAILED"
209
+ result.metadata[:retryable] #=> false
210
+ result.metadata[:failed_at] #=> 2023-10-01 10:30:00 UTC
211
+ end
212
+ ```
213
+
214
+ > [!TIP]
215
+ > Always try to include rich metadata with skip and fail operations. This information is invaluable for debugging, user feedback, and automated error handling.
216
+
217
+ ## Outcome-Based Logic
218
+
219
+ Statuses enable sophisticated outcome-based decision making:
220
+
221
+ ### Good vs Bad Outcomes
222
+
223
+ ```ruby
224
+ # Good outcomes (success OR skipped)
225
+ result.good? #=> true if success? || skipped?
226
+ result.bad? #=> true if !success? (skipped OR failed)
227
+
228
+ # Usage patterns
229
+ if result.good?
230
+ # Both success and skipped are "good" outcomes
231
+ update_user_interface(result)
232
+ log_completed_action(result)
233
+ end
234
+
235
+ if result.bad?
236
+ # Handle any non-success outcome (skipped or failed)
237
+ show_error_message(result.metadata[:reason])
238
+ track_negative_outcome(result)
239
+ end
240
+ ```
241
+
242
+ ## Status Serialization and Inspection
243
+
244
+ Statuses are fully captured in result serialization:
245
+
246
+ ```ruby
247
+ result = ProcessUserOrderTask.call
248
+
249
+ # Hash representation
250
+ result.to_h[:status] #=> "success"
251
+
252
+ # Full serialization includes status
253
+ result.to_h
254
+ #=> {
255
+ # class: "ProcessUserOrderTask",
256
+ # index: 0,
257
+ # state: "complete",
258
+ # status: "success",
259
+ # outcome: "success",
260
+ # metadata: {},
261
+ # # ... other attributes
262
+ # }
263
+
264
+ # Human-readable inspection
265
+ result.to_s
266
+ #=> "ProcessUserOrderTask: type=Task index=0 state=complete status=success outcome=success..."
267
+ ```
268
+
269
+ ## Status vs State vs Outcome
270
+
271
+ Understanding the relationship between these concepts:
272
+
273
+ - **Status**: Business execution outcome (`success`, `skipped`, `failed`)
274
+ - **State**: Technical execution lifecycle (`initialized`, `executing`, `complete`, `interrupted`)
275
+ - **Outcome**: Combined representation for unified logic
276
+
277
+ ```ruby
278
+ result = ProcessUserOrderTask.call
279
+
280
+ # Different scenarios
281
+ result.state #=> "complete"
282
+ result.status #=> "success"
283
+ result.outcome #=> "success" (same as status when complete)
284
+
285
+ # Skipped task
286
+ skipped_result.state #=> "complete" (execution finished)
287
+ skipped_result.status #=> "skipped" (business outcome)
288
+ skipped_result.outcome #=> "skipped" (same as status)
289
+
290
+ # Failed task
291
+ failed_result.state #=> "interrupted" (execution stopped)
292
+ failed_result.status #=> "failed" (business outcome)
293
+ failed_result.outcome #=> "interrupted" (reflects state for interrupted tasks)
294
+ ```
50
295
 
51
296
  ---
52
297
 
53
- - **Prev:** [Outcomes - Result](https://github.com/drexed/cmdx/blob/main/docs/outcomes/result.md)
54
- - **Next:** [Outcomes - States](https://github.com/drexed/cmdx/blob/main/docs/outcomes/states.md)
298
+ - **Prev:** [Outcomes - Result](result.md)
299
+ - **Next:** [Outcomes - States](states.md)