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
data/docs/workflows.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Workflow
2
2
 
3
- A CMDx::Workflow orchestrates sequential execution of multiple tasks in a linear pipeline. Workflows provide a declarative DSL for composing complex business workflows from individual task components, with support for conditional execution, context propagation, and configurable halt behavior.
3
+ CMDx::Workflow orchestrates sequential execution of multiple tasks in a linear pipeline. Workflows provide a declarative DSL for composing complex business workflows from individual task components, with support for conditional execution, context propagation, and configurable halt behavior.
4
4
 
5
5
  Workflows inherit from Task, gaining all task capabilities including callbacks, parameter validation, result tracking, and configuration. The key difference is that workflows coordinate other tasks rather than implementing business logic directly.
6
6
 
@@ -17,55 +17,68 @@ Workflows inherit from Task, gaining all task capabilities including callbacks,
17
17
  - [Group-Level Configuration](#group-level-configuration)
18
18
  - [Available Result Statuses](#available-result-statuses)
19
19
  - [Process Method Options](#process-method-options)
20
- - [Condition Callables](#condition-callables)
20
+ - [Error Handling](#error-handling)
21
21
  - [Nested Workflows](#nested-workflows)
22
22
  - [Task Settings Integration](#task-settings-integration)
23
23
  - [Generator](#generator)
24
24
 
25
25
  ## TLDR
26
26
 
27
- - **Purpose** - Orchestrate sequential execution of multiple tasks in linear pipeline
28
- - **Declaration** - Use `process` method to declare tasks in execution order
29
- - **Context sharing** - Context object shared across all tasks for data pipeline
30
- - **Conditional execution** - Support `:if` and `:unless` options for conditional tasks
31
- - **Halt behavior** - Configurable stopping on failed/skipped results (default: halt on failed only)
32
- - **No call method** - Workflows automatically provide execution logic, don't define `call`
27
+ ```ruby
28
+ # Basic workflow - sequential task execution
29
+ class OrderWorkflow < CMDx::Workflow
30
+ process ValidateOrderTask # Step 1
31
+ process CalculateTaxTask # Step 2
32
+ process ChargePaymentTask # Step 3
33
+ end
34
+
35
+ # Conditional execution
36
+ process SendEmailTask, if: proc { context.notify_user? }
37
+ process SkipableTask, unless: :should_skip?
38
+
39
+ # Halt behavior control
40
+ process CriticalTask, workflow_halt: [CMDx::Result::FAILED, CMDx::Result::SKIPPED]
41
+ process OptionalTask, workflow_halt: [] # Never halt
42
+
43
+ # Context flows through all tasks automatically
44
+ result = OrderWorkflow.call(order: order)
45
+ result.context.tax_amount # Set by CalculateTaxTask
46
+ result.context.payment_id # Set by ChargePaymentTask
47
+ ```
33
48
 
34
49
  ## Basic Usage
35
50
 
36
51
  > [!WARNING]
37
- > Do **NOT** define a `call` method in workflow classes. The workflow class automatically provides the call logic.
52
+ > Do **NOT** define a `call` method in workflow classes. The workflow automatically provides execution logic.
38
53
 
39
54
  ```ruby
40
55
  class OrderProcessingWorkflow < CMDx::Workflow
41
- # Sequential task execution
42
56
  process ValidateOrderTask
43
57
  process CalculateTaxTask
44
58
  process ChargePaymentTask
45
59
  process FulfillOrderTask
46
60
  end
47
61
 
48
- # Execute the workflow
49
- result = WorkflowProcessOrders.call(order: order, user: current_user)
62
+ # Execute workflow
63
+ result = OrderProcessingWorkflow.call(order: order, user: current_user)
50
64
 
51
65
  if result.success?
52
- redirect_to success_path
66
+ redirect_to order_path(result.context.order)
53
67
  elsif result.failed?
54
- flash[:error] = "Order processing failed: #{result.metadata[:reason]}"
55
- redirect_to cart_path
68
+ handle_error(result.metadata[:reason])
56
69
  end
57
70
  ```
58
71
 
59
72
  ## Task Declaration
60
73
 
61
- Tasks are declared using the `process` method and organized into groups with shared execution options:
74
+ Tasks are declared using the `process` method in execution order:
62
75
 
63
76
  ```ruby
64
- class NotificationDeliveryWorkflow < CMDx::Workflow
65
- # Single task declaration
77
+ class NotificationWorkflow < CMDx::Workflow
78
+ # Single task
66
79
  process PrepareNotificationTask
67
80
 
68
- # Multiple tasks in one declaration (grouped)
81
+ # Multiple tasks (grouped with same options)
69
82
  process SendEmailTask, SendSmsTask, SendPushTask
70
83
 
71
84
  # Tasks with conditions
@@ -81,34 +94,32 @@ end
81
94
  ```
82
95
 
83
96
  > [!IMPORTANT]
84
- > Process steps are executed in the order they are declared (FIFO: first in, first out).
97
+ > Tasks execute in declaration order (FIFO). Use grouping to apply the same options to multiple tasks.
85
98
 
86
99
  ## Context Propagation
87
100
 
88
- The context object is shared across all tasks in the workflow, creating a data pipeline:
101
+ The context object flows through all tasks, creating a data pipeline:
89
102
 
90
103
  ```ruby
91
- class EcommerceProcessingWorkflow < CMDx::Workflow
92
- process ValidateOrderTask # Sets context.validation_result
93
- process CalculateTaxTask # Uses context.order, sets context.tax_amount
94
- process ChargePaymentTask # Uses context.tax_amount, sets context.payment_id
95
- process FulfillOrderTask # Uses context.payment_id, sets context.tracking_number
104
+ class PaymentWorkflow < CMDx::Workflow
105
+ process ValidateOrderTask # Sets context.validation_errors
106
+ process CalculateTaxTask # Uses context.order, sets context.tax_amount
107
+ process ChargePaymentTask # Uses context.tax_amount, sets context.payment_id
96
108
  end
97
109
 
98
- result = WorkflowProcessEcommerce.call(order: order)
99
- # Final context contains data from all executed tasks
100
- result.context.validation_result # From ValidateOrderTask
101
- result.context.tax_amount # From CalculateTaxTask
102
- result.context.payment_id # From ChargePaymentTask
103
- result.context.tracking_number # From FulfillOrderTask
110
+ result = PaymentWorkflow.call(order: order)
111
+ # Context contains cumulative data from all executed tasks
112
+ result.context.validation_errors # From ValidateOrderTask
113
+ result.context.tax_amount # From CalculateTaxTask
114
+ result.context.payment_id # From ChargePaymentTask
104
115
  ```
105
116
 
106
117
  ## Conditional Execution
107
118
 
108
- Tasks can be executed conditionally using `:if` and `:unless` options. Conditions can be procs, lambdas, or method names:
119
+ Tasks can execute conditionally using `:if` and `:unless` options:
109
120
 
110
121
  ```ruby
111
- class UserProcessingWorkflow < CMDx::Workflow
122
+ class UserWorkflow < CMDx::Workflow
112
123
  process ValidateUserTask
113
124
 
114
125
  # Proc condition
@@ -124,7 +135,7 @@ class UserProcessingWorkflow < CMDx::Workflow
124
135
  process SendSpecialOfferTask, if: proc {
125
136
  context.user.active? &&
126
137
  context.feature_enabled?(:offers) &&
127
- Time.now.hour.between?(9, 17)
138
+ business_hours?
128
139
  }
129
140
 
130
141
  private
@@ -132,41 +143,48 @@ class UserProcessingWorkflow < CMDx::Workflow
132
143
  def debug_enabled?
133
144
  Rails.env.development?
134
145
  end
146
+
147
+ def business_hours?
148
+ Time.now.hour.between?(9, 17)
149
+ end
135
150
  end
136
151
  ```
137
152
 
153
+ > [!NOTE]
154
+ > Conditions are evaluated in the workflow instance context. Skipped tasks return `SKIPPED` status but don't halt execution by default.
155
+
138
156
  ## Halt Behavior
139
157
 
140
- Workflows control execution flow through halt behavior, which determines when to stop processing based on task results.
158
+ Workflows control execution flow by halting on specific result statuses.
141
159
 
142
160
  ### Default Behavior
143
161
 
144
- By default, workflows halt on `FAILED` status but continue on `SKIPPED`. This reflects the philosophy that skipped tasks are bypass mechanisms, not execution blockers.
162
+ By default, workflows halt on `FAILED` status but continue on `SKIPPED`:
145
163
 
146
164
  ```ruby
147
- class DataProcessingWorkflow < CMDx::Workflow
148
- process LoadDataTask # If this fails, workflow stops
149
- process ValidateDataTask # If this is skipped, workflow continues
150
- process SaveDataTask # This only runs if LoadDataTask and ValidateDataTask don't fail
165
+ class DataWorkflow < CMDx::Workflow
166
+ process LoadDataTask # If fails workflow stops
167
+ process ValidateDataTask # If skipped workflow continues
168
+ process SaveDataTask # Only runs if no failures occurred
151
169
  end
152
170
  ```
153
171
 
154
172
  ### Class-Level Configuration
155
173
 
156
- Configure halt behavior for the entire workflow using `task_settings!`:
174
+ Configure halt behavior for the entire workflow:
157
175
 
158
176
  ```ruby
159
- class CriticalDataProcessingWorkflow < CMDx::Workflow
177
+ class CriticalWorkflow < CMDx::Workflow
160
178
  # Halt on both failed and skipped results
161
- task_settings!(workflow_halt: [CMDx::Result::FAILED, CMDx::Result::SKIPPED])
179
+ cmd_settings!(workflow_halt: [CMDx::Result::FAILED, CMDx::Result::SKIPPED])
162
180
 
163
181
  process LoadCriticalDataTask
164
182
  process ValidateCriticalDataTask
165
183
  end
166
184
 
167
- class OptionalDataProcessingWorkflow < CMDx::Workflow
185
+ class OptionalWorkflow < CMDx::Workflow
168
186
  # Never halt, always continue
169
- task_settings!(workflow_halt: [])
187
+ cmd_settings!(workflow_halt: [])
170
188
 
171
189
  process TryLoadDataTask
172
190
  process TryValidateDataTask
@@ -176,73 +194,102 @@ end
176
194
 
177
195
  ### Group-Level Configuration
178
196
 
179
- Different groups can have different halt behavior:
197
+ Different task groups can have different halt behavior:
180
198
 
181
199
  ```ruby
182
- class UserAccountProcessingWorkflow < CMDx::Workflow
200
+ class AccountWorkflow < CMDx::Workflow
183
201
  # Critical tasks - halt on any failure or skip
184
202
  process CreateUserTask, ValidateUserTask,
185
203
  workflow_halt: [CMDx::Result::FAILED, CMDx::Result::SKIPPED]
186
204
 
187
- # Optional tasks - never halt execution
188
- process SendWelcomeEmailTask, CreateProfileTask, workflow_halt: []
205
+ # Optional tasks - never halt
206
+ process SendWelcomeEmailTask, CreateProfileTask,
207
+ workflow_halt: []
189
208
 
190
- # Notification tasks - use default behavior (halt on failed only)
209
+ # Default behavior for remaining tasks
191
210
  process NotifyAdminTask, LogUserCreationTask
192
211
  end
193
212
  ```
194
213
 
195
214
  ### Available Result Statuses
196
215
 
197
- The following result statuses can be used in `workflow_halt` arrays:
216
+ Use these statuses in `workflow_halt` arrays:
198
217
 
199
- - `CMDx::Result::SUCCESS` - Task completed successfully
200
- - `CMDx::Result::SKIPPED` - Task was skipped intentionally
201
- - `CMDx::Result::FAILED` - Task failed due to error or validation
218
+ | Status | Description |
219
+ |--------|-------------|
220
+ | `CMDx::Result::SUCCESS` | Task completed successfully |
221
+ | `CMDx::Result::SKIPPED` | Task was skipped intentionally |
222
+ | `CMDx::Result::FAILED` | Task failed due to error or validation |
202
223
 
203
224
  ## Process Method Options
204
225
 
205
- The `process` method supports the following options:
226
+ The `process` method supports these options:
206
227
 
207
- | Option | Description |
208
- | ------------- | ----------- |
209
- | `:if` | Specifies a callable method, proc or string to determine if processing steps should occur. |
210
- | `:unless` | Specifies a callable method, proc, or string to determine if processing steps should not occur. |
211
- | `:workflow_halt` | Sets which result statuses processing of further steps should be prevented. (default: `CMDx::Result::FAILED`) |
228
+ | Option | Description | Example |
229
+ |--------|-------------|---------|
230
+ | `:if` | Execute task if condition is true | `if: proc { context.enabled? }` |
231
+ | `:unless` | Execute task if condition is false | `unless: :should_skip?` |
232
+ | `:workflow_halt` | Which statuses should halt execution | `workflow_halt: [CMDx::Result::FAILED]` |
212
233
 
213
- ### Condition Callables
234
+ Conditions can be procs, lambdas, symbols, or strings referencing instance methods.
214
235
 
215
- Conditions can be provided in several formats:
236
+ ## Error Handling
216
237
 
217
- ```ruby
218
- class AccountProcessingWorkflow < CMDx::Workflow
219
- # Proc - executed in workflow instance context
220
- process UpgradeAccountTask, if: proc { context.user.admin? }
238
+ > [!WARNING]
239
+ > Workflow failures provide detailed information about which task failed and why, enabling precise error handling and debugging.
221
240
 
222
- # Lambda - executed in workflow instance context
223
- process MaintenanceModeTask, unless: -> { context.maintenance_mode? }
241
+ ```ruby
242
+ class OrderWorkflow < CMDx::Workflow
243
+ process ValidateOrderTask
244
+ process CalculateTaxTask
245
+ process ChargePaymentTask
246
+ end
224
247
 
225
- # Symbol - method name called on workflow instance
226
- process AdvancedFeatureTask, if: :feature_enabled?
248
+ result = OrderWorkflow.call(order: invalid_order)
249
+
250
+ if result.failed?
251
+ result.metadata
252
+ # {
253
+ # reason: "ValidateOrderTask failed: Order ID is required",
254
+ # failed_task: "ValidateOrderTask",
255
+ # task_index: 0,
256
+ # executed_tasks: ["ValidateOrderTask"],
257
+ # skipped_tasks: [],
258
+ # context_at_failure: { order: {...} }
259
+ # }
260
+ end
261
+ ```
227
262
 
228
- # String - method name called on workflow instance
229
- process OptionalTask, unless: "skip_task?"
263
+ ### Common Error Scenarios
230
264
 
231
- private
265
+ ```ruby
266
+ # Task raises exception
267
+ class ProcessDataWorkflow < CMDx::Workflow
268
+ process ValidateDataTask # Raises validation error
269
+ process TransformDataTask # Never executes
270
+ end
232
271
 
233
- def feature_enabled?
234
- context.features.include?(:advanced)
235
- end
272
+ result = ProcessDataWorkflow.call(data: nil)
273
+ result.failed? # → true
274
+ result.metadata[:reason] # → "ValidateDataTask failed: Data cannot be nil"
236
275
 
237
- def skip_task?
238
- context.skip_optional_tasks?
239
- end
276
+ # Halt on skipped task
277
+ class StrictWorkflow < CMDx::Workflow
278
+ process RequiredTask, workflow_halt: [CMDx::Result::SKIPPED]
279
+ process OptionalTask, if: proc { false } # Always skipped
280
+ process FinalTask # Never executes
240
281
  end
282
+
283
+ result = StrictWorkflow.call
284
+ result.failed? # → true (halted on skipped task)
241
285
  ```
242
286
 
287
+ > [!TIP]
288
+ > Use specific halt configurations to implement different failure strategies: strict validation, best-effort processing, or fault-tolerant pipelines.
289
+
243
290
  ## Nested Workflows
244
291
 
245
- Workflows can process other workflows, creating hierarchical workflows:
292
+ Workflows can process other workflows for hierarchical composition:
246
293
 
247
294
  ```ruby
248
295
  class DataPreProcessingWorkflow < CMDx::Workflow
@@ -255,35 +302,33 @@ class DataProcessingWorkflow < CMDx::Workflow
255
302
  process ApplyBusinessLogicTask
256
303
  end
257
304
 
258
- class DataPostProcessingWorkflow < CMDx::Workflow
259
- process GenerateReportTask
260
- process SendNotificationTask
261
- end
262
-
263
- class CompleteDataProcessingWorkflow < CMDx::Workflow
305
+ class CompleteDataWorkflow < CMDx::Workflow
264
306
  process DataPreProcessingWorkflow
265
307
  process DataProcessingWorkflow, if: proc { context.pre_processing_successful? }
266
- process DataPostProcessingWorkflow, unless: proc { context.skip_post_processing? }
308
+ process GenerateReportTask
267
309
  end
268
310
  ```
269
311
 
312
+ > [!NOTE]
313
+ > Nested workflows share the same context object, enabling seamless data flow across workflow boundaries.
314
+
270
315
  ## Task Settings Integration
271
316
 
272
- Workflows support all task settings and can be configured like regular tasks:
317
+ Workflows support all task capabilities including parameters, callbacks, and configuration:
273
318
 
274
319
  ```ruby
275
- class PaymentProcessingWorkflow < CMDx::Workflow
276
- # Configure workflow-specific settings
277
- task_settings!(
320
+ class PaymentWorkflow < CMDx::Workflow
321
+ # Parameter validation
322
+ required :order_id, type: :integer
323
+ optional :notify_user, type: :boolean, default: true
324
+
325
+ # Workflow settings
326
+ cmd_settings!(
278
327
  workflow_halt: [CMDx::Result::FAILED],
279
328
  log_level: :debug,
280
329
  tags: [:critical, :payment]
281
330
  )
282
331
 
283
- # Parameter validation
284
- required :order_id, type: :integer
285
- optional :notify_user, type: :boolean, default: true
286
-
287
332
  # Callbacks
288
333
  before_execution :setup_context
289
334
  after_execution :cleanup_resources
@@ -306,22 +351,22 @@ end
306
351
 
307
352
  ## Generator
308
353
 
309
- Generate a new workflow using the Rails generator:
354
+ Generate workflow scaffolding using the Rails generator:
310
355
 
311
356
  ```bash
312
357
  rails g cmdx:workflow ProcessOrder
313
358
  ```
314
359
 
315
- This creates a workflow template file under `app/cmds`:
360
+ Creates `app/commands/process_order_workflow.rb`:
316
361
 
317
362
  ```ruby
318
- class OrderProcessingWorkflow < ApplicationWorkflow
319
- process # TODO
363
+ class ProcessOrderWorkflow < ApplicationWorkflow
364
+ process # TODO: Add your tasks here
320
365
  end
321
366
  ```
322
367
 
323
368
  > [!NOTE]
324
- > The generator creates workflow files in `app/commands/workflow_[name].rb`, inherits from `ApplicationWorkflow` if available (otherwise `CMDx::Workflow`) and handles proper naming conventions.
369
+ > The generator creates workflow files in `app/commands/`, inherits from `ApplicationWorkflow` if available (otherwise `CMDx::Workflow`), and handles proper naming conventions.
325
370
 
326
371
  ---
327
372
 
data/lib/cmdx/.DS_Store CHANGED
Binary file
data/lib/cmdx/callback.rb CHANGED
@@ -1,67 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- ##
5
- # Base class for CMDx callbacks that provides lifecycle execution points.
4
+ # Base class for implementing callback functionality in task processing.
6
5
  #
7
- # Callback components can wrap or observe task execution at specific lifecycle
8
- # points like before validation, on success, after execution, etc.
9
- # Each callback must implement the `call` method which receives the
10
- # task instance and callback context.
11
- #
12
- # @example Basic callback implementation
13
- # class LoggingCallback < CMDx::Callback
14
- # def call(task, callback_type)
15
- # puts "Executing #{callback_type} callback for #{task.class.name}"
16
- # task.logger.info("Callback executed: #{callback_type}")
17
- # end
18
- # end
19
- #
20
- # @example Callback with initialization parameters
21
- # class NotificationCallback < CMDx::Callback
22
- # def initialize(channels)
23
- # @channels = channels
24
- # end
25
- #
26
- # def call(task, callback_type)
27
- # return unless callback_type == :on_success
28
- #
29
- # @channels.each do |channel|
30
- # NotificationService.send(channel, "Task #{task.class.name} completed")
31
- # end
32
- # end
33
- # end
34
- #
35
- # @example Conditional callback execution
36
- # class ErrorReportingCallback < CMDx::Callback
37
- # def call(task, callback_type)
38
- # return unless callback_type == :on_failure
39
- # return unless task.result.failed?
40
- #
41
- # ErrorReporter.notify(
42
- # task.errors.full_messages.join(", "),
43
- # context: task.context.to_h
44
- # )
45
- # end
46
- # end
47
- #
48
- # @see CallbackRegistry Callback management
49
- # @see Task Callback integration
50
- # @since 1.0.0
6
+ # Callbacks are executed at specific points in the task lifecycle, such as
7
+ # before execution, after success, or on failure. All callback implementations
8
+ # must inherit from this class and implement the abstract call method.
51
9
  class Callback
52
10
 
53
- ##
54
- # Executes the callback logic.
11
+ # Executes a callback by creating a new instance and calling it.
12
+ #
13
+ # @param task [CMDx::Task] the task instance triggering the callback
14
+ # @param type [Symbol] the callback type being executed (e.g., :before_execution, :on_success, :on_failure)
15
+ #
16
+ # @return [void]
17
+ #
18
+ # @raise [UndefinedCallError] when the callback subclass doesn't implement call
19
+ #
20
+ # @example Execute a callback for task success
21
+ # LogSuccessCallback.call(task, :on_success)
22
+ #
23
+ # @example Execute a callback before task execution
24
+ # SetupCallback.call(task, :before_execution)
25
+ def self.call(task, type)
26
+ new.call(task, type)
27
+ end
28
+
29
+ # Abstract method that must be implemented by callback subclasses.
30
+ #
31
+ # This method contains the actual callback logic to be executed at the
32
+ # specified point in the task lifecycle. Subclasses must override this method
33
+ # to provide their specific callback implementation.
55
34
  #
56
- # This method must be implemented by subclasses to define the callback
57
- # behavior. The method receives the task instance and the callback type
58
- # being executed.
35
+ # @param task [CMDx::Task] the task instance triggering the callback
36
+ # @param type [Symbol] the callback type being executed
59
37
  #
60
- # @param task [Task] the task instance being executed
61
- # @param callback_type [Symbol] the type of callback being executed
62
38
  # @return [void]
63
- # @abstract Subclasses must implement this method
64
- def call(_task, _callback_type)
39
+ #
40
+ # @raise [UndefinedCallError] always raised in the base class
41
+ #
42
+ # @example Implement in a subclass
43
+ # class NotificationCallback < CMDx::Callback
44
+ # def call(task, type)
45
+ # puts "Task #{task.class.name} triggered #{type} callback"
46
+ # end
47
+ # end
48
+ def call(task, type) # rubocop:disable Lint/UnusedMethodArgument
65
49
  raise UndefinedCallError, "call method not defined in #{self.class.name}"
66
50
  end
67
51