cmdx 1.1.1 → 1.5.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 (193) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/prompts/docs.md +4 -1
  4. data/.cursor/prompts/llms.md +20 -0
  5. data/.cursor/prompts/rspec.md +4 -1
  6. data/.cursor/prompts/yardoc.md +3 -2
  7. data/.cursor/rules/cursor-instructions.mdc +56 -1
  8. data/.irbrc +6 -0
  9. data/.rubocop.yml +29 -18
  10. data/.ruby-version +1 -1
  11. data/CHANGELOG.md +6 -128
  12. data/LLM.md +3317 -0
  13. data/README.md +68 -44
  14. data/docs/attributes/coercions.md +162 -0
  15. data/docs/attributes/defaults.md +90 -0
  16. data/docs/attributes/definitions.md +281 -0
  17. data/docs/attributes/naming.md +78 -0
  18. data/docs/attributes/validations.md +309 -0
  19. data/docs/basics/chain.md +56 -249
  20. data/docs/basics/context.md +56 -289
  21. data/docs/basics/execution.md +114 -0
  22. data/docs/basics/setup.md +37 -334
  23. data/docs/callbacks.md +89 -467
  24. data/docs/deprecation.md +91 -174
  25. data/docs/getting_started.md +212 -202
  26. data/docs/internationalization.md +11 -647
  27. data/docs/interruptions/exceptions.md +23 -198
  28. data/docs/interruptions/faults.md +71 -151
  29. data/docs/interruptions/halt.md +109 -186
  30. data/docs/logging.md +44 -256
  31. data/docs/middlewares.md +113 -426
  32. data/docs/outcomes/result.md +81 -228
  33. data/docs/outcomes/states.md +33 -221
  34. data/docs/outcomes/statuses.md +21 -311
  35. data/docs/tips_and_tricks.md +120 -70
  36. data/docs/workflows.md +99 -283
  37. data/lib/cmdx/.DS_Store +0 -0
  38. data/lib/cmdx/attribute.rb +229 -0
  39. data/lib/cmdx/attribute_registry.rb +94 -0
  40. data/lib/cmdx/attribute_value.rb +193 -0
  41. data/lib/cmdx/callback_registry.rb +69 -77
  42. data/lib/cmdx/chain.rb +56 -73
  43. data/lib/cmdx/coercion_registry.rb +52 -68
  44. data/lib/cmdx/coercions/array.rb +19 -18
  45. data/lib/cmdx/coercions/big_decimal.rb +20 -24
  46. data/lib/cmdx/coercions/boolean.rb +26 -25
  47. data/lib/cmdx/coercions/complex.rb +21 -22
  48. data/lib/cmdx/coercions/date.rb +25 -23
  49. data/lib/cmdx/coercions/date_time.rb +24 -25
  50. data/lib/cmdx/coercions/float.rb +25 -22
  51. data/lib/cmdx/coercions/hash.rb +31 -32
  52. data/lib/cmdx/coercions/integer.rb +30 -24
  53. data/lib/cmdx/coercions/rational.rb +29 -24
  54. data/lib/cmdx/coercions/string.rb +19 -22
  55. data/lib/cmdx/coercions/symbol.rb +37 -0
  56. data/lib/cmdx/coercions/time.rb +26 -25
  57. data/lib/cmdx/configuration.rb +49 -108
  58. data/lib/cmdx/context.rb +222 -44
  59. data/lib/cmdx/deprecator.rb +61 -0
  60. data/lib/cmdx/errors.rb +42 -252
  61. data/lib/cmdx/exceptions.rb +39 -0
  62. data/lib/cmdx/faults.rb +78 -39
  63. data/lib/cmdx/freezer.rb +51 -0
  64. data/lib/cmdx/identifier.rb +30 -0
  65. data/lib/cmdx/locale.rb +52 -0
  66. data/lib/cmdx/log_formatters/json.rb +21 -22
  67. data/lib/cmdx/log_formatters/key_value.rb +20 -22
  68. data/lib/cmdx/log_formatters/line.rb +15 -22
  69. data/lib/cmdx/log_formatters/logstash.rb +22 -23
  70. data/lib/cmdx/log_formatters/raw.rb +16 -22
  71. data/lib/cmdx/middleware_registry.rb +70 -74
  72. data/lib/cmdx/middlewares/correlate.rb +90 -54
  73. data/lib/cmdx/middlewares/runtime.rb +58 -0
  74. data/lib/cmdx/middlewares/timeout.rb +48 -68
  75. data/lib/cmdx/railtie.rb +12 -45
  76. data/lib/cmdx/result.rb +229 -314
  77. data/lib/cmdx/task.rb +194 -366
  78. data/lib/cmdx/utils/call.rb +49 -0
  79. data/lib/cmdx/utils/condition.rb +71 -0
  80. data/lib/cmdx/utils/format.rb +61 -0
  81. data/lib/cmdx/validator_registry.rb +63 -72
  82. data/lib/cmdx/validators/exclusion.rb +38 -67
  83. data/lib/cmdx/validators/format.rb +48 -49
  84. data/lib/cmdx/validators/inclusion.rb +43 -74
  85. data/lib/cmdx/validators/length.rb +91 -154
  86. data/lib/cmdx/validators/numeric.rb +87 -162
  87. data/lib/cmdx/validators/presence.rb +37 -50
  88. data/lib/cmdx/version.rb +1 -1
  89. data/lib/cmdx/worker.rb +178 -0
  90. data/lib/cmdx/workflow.rb +85 -81
  91. data/lib/cmdx.rb +19 -13
  92. data/lib/generators/cmdx/install_generator.rb +14 -13
  93. data/lib/generators/cmdx/task_generator.rb +25 -50
  94. data/lib/generators/cmdx/templates/install.rb +11 -46
  95. data/lib/generators/cmdx/templates/task.rb.tt +3 -2
  96. data/lib/locales/en.yml +18 -4
  97. data/src/cmdx-logo.png +0 -0
  98. metadata +32 -116
  99. data/docs/ai_prompts.md +0 -393
  100. data/docs/basics/call.md +0 -317
  101. data/docs/configuration.md +0 -344
  102. data/docs/parameters/coercions.md +0 -396
  103. data/docs/parameters/defaults.md +0 -335
  104. data/docs/parameters/definitions.md +0 -446
  105. data/docs/parameters/namespacing.md +0 -378
  106. data/docs/parameters/validations.md +0 -405
  107. data/docs/testing.md +0 -553
  108. data/lib/cmdx/callback.rb +0 -53
  109. data/lib/cmdx/chain_inspector.rb +0 -56
  110. data/lib/cmdx/chain_serializer.rb +0 -63
  111. data/lib/cmdx/coercion.rb +0 -57
  112. data/lib/cmdx/coercions/virtual.rb +0 -29
  113. data/lib/cmdx/core_ext/hash.rb +0 -83
  114. data/lib/cmdx/core_ext/module.rb +0 -98
  115. data/lib/cmdx/core_ext/object.rb +0 -125
  116. data/lib/cmdx/correlator.rb +0 -122
  117. data/lib/cmdx/error.rb +0 -60
  118. data/lib/cmdx/fault.rb +0 -140
  119. data/lib/cmdx/immutator.rb +0 -52
  120. data/lib/cmdx/lazy_struct.rb +0 -246
  121. data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
  122. data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
  123. data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
  124. data/lib/cmdx/logger.rb +0 -49
  125. data/lib/cmdx/logger_ansi.rb +0 -68
  126. data/lib/cmdx/logger_serializer.rb +0 -116
  127. data/lib/cmdx/middleware.rb +0 -70
  128. data/lib/cmdx/parameter.rb +0 -312
  129. data/lib/cmdx/parameter_evaluator.rb +0 -231
  130. data/lib/cmdx/parameter_inspector.rb +0 -66
  131. data/lib/cmdx/parameter_registry.rb +0 -106
  132. data/lib/cmdx/parameter_serializer.rb +0 -59
  133. data/lib/cmdx/result_ansi.rb +0 -71
  134. data/lib/cmdx/result_inspector.rb +0 -71
  135. data/lib/cmdx/result_logger.rb +0 -59
  136. data/lib/cmdx/result_serializer.rb +0 -104
  137. data/lib/cmdx/rspec/matchers.rb +0 -28
  138. data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
  139. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
  140. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
  141. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
  142. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
  143. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
  144. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
  145. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
  146. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
  147. data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
  148. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
  149. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
  150. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
  151. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
  152. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
  153. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
  154. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
  155. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
  156. data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
  157. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
  158. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
  159. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
  160. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
  161. data/lib/cmdx/task_deprecator.rb +0 -52
  162. data/lib/cmdx/task_processor.rb +0 -246
  163. data/lib/cmdx/task_serializer.rb +0 -57
  164. data/lib/cmdx/utils/ansi_color.rb +0 -73
  165. data/lib/cmdx/utils/log_timestamp.rb +0 -36
  166. data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
  167. data/lib/cmdx/utils/name_affix.rb +0 -52
  168. data/lib/cmdx/validator.rb +0 -57
  169. data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
  170. data/lib/generators/cmdx/workflow_generator.rb +0 -84
  171. data/lib/locales/ar.yml +0 -35
  172. data/lib/locales/cs.yml +0 -35
  173. data/lib/locales/da.yml +0 -35
  174. data/lib/locales/de.yml +0 -35
  175. data/lib/locales/el.yml +0 -35
  176. data/lib/locales/es.yml +0 -35
  177. data/lib/locales/fi.yml +0 -35
  178. data/lib/locales/fr.yml +0 -35
  179. data/lib/locales/he.yml +0 -35
  180. data/lib/locales/hi.yml +0 -35
  181. data/lib/locales/it.yml +0 -35
  182. data/lib/locales/ja.yml +0 -35
  183. data/lib/locales/ko.yml +0 -35
  184. data/lib/locales/nl.yml +0 -35
  185. data/lib/locales/no.yml +0 -35
  186. data/lib/locales/pl.yml +0 -35
  187. data/lib/locales/pt.yml +0 -35
  188. data/lib/locales/ru.yml +0 -35
  189. data/lib/locales/sv.yml +0 -35
  190. data/lib/locales/th.yml +0 -35
  191. data/lib/locales/tr.yml +0 -35
  192. data/lib/locales/vi.yml +0 -35
  193. data/lib/locales/zh.yml +0 -35
