cmdx 0.5.0 → 1.0.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 (151) 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 +19 -1
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +95 -28
  7. data/README.md +73 -25
  8. data/docs/ai_prompts.md +319 -0
  9. data/docs/basics/call.md +234 -14
  10. data/docs/basics/chain.md +280 -0
  11. data/docs/basics/context.md +241 -33
  12. data/docs/basics/setup.md +85 -12
  13. data/docs/callbacks.md +283 -0
  14. data/docs/configuration.md +155 -30
  15. data/docs/getting_started.md +145 -22
  16. data/docs/internationalization.md +148 -0
  17. data/docs/interruptions/exceptions.md +198 -11
  18. data/docs/interruptions/faults.md +196 -44
  19. data/docs/interruptions/halt.md +188 -35
  20. data/docs/logging.md +204 -53
  21. data/docs/middlewares.md +745 -0
  22. data/docs/outcomes/result.md +305 -10
  23. data/docs/outcomes/states.md +212 -31
  24. data/docs/outcomes/statuses.md +284 -30
  25. data/docs/parameters/coercions.md +411 -29
  26. data/docs/parameters/defaults.md +258 -25
  27. data/docs/parameters/definitions.md +247 -72
  28. data/docs/parameters/namespacing.md +259 -27
  29. data/docs/parameters/validations.md +173 -168
  30. data/docs/testing.md +560 -0
  31. data/docs/tips_and_tricks.md +103 -42
  32. data/docs/workflows.md +329 -0
  33. data/lib/cmdx/.DS_Store +0 -0
  34. data/lib/cmdx/callback.rb +69 -0
  35. data/lib/cmdx/callback_registry.rb +106 -0
  36. data/lib/cmdx/chain.rb +190 -0
  37. data/lib/cmdx/chain_inspector.rb +149 -0
  38. data/lib/cmdx/chain_serializer.rb +175 -0
  39. data/lib/cmdx/coercions/array.rb +37 -0
  40. data/lib/cmdx/coercions/big_decimal.rb +33 -0
  41. data/lib/cmdx/coercions/boolean.rb +41 -1
  42. data/lib/cmdx/coercions/complex.rb +31 -0
  43. data/lib/cmdx/coercions/date.rb +39 -0
  44. data/lib/cmdx/coercions/date_time.rb +39 -0
  45. data/lib/cmdx/coercions/float.rb +31 -0
  46. data/lib/cmdx/coercions/hash.rb +42 -0
  47. data/lib/cmdx/coercions/integer.rb +32 -0
  48. data/lib/cmdx/coercions/rational.rb +31 -0
  49. data/lib/cmdx/coercions/string.rb +31 -0
  50. data/lib/cmdx/coercions/time.rb +39 -0
  51. data/lib/cmdx/coercions/virtual.rb +31 -0
  52. data/lib/cmdx/configuration.rb +217 -9
  53. data/lib/cmdx/context.rb +173 -2
  54. data/lib/cmdx/core_ext/hash.rb +72 -0
  55. data/lib/cmdx/core_ext/module.rb +94 -0
  56. data/lib/cmdx/core_ext/object.rb +105 -0
  57. data/lib/cmdx/correlator.rb +217 -0
  58. data/lib/cmdx/error.rb +210 -8
  59. data/lib/cmdx/errors.rb +256 -1
  60. data/lib/cmdx/fault.rb +177 -2
  61. data/lib/cmdx/faults.rb +158 -2
  62. data/lib/cmdx/immutator.rb +121 -2
  63. data/lib/cmdx/lazy_struct.rb +261 -18
  64. data/lib/cmdx/log_formatters/json.rb +46 -0
  65. data/lib/cmdx/log_formatters/key_value.rb +46 -0
  66. data/lib/cmdx/log_formatters/line.rb +54 -0
  67. data/lib/cmdx/log_formatters/logstash.rb +64 -0
  68. data/lib/cmdx/log_formatters/pretty_json.rb +57 -0
  69. data/lib/cmdx/log_formatters/pretty_key_value.rb +51 -0
  70. data/lib/cmdx/log_formatters/pretty_line.rb +60 -0
  71. data/lib/cmdx/log_formatters/raw.rb +54 -0
  72. data/lib/cmdx/logger.rb +85 -0
  73. data/lib/cmdx/logger_ansi.rb +93 -7
  74. data/lib/cmdx/logger_serializer.rb +116 -0
  75. data/lib/cmdx/middleware.rb +74 -0
  76. data/lib/cmdx/middleware_registry.rb +106 -0
  77. data/lib/cmdx/middlewares/correlate.rb +266 -0
  78. data/lib/cmdx/middlewares/timeout.rb +232 -0
  79. data/lib/cmdx/parameter.rb +228 -1
  80. data/lib/cmdx/parameter_inspector.rb +61 -0
  81. data/lib/cmdx/parameter_registry.rb +125 -0
  82. data/lib/cmdx/parameter_serializer.rb +83 -0
  83. data/lib/cmdx/parameter_validator.rb +62 -0
  84. data/lib/cmdx/parameter_value.rb +109 -1
  85. data/lib/cmdx/parameters_inspector.rb +59 -0
  86. data/lib/cmdx/parameters_serializer.rb +102 -0
  87. data/lib/cmdx/railtie.rb +123 -3
  88. data/lib/cmdx/result.rb +367 -25
  89. data/lib/cmdx/result_ansi.rb +105 -9
  90. data/lib/cmdx/result_inspector.rb +76 -0
  91. data/lib/cmdx/result_logger.rb +90 -3
  92. data/lib/cmdx/result_serializer.rb +137 -0
  93. data/lib/cmdx/rspec/result_matchers.rb +917 -0
  94. data/lib/cmdx/rspec/task_matchers.rb +570 -0
  95. data/lib/cmdx/task.rb +405 -37
  96. data/lib/cmdx/task_serializer.rb +74 -2
  97. data/lib/cmdx/utils/ansi_color.rb +95 -0
  98. data/lib/cmdx/utils/log_timestamp.rb +48 -0
  99. data/lib/cmdx/utils/monotonic_runtime.rb +71 -4
  100. data/lib/cmdx/utils/name_affix.rb +78 -0
  101. data/lib/cmdx/validators/custom.rb +82 -0
  102. data/lib/cmdx/validators/exclusion.rb +94 -0
  103. data/lib/cmdx/validators/format.rb +102 -8
  104. data/lib/cmdx/validators/inclusion.rb +104 -0
  105. data/lib/cmdx/validators/length.rb +128 -0
  106. data/lib/cmdx/validators/numeric.rb +128 -0
  107. data/lib/cmdx/validators/presence.rb +93 -7
  108. data/lib/cmdx/version.rb +7 -1
  109. data/lib/cmdx/workflow.rb +394 -0
  110. data/lib/cmdx.rb +25 -64
  111. data/lib/generators/cmdx/install_generator.rb +37 -1
  112. data/lib/generators/cmdx/task_generator.rb +69 -1
  113. data/lib/generators/cmdx/templates/install.rb +43 -15
  114. data/lib/generators/cmdx/workflow_generator.rb +109 -0
  115. data/lib/locales/ar.yml +36 -0
  116. data/lib/locales/cs.yml +36 -0
  117. data/lib/locales/da.yml +36 -0
  118. data/lib/locales/de.yml +36 -0
  119. data/lib/locales/el.yml +36 -0
  120. data/lib/locales/en.yml +20 -20
  121. data/lib/locales/es.yml +20 -20
  122. data/lib/locales/fi.yml +36 -0
  123. data/lib/locales/fr.yml +36 -0
  124. data/lib/locales/he.yml +36 -0
  125. data/lib/locales/hi.yml +36 -0
  126. data/lib/locales/it.yml +36 -0
  127. data/lib/locales/ja.yml +36 -0
  128. data/lib/locales/ko.yml +36 -0
  129. data/lib/locales/nl.yml +36 -0
  130. data/lib/locales/no.yml +36 -0
  131. data/lib/locales/pl.yml +36 -0
  132. data/lib/locales/pt.yml +36 -0
  133. data/lib/locales/ru.yml +36 -0
  134. data/lib/locales/sv.yml +36 -0
  135. data/lib/locales/th.yml +36 -0
  136. data/lib/locales/tr.yml +36 -0
  137. data/lib/locales/vi.yml +36 -0
  138. data/lib/locales/zh.yml +36 -0
  139. metadata +77 -15
  140. data/docs/basics/run.md +0 -34
  141. data/docs/batch.md +0 -53
  142. data/docs/example.md +0 -82
  143. data/docs/hooks.md +0 -62
  144. data/lib/cmdx/batch.rb +0 -43
  145. data/lib/cmdx/parameters.rb +0 -35
  146. data/lib/cmdx/run.rb +0 -39
  147. data/lib/cmdx/run_inspector.rb +0 -26
  148. data/lib/cmdx/run_serializer.rb +0 -20
  149. data/lib/cmdx/task_hook.rb +0 -18
  150. data/lib/generators/cmdx/batch_generator.rb +0 -30
  151. /data/lib/generators/cmdx/templates/{batch.rb.tt → workflow.rb.tt} +0 -0