@@ -1,363 +1,130 @@
1
1
  # Basics - Context
2
2
 
3
- Task context provides flexible data storage and sharing for task execution. Built on `LazyStruct`, context enables dynamic attribute access, parameter validation, and seamless data flow between related tasks.
3
+ Task context provides flexible data storage, access, and sharing within task execution. It serves as the primary data container for all task inputs, intermediate results, and outputs.
4
4
 
5
5
  ## Table of Contents
6
6
 
7
- - [TLDR](#tldr)
8
- - [Context Fundamentals](#context-fundamentals)
7
+ - [Assigning Data](#assigning-data)
9
8
  - [Accessing Data](#accessing-data)
10
9
  - [Modifying Context](#modifying-context)
11
- - [Data Sharing Between Tasks](#data-sharing-between-tasks)
12
- - [Result Object Context Passing](#result-object-context-passing)
13
- - [Context Inspection and Debugging](#context-inspection-and-debugging)
14
- - [Error Handling](#error-handling)
10
+ - [Data Sharing](#data-sharing)
15
11
 
16
- ## TLDR
12
+ ## Assigning Data
17
13
 
18
- ```ruby
19
- # Automatic parameter loading
20
- ProcessOrderTask.call(user_id: 123, amount: 99.99)
21
-
22
- # Dynamic attribute access
23
- context.user_id # → 123
24
- context[:amount] # → 99.99
25
-
26
- # Safe modification
27
- context.total = amount * 1.08
28
- context.merge!(status: "processed", completed_at: Time.now)
29
-
30
- # Task chaining with context preservation
31
- result = ValidateOrderTask.call(context)
32
- ProcessPaymentTask.call(result) if result.success?
33
- ```
34
-
35
- ## Context Fundamentals
36
-
37
- > [!IMPORTANT]
38
- > Context is automatically populated with all parameters passed to a task. Parameters become accessible as dynamic attributes using both method and hash-style access patterns.
39
-
40
- ### Automatic Parameter Loading
41
-
42
- When calling tasks, all parameters automatically become context attributes:
14
+ Context is automatically populated with all inputs passed to a task. All keys are normalized to symbols for consistent access:
43
15
 
44
16
  ```ruby
45
- class ProcessOrderTask < CMDx::Task
46
- required :user_id, type: :integer
47
- required :amount, type: :float
48
- optional :currency, default: "USD"
49
-
50
- def call
51
- # Parameters automatically available in context
52
- context.user_id # → 123
53
- context.amount # → 99.99
54
- context.currency # → "USD"
55
- end
56
- end
17
+ # Direct execution
18
+ CalculateShipping.execute(weight: 2.5, destination: "CA")
57
19
 
58
- ProcessOrderTask.call(user_id: 123, amount: 99.99)
20
+ # Instance creation
21
+ CalculateShipping.new(weight: 2.5, "destination" => "CA")
59
22
  ```
60
23
 
61
- ### Key Normalization
62
-
63
- All keys are automatically normalized to symbols for consistent access:
64
-
65
- ```ruby
66
- # String and symbol keys both work
67
- ProcessOrderTask.call("user_id" => 123, :amount => 99.99)
68
-
69
- # Both accessible as symbols
70
- context.user_id # → 123
71
- context.amount # → 99.99
72
- ```
24
+ > [!IMPORTANT]
25
+ > String keys are automatically converted to symbols. Use symbols for consistency in your code.
73
26
 
74
27
  ## Accessing Data
75
28
 
76
29
  Context provides multiple access patterns with automatic nil safety:
77
30
 
78
31
  ```ruby
79
- class ProcessOrderTask < CMDx::Task
80
- def call
81
- # Method-style access (preferred)
82
- user_id = context.user_id
83
- amount = context.amount
32
+ class CalculateShipping < CMDx::Task
33
+ def work
34
+ # Method style access (preferred)
35
+ weight = context.weight
36
+ destination = context.destination
84
37
 
85
- # Hash-style access
86
- order_id = context[:order_id]
87
- metadata = context["metadata"]
38
+ # Hash style access
39
+ service_type = context[:service_type]
40
+ options = context["options"]
88
41
 
89
42
  # Safe access with defaults
90
- priority = context.fetch!(:priority, "normal")
91
- source = context.dig(:metadata, :source)
43
+ rush_delivery = context.fetch!(:rush_delivery, false)
44
+ carrier = context.dig(:options, :carrier)
92
45
 
93
46
  # Shorter alias
94
- total = ctx.amount * ctx.tax_rate # ctx aliases context
47
+ cost = ctx.weight * ctx.rate_per_pound # ctx aliases context
95
48
  end
96
49
  end
97
50
  ```
98
51
 
99
- > [!NOTE]
100
- > Accessing undefined attributes returns `nil` instead of raising errors, enabling graceful handling of optional parameters.
101
-
102
- ### Type Safety
103
-
104
- Context accepts any data type without restrictions:
105
-
106
- ```ruby
107
- context.string_value = "Order #12345"
108
- context.numeric_value = 42
109
- context.array_value = [1, 2, 3]
110
- context.hash_value = { total: 99.99, tax: 8.99 }
111
- context.object_value = User.find(123)
112
- context.timestamp = Time.now
113
- ```
52
+ > [!IMPORTANT]
53
+ > Accessing undefined context attributes returns `nil` instead of raising errors, enabling graceful handling of optional attributes.
114
54
 
115
55
  ## Modifying Context
116
56
 
117
57
  Context supports dynamic modification during task execution:
118
58
 
119
59
  ```ruby
120
- class ProcessOrderTask < CMDx::Task
121
- def call
60
+ class CalculateShipping < CMDx::Task
61
+ def work
122
62
  # Direct assignment
123
- context.user = User.find(context.user_id)
124
- context.order = Order.find(context.order_id)
125
- context.processed_at = Time.now
63
+ context.carrier = Carrier.find_by(code: context.carrier_code)
64
+ context.package = Package.new(weight: context.weight)
65
+ context.calculated_at = Time.now
126
66
 
127
67
  # Hash-style assignment
128
- context[:status] = "processing"
129
- context["result_code"] = "SUCCESS"
68
+ context[:status] = "calculating"
69
+ context["tracking_number"] = "SHIP#{SecureRandom.hex(6)}"
130
70
 
131
71
  # Conditional assignment
132
- context.notification_sent ||= false
72
+ context.insurance_included ||= false
133
73
 
134
74
  # Batch updates
135
75
  context.merge!(
136
76
  status: "completed",
137
- total_amount: calculate_total,
138
- completion_time: Time.now
77
+ shipping_cost: calculate_cost,
78
+ estimated_delivery: Time.now + 3.days
139
79
  )
140
80
 
141
81
  # Remove sensitive data
142
- context.delete!(:credit_card_number)
82
+ context.delete!(:credit_card_token)
143
83
  end
144
84
 
145
85
  private
146
86
 
147
- def calculate_total
148
- context.amount + (context.amount * context.tax_rate)
87
+ def calculate_cost
88
+ base_rate = context.weight * context.rate_per_pound
89
+ base_rate + (base_rate * context.tax_percentage)
149
90
  end
150
91
  end
151
92
  ```
152
93
 
153
94
  > [!TIP]
154
- > Use context for both input parameters and intermediate results. This creates natural data flow through your task execution pipeline.
95
+ > Use context for both input values and intermediate results. This creates natural data flow through your task execution pipeline.
155
96
 
156
- ## Data Sharing Between Tasks
97
+ ## Data Sharing
157
98
 
158
99
  Context enables seamless data flow between related tasks in complex workflows:
159
100
 
160
- ### Task Composition
161
-
162
101
  ```ruby
163
- class ProcessOrderWorkflowTask < CMDx::Task
164
- def call
165
- # Validate order data
166
- validation_result = ValidateOrderTask.call(context)
167
- throw!(validation_result) unless validation_result.success?
102
+ # During execution
103
+ class CalculateShipping < CMDx::Task
104
+ def work
105
+ # Validate shipping data
106
+ validation_result = ValidateAddress.execute(context)
168
107
 
169
- # Process payment with enriched context
170
- payment_result = ProcessPaymentTask.call(context)
171
- throw!(payment_result) unless payment_result.success?
108
+ # Via context
109
+ CalculateInsurance.execute(context)
172
110
 
173
- # Send notifications with complete context
174
- NotifyOrderProcessedTask.call(context)
111
+ # Via result
112
+ NotifyShippingCalculated.execute(validation_result)
175
113
 
176
114
  # Context now contains accumulated data from all tasks
177
- context.order_validated # true (from validation)
178
- context.payment_processed # true (from payment)
179
- context.notification_sent # true (from notification)
115
+ context.address_validated #=> true (from validation)
116
+ context.insurance_calculated #=> true (from insurance)
117
+ context.notification_sent #=> true (from notification)
180
118
  end
181
119
  end
182
- ```
183
-
184
- ### Workflow Chains
185
-
186
- ```ruby
187
- # Initialize workflow context
188
- initial_data = { user_id: 123, product_ids: [1, 2, 3] }
189
120
 
190
- # Chain tasks with context flow
191
- validation_result = ValidateCartTask.call(initial_data)
121
+ # After execution
122
+ result = CalculateShipping.execute(destination: "New York, NY")
192
123
 
193
- if validation_result.success?
194
- # Context accumulates data through the chain
195
- inventory_result = CheckInventoryTask.call(validation_result.context)
196
- payment_result = ProcessPaymentTask.call(inventory_result.context)
197
- shipping_result = CreateShipmentTask.call(payment_result.context)
198
- end
124
+ CreateShippingLabel.execute(result)
199
125
  ```
200
126
 
201
- ## Result Object Context Passing
202
-
203
- > [!IMPORTANT]
204
- > CMDx automatically extracts context when Result objects are passed to task methods, enabling powerful workflow compositions where task output becomes the next task's input.
205
-
206
- ```ruby
207
- # Seamless task chaining
208
- extraction_result = ExtractDataTask.call(source_id: 123)
209
- processing_result = ProcessDataTask.call(extraction_result)
210
-
211
- # Context flows automatically between tasks
212
- processing_result.context.source_id # → 123 (from first task)
213
- processing_result.context.extracted_records # → [...] (from first task)
214
- processing_result.context.processed_count # → 50 (from second task)
215
- ```
216
-
217
- ### Error Propagation in Chains
218
-
219
- ```ruby
220
- # Non-raising chain with error handling
221
- extraction_result = ExtractDataTask.call(source_id: 123)
222
-
223
- if extraction_result.failed?
224
- # Context preserved even in failure scenarios
225
- error_handler_result = HandleExtractionErrorTask.call(extraction_result)
226
- return error_handler_result
227
- end
228
-
229
- # Continue processing with successful result
230
- ProcessDataTask.call(extraction_result)
231
- ```
232
-
233
- ### Exception-Based Chains
234
-
235
- ```ruby
236
- begin
237
- # Raising version propagates exceptions while preserving context
238
- extraction_result = ExtractDataTask.call!(source_id: 123)
239
- processing_result = ProcessDataTask.call!(extraction_result)
240
- notification_result = NotifyCompletionTask.call!(processing_result)
241
- rescue CMDx::Failed => e
242
- # Access failed task's context for error analysis
243
- ErrorReportingTask.call(
244
- error: e.message,
245
- failed_context: e.result.context,
246
- user_id: e.result.context.user_id
247
- )
248
- end
249
- ```
250
-
251
- ## Context Inspection and Debugging
252
-
253
- Context provides comprehensive inspection capabilities for debugging and logging:
254
-
255
- ```ruby
256
- class DebuggableTask < CMDx::Task
257
- def call
258
- # Log current context state
259
- Rails.logger.info "Context: #{context.inspect}"
260
-
261
- # Convert to hash for serialization
262
- context_data = context.to_h
263
- # → { user_id: 123, amount: 99.99, status: "processing" }
264
-
265
- # Iterate over context data
266
- context.each_pair do |key, value|
267
- puts "#{key}: #{value.class} = #{value}"
268
- end
269
-
270
- # Check for specific keys
271
- has_user = context.key?(:user_id) # → true
272
- has_admin = context.key?(:admin_mode) # → false
273
- end
274
- end
275
- ```
276
-
277
- ### Production Logging
278
-
279
- ```ruby
280
- class OrderProcessingTask < CMDx::Task
281
- def call
282
- log_context_snapshot("start")
283
-
284
- process_order
285
-
286
- log_context_snapshot("complete")
287
- end
288
-
289
- private
290
-
291
- def log_context_snapshot(stage)
292
- Rails.logger.info({
293
- stage: stage,
294
- task: self.class.name,
295
- context: context.to_h.except(:sensitive_data)
296
- }.to_json)
297
- end
298
- end
299
- ```
300
-
301
- ## Error Handling
302
-
303
- > [!WARNING]
304
- > Context operations are generally safe, but understanding error scenarios helps build robust applications.
305
-
306
- ### Safe Access Patterns
307
-
308
- ```ruby
309
- class RobustTask < CMDx::Task
310
- def call
311
- # Safe: returns nil for missing attributes
312
- user_id = context.user_id || 'anonymous'
313
-
314
- # Safe: fetch with default
315
- timeout = context.fetch!(:timeout, 30)
316
-
317
- # Safe: deep access with nil protection
318
- api_key = context.dig(:credentials, :api_key)
319
-
320
- # Safe: conditional assignment
321
- context.processed_at ||= Time.now
322
- end
323
- end
324
- ```
325
-
326
- ### Common Error Scenarios
327
-
328
- ```ruby
329
- # Missing required context data
330
- class PaymentTask < CMDx::Task
331
- def call
332
- # Check for required context before proceeding
333
- unless context.user_id && context.amount
334
- context.error_message = "Missing required payment data"
335
- fail!(reason: "Cannot process payment")
336
- end
337
-
338
- process_payment
339
- end
340
- end
341
-
342
- # Invalid context modifications
343
- class ValidationTask < CMDx::Task
344
- def call
345
- # Context cannot be replaced entirely
346
- # context = {} # This won't work as expected
347
-
348
- # Instead, clear individual keys or use merge!
349
- context.delete!(:temporary_data)
350
- context.merge!(validation_status: "complete")
351
- end
352
- end
353
- ```
354
-
355
- > [!TIP]
356
- > Use context inspection methods liberally during development and testing. The `to_h` method is particularly useful for logging and debugging complex workflows.
357
-
358
- [Learn more](../../lib/cmdx/lazy_struct.rb) about the `LazyStruct` implementation that powers context functionality.
359
-
360
127
  ---
361
128
 
362
- - **Prev:** [Basics - Call](call.md)
129
+ - **Prev:** [Basics - Execution](execution.md)
363
130
  - **Next:** [Basics - Chain](chain.md)
@@ -0,0 +1,114 @@
1
+ # Basics - Execution
2
+
3
+ Task execution in CMDx provides two distinct methods that handle success and halt scenarios differently. Understanding when to use each method is crucial for proper error handling and control flow in your application workflows.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Methods Overview](#methods-overview)
8
+ - [Non-bang Execution](#non-bang-execution)
9
+ - [Bang Execution](#bang-execution)
10
+ - [Direct Instantiation](#direct-instantiation)
11
+ - [Result Details](#result-details)
12
+
13
+ ## Methods Overview
14
+
15
+ Tasks are single-use objects. Once executed, they are frozen and cannot be executed again.
16
+ Create a new instance for subsequent executions.
17
+
18
+ | Method | Returns | Exceptions | Use Case |
19
+ |--------|---------|------------|----------|
20
+ | `execute` | Always returns `CMDx::Result` | Never raises | Predictable result handling |
21
+ | `execute!` | Returns `CMDx::Result` on success | Raises `CMDx::Fault` when skipped or failed | Exception-based control flow |
22
+
23
+ ## Non-bang Execution
24
+
25
+ The `execute` method always returns a `CMDx::Result` object regardless of execution outcome.
26
+ This is the preferred method for most use cases.
27
+
28
+ Any unhandled exceptions will be caught and returned as a task failure.
29
+
30
+ ```ruby
31
+ result = CreateAccount.execute(email: "user@example.com")
32
+
33
+ # Check execution state
34
+ result.success? #=> true/false
35
+ result.failed? #=> true/false
36
+ result.skipped? #=> true/false
37
+
38
+ # Access result data
39
+ result.context.email #=> "user@example.com"
40
+ result.state #=> "complete"
41
+ result.status #=> "success"
42
+ ```
43
+
44
+ ## Bang Execution
45
+
46
+ The bang `execute!` method raises a `CMDx::Fault` based exception when tasks fail or are skipped, and returns a `CMDx::Result` object only on success.
47
+
48
+ It raises any unhandled non-fault exceptions caused during execution.
49
+
50
+ | Exception | Raised When |
51
+ |-----------|-------------|
52
+ | `CMDx::FailFault` | Task execution fails |
53
+ | `CMDx::SkipFault` | Task execution is skipped |
54
+
55
+ > [!IMPORTANT]
56
+ > `execute!` behavior depends on the `task_breakpoints` or `workflow_breakpoints` configuration. By default, it raises exceptions only on failures.
57
+
58
+ ```ruby
59
+ begin
60
+ result = CreateAccount.execute!(email: "user@example.com")
61
+ SendWelcomeEmail.execute(result.context)
62
+ rescue CMDx::FailFault => e
63
+ ScheduleAccountRetryJob.perform_later(e.result.context.email)
64
+ rescue CMDx::SkipFault => e
65
+ Rails.logger.info("Account creation skipped: #{e.result.reason}")
66
+ rescue Exception => e
67
+ ErrorTracker.capture(unhandled_exception: e)
68
+ end
69
+ ```
70
+
71
+ ## Direct Instantiation
72
+
73
+ Tasks can be instantiated directly for advanced use cases, testing, and custom execution patterns:
74
+
75
+ ```ruby
76
+ # Direct instantiation
77
+ task = CreateAccount.new(email: "user@example.com", send_welcome: true)
78
+
79
+ # Access properties before execution
80
+ task.id #=> "abc123..." (unique task ID)
81
+ task.context.email #=> "user@example.com"
82
+ task.context.send_welcome #=> true
83
+ task.result.state #=> "initialized"
84
+ task.result.status #=> "success"
85
+
86
+ # Manual execution
87
+ task.execute
88
+ # or
89
+ task.execute!
90
+
91
+ task.result.success? #=> true/false
92
+ ```
93
+
94
+ ## Result Details
95
+
96
+ The `Result` object provides comprehensive execution information:
97
+
98
+ ```ruby
99
+ result = CreateAccount.execute(email: "user@example.com")
100
+
101
+ # Execution metadata
102
+ result.id #=> "abc123..." (unique execution ID)
103
+ result.task #=> CreateAccount instance (frozen)
104
+ result.chain #=> Task execution chain
105
+
106
+ # Context and metadata
107
+ result.context #=> Context with all task data
108
+ result.metadata #=> Hash with execution metadata
109
+ ```
110
+
111
+ ---
112
+
113
+ - **Prev:** [Basics - Setup](setup.md)
114
+ - **Next:** [Basics - Context](context.md)