@@ -1,54 +1,308 @@
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
+ - [TLDR](#tldr)
8
+ - [Status Definitions](#status-definitions)
9
+ - [Status Characteristics](#status-characteristics)
10
+ - [Status Predicates](#status-predicates)
11
+ - [Status Transitions](#status-transitions)
12
+ - [Status-Based Callbacks](#status-based-callbacks)
13
+ - [Status Metadata](#status-metadata)
14
+ - [Outcome-Based Logic](#outcome-based-logic)
15
+ - [Status Serialization and Inspection](#status-serialization-and-inspection)
16
+ - [Status vs State vs Outcome](#status-vs-state-vs-outcome)
17
+
18
+ ## TLDR
19
+
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
5
27
 
6
28
  | Status | Description |
7
29
  | --------- | ----------- |
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`. |
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 |
11
33
 
12
34
  > [!NOTE]
13
- > Statuses (except success) are paired with halt methods used to stop call execution.
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
50
+
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
56
+
57
+ ## Status Predicates
58
+
59
+ Use status predicates to check execution outcomes:
60
+
61
+ ```ruby
62
+ result = ProcessUserOrderTask.call
63
+
64
+ # Individual status checks
65
+ result.success? #=> true/false
66
+ result.skipped? #=> true/false
67
+ result.failed? #=> true/false
68
+
69
+ # Outcome categorization
70
+ result.good? #=> true if success OR skipped
71
+ result.bad? #=> true if skipped OR failed (not success)
72
+ ```
73
+
74
+ ## Status Transitions
75
+
76
+ Unlike states, statuses can only transition from success to skipped/failed:
14
77
 
15
78
  ```ruby
16
- result = ProcessOrderTask.call
17
- result.status #=> "skipped"
79
+ # Valid status transitions
80
+ success -> skipped # (via skip!)
81
+ success -> failed # (via fail! or exception)
18
82
 
19
- result.success? #=> false
20
- result.skipped? #=> true
21
- result.failed? #=> false
83
+ # 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
88
+ ```
89
+
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
+ ### Status Transition Examples
94
+
95
+ ```ruby
96
+ class ProcessUserOrderTask < CMDx::Task
97
+ def call
98
+ # Task starts with success status
99
+ context.result.success? #=> true
22
100
 
23
- # `success` or `skipped`
24
- result.good? #=> true
101
+ # Conditional skip
102
+ if context.order.already_processed?
103
+ skip!(reason: "Order already processed")
104
+ # Status is now skipped, execution halts
105
+ end
25
106
 
26
- # `skipped` or `failed`
27
- result.bad? #=> true
107
+ # Conditional failure
108
+ unless context.user.has_permission?
109
+ fail!(reason: "Insufficient permissions")
110
+ # Status is now failed, execution halts
111
+ end
112
+
113
+ # Continue with business logic
114
+ process_order
115
+ # Status remains success
116
+ end
117
+ end
28
118
  ```
29
119
 
30
- ## Handlers
120
+ ## Status-Based Callbacks
31
121
 
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.
122
+ Results provide comprehensive callback methods for status-based logic:
35
123
 
36
124
  ```ruby
37
- result = ProcessOrderTask.call
38
- result.on_success { do_work }
125
+ result = ProcessUserOrderTask.call
39
126
 
40
- # - or -
127
+ # Individual status callbacks
128
+ result
129
+ .on_success { |r| handle_success(r) }
130
+ .on_skipped { |r| handle_skip(r) }
131
+ .on_failed { |r| handle_failure(r) }
41
132
 
42
- ProcessOrderTask
43
- .call(...)
44
- .on_success { do_work }
45
- .on_bad { |result| $statsd.increment(result.state) }
133
+ # Outcome-based callbacks
134
+ result
135
+ .on_good { |r| log_positive_outcome(r) }
136
+ .on_bad { |r| log_negative_outcome(r) }
46
137
  ```
47
138
 
48
139
  > [!TIP]
49
- > Handlers help execute you logical branches without `if/else` blocks.
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
+ ## Status Metadata
143
+
144
+ Statuses carry rich metadata providing context about execution outcomes:
145
+
146
+ ### Success Metadata
147
+
148
+ ```ruby
149
+ class ProcessUserOrderTask < CMDx::Task
150
+ 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
159
+ end
160
+ end
161
+
162
+ result = ProcessUserOrderTask.call(order_id: 123)
163
+ result.success? #=> true
164
+ result.metadata #=> {} (usually empty for success)
165
+ ```
166
+
167
+ ### Skip Metadata
168
+
169
+ ```ruby
170
+ class ProcessUserOrderTask < CMDx::Task
171
+ def call
172
+ order = Order.find(context.order_id)
173
+
174
+ if order.already_processed?
175
+ skip!(
176
+ reason: "Order already processed",
177
+ processed_at: order.processed_at,
178
+ original_processor: order.processor_id,
179
+ skip_code: "DUPLICATE_PROCESSING"
180
+ )
181
+ end
182
+
183
+ # Continue processing...
184
+ end
185
+ end
186
+
187
+ result = ProcessUserOrderTask.call(order_id: 123)
188
+ 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"
193
+ end
194
+ ```
195
+
196
+ ### Failure Metadata
197
+
198
+ ```ruby
199
+ class ValidateOrderDataTask < CMDx::Task
200
+ def call
201
+ unless context.order.valid?
202
+ fail!(
203
+ reason: "Order validation failed",
204
+ errors: context.order.errors.full_messages,
205
+ error_code: "VALIDATION_FAILED",
206
+ retryable: false,
207
+ failed_at: Time.now
208
+ )
209
+ end
210
+ end
211
+ end
212
+
213
+ result = ValidateOrderDataTask.call(order_id: 123)
214
+ if result.failed?
215
+ result.metadata[:reason] #=> "Order validation failed"
216
+ result.metadata[:errors] #=> ["Name can't be blank", "Email is invalid"]
217
+ result.metadata[:error_code] #=> "VALIDATION_FAILED"
218
+ result.metadata[:retryable] #=> false
219
+ result.metadata[:failed_at] #=> 2023-10-01 10:30:00 UTC
220
+ end
221
+ ```
222
+
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
+ ## Outcome-Based Logic
227
+
228
+ Statuses enable sophisticated outcome-based decision making:
229
+
230
+ ### Good vs Bad Outcomes
231
+
232
+ ```ruby
233
+ # Good outcomes (success OR skipped)
234
+ result.good? #=> true if success? || skipped?
235
+ result.bad? #=> true if !success? (skipped OR failed)
236
+
237
+ # Usage patterns
238
+ if result.good?
239
+ # Both success and skipped are "good" outcomes
240
+ update_user_interface(result)
241
+ log_completed_action(result)
242
+ end
243
+
244
+ if result.bad?
245
+ # Handle any non-success outcome (skipped or failed)
246
+ show_error_message(result.metadata[:reason])
247
+ track_negative_outcome(result)
248
+ end
249
+ ```
250
+
251
+ ## Status Serialization and Inspection
252
+
253
+ Statuses are fully captured in result serialization:
254
+
255
+ ```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..."
276
+ ```
277
+
278
+ ## Status vs State vs Outcome
279
+
280
+ Understanding the relationship between these concepts:
281
+
282
+ - **Status**: Business execution outcome (`success`, `skipped`, `failed`)
283
+ - **State**: Technical execution lifecycle (`initialized`, `executing`, `complete`, `interrupted`)
284
+ - **Outcome**: Combined representation for unified logic
285
+
286
+ ```ruby
287
+ result = ProcessUserOrderTask.call
288
+
289
+ # Different scenarios
290
+ result.state #=> "complete"
291
+ result.status #=> "success"
292
+ result.outcome #=> "success" (same as status when complete)
293
+
294
+ # Skipped task
295
+ skipped_result.state #=> "complete" (execution finished)
296
+ skipped_result.status #=> "skipped" (business outcome)
297
+ skipped_result.outcome #=> "skipped" (same as status)
298
+
299
+ # Failed task
300
+ failed_result.state #=> "interrupted" (execution stopped)
301
+ failed_result.status #=> "failed" (business outcome)
302
+ failed_result.outcome #=> "interrupted" (reflects state for interrupted tasks)
303
+ ```
50
304
 
51
305
  ---
52
306
 
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)
307
+ - **Prev:** [Outcomes - Result](result.md)
308
+ - **Next:** [Outcomes - States](states.md)