cmdx 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +13 -12
  4. data/.cursor/prompts/yardoc.md +11 -6
  5. data/CHANGELOG.md +13 -2
  6. data/README.md +1 -0
  7. data/docs/ai_prompts.md +269 -195
  8. data/docs/basics/call.md +124 -58
  9. data/docs/basics/chain.md +190 -160
  10. data/docs/basics/context.md +242 -154
  11. data/docs/basics/setup.md +302 -32
  12. data/docs/callbacks.md +390 -94
  13. data/docs/configuration.md +181 -65
  14. data/docs/deprecation.md +245 -0
  15. data/docs/getting_started.md +161 -39
  16. data/docs/internationalization.md +590 -70
  17. data/docs/interruptions/exceptions.md +135 -118
  18. data/docs/interruptions/faults.md +150 -125
  19. data/docs/interruptions/halt.md +134 -80
  20. data/docs/logging.md +181 -118
  21. data/docs/middlewares.md +150 -377
  22. data/docs/outcomes/result.md +140 -112
  23. data/docs/outcomes/states.md +134 -99
  24. data/docs/outcomes/statuses.md +204 -146
  25. data/docs/parameters/coercions.md +232 -281
  26. data/docs/parameters/defaults.md +224 -169
  27. data/docs/parameters/definitions.md +289 -141
  28. data/docs/parameters/namespacing.md +250 -161
  29. data/docs/parameters/validations.md +260 -133
  30. data/docs/testing.md +191 -197
  31. data/docs/workflows.md +143 -98
  32. data/lib/cmdx/callback.rb +23 -19
  33. data/lib/cmdx/callback_registry.rb +1 -3
  34. data/lib/cmdx/chain_inspector.rb +23 -23
  35. data/lib/cmdx/chain_serializer.rb +38 -19
  36. data/lib/cmdx/coercion.rb +20 -12
  37. data/lib/cmdx/coercion_registry.rb +51 -32
  38. data/lib/cmdx/configuration.rb +84 -31
  39. data/lib/cmdx/context.rb +32 -21
  40. data/lib/cmdx/core_ext/hash.rb +13 -13
  41. data/lib/cmdx/core_ext/module.rb +1 -1
  42. data/lib/cmdx/core_ext/object.rb +12 -12
  43. data/lib/cmdx/correlator.rb +60 -39
  44. data/lib/cmdx/errors.rb +105 -131
  45. data/lib/cmdx/fault.rb +66 -45
  46. data/lib/cmdx/immutator.rb +20 -21
  47. data/lib/cmdx/lazy_struct.rb +78 -70
  48. data/lib/cmdx/log_formatters/json.rb +1 -1
  49. data/lib/cmdx/log_formatters/key_value.rb +1 -1
  50. data/lib/cmdx/log_formatters/line.rb +1 -1
  51. data/lib/cmdx/log_formatters/logstash.rb +1 -1
  52. data/lib/cmdx/log_formatters/pretty_json.rb +1 -1
  53. data/lib/cmdx/log_formatters/pretty_key_value.rb +1 -1
  54. data/lib/cmdx/log_formatters/pretty_line.rb +1 -1
  55. data/lib/cmdx/log_formatters/raw.rb +2 -2
  56. data/lib/cmdx/logger.rb +19 -14
  57. data/lib/cmdx/logger_ansi.rb +33 -17
  58. data/lib/cmdx/logger_serializer.rb +85 -24
  59. data/lib/cmdx/middleware.rb +39 -21
  60. data/lib/cmdx/middleware_registry.rb +4 -3
  61. data/lib/cmdx/parameter.rb +151 -89
  62. data/lib/cmdx/parameter_inspector.rb +34 -21
  63. data/lib/cmdx/parameter_registry.rb +36 -30
  64. data/lib/cmdx/parameter_serializer.rb +21 -14
  65. data/lib/cmdx/result.rb +136 -135
  66. data/lib/cmdx/result_ansi.rb +31 -17
  67. data/lib/cmdx/result_inspector.rb +32 -27
  68. data/lib/cmdx/result_logger.rb +23 -14
  69. data/lib/cmdx/result_serializer.rb +65 -27
  70. data/lib/cmdx/task.rb +234 -113
  71. data/lib/cmdx/task_deprecator.rb +22 -25
  72. data/lib/cmdx/task_processor.rb +89 -88
  73. data/lib/cmdx/task_serializer.rb +27 -14
  74. data/lib/cmdx/utils/monotonic_runtime.rb +2 -4
  75. data/lib/cmdx/validator.rb +25 -16
  76. data/lib/cmdx/validator_registry.rb +53 -31
  77. data/lib/cmdx/validators/exclusion.rb +1 -1
  78. data/lib/cmdx/validators/format.rb +2 -2
  79. data/lib/cmdx/validators/inclusion.rb +2 -2
  80. data/lib/cmdx/validators/length.rb +2 -2
  81. data/lib/cmdx/validators/numeric.rb +3 -3
  82. data/lib/cmdx/validators/presence.rb +2 -2
  83. data/lib/cmdx/version.rb +1 -1
  84. data/lib/cmdx/workflow.rb +54 -33
  85. data/lib/generators/cmdx/task_generator.rb +6 -6
  86. data/lib/generators/cmdx/workflow_generator.rb +6 -6
  87. metadata +3 -1
@@ -1,82 +1,127 @@
1
1
  # Basics - Context
2
2
 
3
- The task `context` provides flexible data storage and sharing for task objects.
4
- Built on `LazyStruct`, context enables dynamic attribute access, parameter
5
- validation, and seamless data flow between related tasks.
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.
6
4
 
7
5
  ## Table of Contents
8
6
 
9
7
  - [TLDR](#tldr)
10
- - [Loading Parameters](#loading-parameters)
8
+ - [Context Fundamentals](#context-fundamentals)
11
9
  - [Accessing Data](#accessing-data)
12
10
  - [Modifying Context](#modifying-context)
13
- - [Context Features](#context-features)
14
11
  - [Data Sharing Between Tasks](#data-sharing-between-tasks)
15
12
  - [Result Object Context Passing](#result-object-context-passing)
16
- - [Context Inspection](#context-inspection)
13
+ - [Context Inspection and Debugging](#context-inspection-and-debugging)
14
+ - [Error Handling](#error-handling)
17
15
 
18
16
  ## TLDR
19
17
 
20
- - **Dynamic attributes** - Access data with `context.user_id` or `context[:user_id]`
21
- - **Automatic loading** - Parameters become context attributes automatically
22
- - **Modification** - Assign data with `context.user = User.find(id)`
23
- - **Sharing** - Pass context between tasks with `SomeTask.call(context)`
24
- - **Nil safety** - Missing attributes return `nil` instead of raising errors
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
25
36
 
26
- ## Loading Parameters
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.
27
39
 
28
- Context is automatically populated when calling tasks with parameters. All
29
- parameters become accessible as dynamic attributes within the task.
40
+ ### Automatic Parameter Loading
41
+
42
+ When calling tasks, all parameters automatically become context attributes:
30
43
 
31
44
  ```ruby
32
- ProcessUserOrderTask.call(
33
- user: User.first,
34
- order_id: 456,
35
- send_notification: true
36
- )
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
57
+
58
+ ProcessOrderTask.call(user_id: 123, amount: 99.99)
37
59
  ```
38
60
 
39
- ## Accessing Data
61
+ ### Key Normalization
40
62
 
41
- Context provides multiple ways to access stored data with automatic key
42
- normalization to symbols:
63
+ All keys are automatically normalized to symbols for consistent access:
43
64
 
44
65
  ```ruby
45
- class ProcessUserOrderTask < CMDx::Task
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
+ ```
73
+
74
+ ## Accessing Data
46
75
 
76
+ Context provides multiple access patterns with automatic nil safety:
77
+
78
+ ```ruby
79
+ class ProcessOrderTask < CMDx::Task
47
80
  def call
48
81
  # Method-style access (preferred)
49
- context.user_id #=> 123
50
- context.send_notification #=> true
82
+ user_id = context.user_id
83
+ amount = context.amount
51
84
 
52
85
  # Hash-style access
53
- context[:order_id] #=> 456
54
- context["user_id"] #=> 123
86
+ order_id = context[:order_id]
87
+ metadata = context["metadata"]
55
88
 
56
89
  # Safe access with defaults
57
- context.fetch!(:priority, "normal") #=> "high"
90
+ priority = context.fetch!(:priority, "normal")
91
+ source = context.dig(:metadata, :source)
58
92
 
59
- # Deep access for nested data
60
- context.dig(:metadata, :source) #=> "mobile"
61
-
62
- # Alias for shorter code
63
- ctx.user_id #=> 123 (ctx is alias for context)
93
+ # Shorter alias
94
+ total = ctx.amount * ctx.tax_rate # ctx aliases context
64
95
  end
65
-
66
96
  end
67
97
  ```
68
98
 
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
+ ```
114
+
69
115
  ## Modifying Context
70
116
 
71
117
  Context supports dynamic modification during task execution:
72
118
 
73
119
  ```ruby
74
- class ProcessUserOrderTask < CMDx::Task
75
-
120
+ class ProcessOrderTask < CMDx::Task
76
121
  def call
77
122
  # Direct assignment
78
- context.user = User.find(user_id)
79
- context.order = Order.find(order_id)
123
+ context.user = User.find(context.user_id)
124
+ context.order = Order.find(context.order_id)
80
125
  context.processed_at = Time.now
81
126
 
82
127
  # Hash-style assignment
@@ -89,185 +134,228 @@ class ProcessUserOrderTask < CMDx::Task
89
134
  # Batch updates
90
135
  context.merge!(
91
136
  status: "completed",
92
- processed_by: current_user.id,
137
+ total_amount: calculate_total,
93
138
  completion_time: Time.now
94
139
  )
95
140
 
96
- # Removing data
97
- context.delete!(:temporary_data)
141
+ # Remove sensitive data
142
+ context.delete!(:credit_card_number)
98
143
  end
99
144
 
145
+ private
146
+
147
+ def calculate_total
148
+ context.amount + (context.amount * context.tax_rate)
149
+ end
100
150
  end
101
151
  ```
102
152
 
103
153
  > [!TIP]
104
- > Use context for both input parameters and intermediate results. This creates
105
- > a natural data flow through your task execution pipeline.
154
+ > Use context for both input parameters and intermediate results. This creates natural data flow through your task execution pipeline.
106
155
 
107
- ## Context Features
156
+ ## Data Sharing Between Tasks
108
157
 
109
- ### Key Normalization
158
+ Context enables seamless data flow between related tasks in complex workflows:
110
159
 
111
- All keys are automatically converted to symbols for consistent access:
160
+ ### Task Composition
112
161
 
113
162
  ```ruby
114
- SomeTask.call("user_id" => 123, :order_id => 456)
115
-
116
- # Both accessible as symbols
117
- context.user_id #=> 123
118
- context.order_id #=> 456
119
- ```
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?
120
168
 
121
- ### Nil Safety
169
+ # Process payment with enriched context
170
+ payment_result = ProcessPaymentTask.call(context)
171
+ throw!(payment_result) unless payment_result.success?
122
172
 
123
- Accessing undefined attributes returns `nil` instead of raising errors:
173
+ # Send notifications with complete context
174
+ NotifyOrderProcessedTask.call(context)
124
175
 
125
- ```ruby
126
- context.undefined_attribute #=> nil
127
- context[:missing_key] #=> nil
176
+ # 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)
180
+ end
181
+ end
128
182
  ```
129
183
 
130
- > [!NOTE]
131
- > Context attributes that are **NOT** loaded will return `nil` rather than
132
- > raising an error. This allows for graceful handling of optional parameters.
184
+ ### Workflow Chains
133
185
 
134
- ### Type Flexibility
186
+ ```ruby
187
+ # Initialize workflow context
188
+ initial_data = { user_id: 123, product_ids: [1, 2, 3] }
135
189
 
136
- Context accepts any data type without restrictions:
190
+ # Chain tasks with context flow
191
+ validation_result = ValidateCartTask.call(initial_data)
137
192
 
138
- ```ruby
139
- context.string_value = "Order processed"
140
- context.numeric_value = 42
141
- context.array_value = [1, 2, 3]
142
- context.hash_value = { total: 99.99, currency: "USD" }
143
- context.object_value = User.find(123)
144
- context.proc_value = -> { "dynamic value" }
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
145
199
  ```
146
200
 
147
- ## Data Sharing Between Tasks
201
+ ## Result Object Context Passing
148
202
 
149
- Context objects can be passed between tasks, enabling data flow in complex
150
- workflows:
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.
151
205
 
152
- ### Within Task Composition
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
153
218
 
154
219
  ```ruby
155
- class ProcessUserOrderTask < CMDx::Task
220
+ # Non-raising chain with error handling
221
+ extraction_result = ExtractDataTask.call(source_id: 123)
156
222
 
157
- def call
158
- # Subtasks inherit and modify the same context
159
- validation_result = ValidateUserOrderTask.call(context)
160
- throw!(validation_result) unless validation_result.success?
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
161
228
 
162
- payment_result = ProcessOrderPaymentTask.call(context)
163
- throw!(payment_result) unless payment_result.success?
229
+ # Continue processing with successful result
230
+ ProcessDataTask.call(extraction_result)
231
+ ```
164
232
 
165
- # Context now contains data from all subtasks
166
- context.order_validated #=> true (from ValidateUserOrderTask)
167
- context.payment_processed #=> true (from ProcessOrderPaymentTask)
168
- end
233
+ ### Exception-Based Chains
169
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
+ )
170
248
  end
171
249
  ```
172
250
 
173
- ### After Task Completion
251
+ ## Context Inspection and Debugging
252
+
253
+ Context provides comprehensive inspection capabilities for debugging and logging:
174
254
 
175
255
  ```ruby
176
- # Chain task results using context
177
- validation_result = ValidateUserOrderTask.call(user_id: 123, order_id: 456)
256
+ class DebuggableTask < CMDx::Task
257
+ def call
258
+ # Log current context state
259
+ Rails.logger.info "Context: #{context.inspect}"
178
260
 
179
- if validation_result.success?
180
- # Pass accumulated context to next task
181
- process_result = ProcessUserOrderTask.call(validation_result)
261
+ # Convert to hash for serialization
262
+ context_data = context.to_h
263
+ # { user_id: 123, amount: 99.99, status: "processing" }
182
264
 
183
- # Continue chain with enriched context
184
- notification_result = SendOrderNotificationTask.call(process_result.context)
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
185
274
  end
186
275
  ```
187
276
 
188
- ### Workflow Processing
277
+ ### Production Logging
189
278
 
190
279
  ```ruby
191
- # Context maintains continuity across workflow operations
192
- initial_context = {
193
- user_id: 123,
194
- action: "bulk_order_processing"
195
- }
196
-
197
- results = [
198
- ValidateOrderDataTask.call(initial_context),
199
- ProcessOrderPaymentTask.call(initial_context),
200
- UpdateInventoryTask.call(initial_context)
201
- ]
202
-
203
- # All tasks share and modify the same context data
204
- results.first.context.validation_completed #=> true
205
- results.last.context.inventory_updated #=> true
206
- ```
280
+ class OrderProcessingTask < CMDx::Task
281
+ def call
282
+ log_context_snapshot("start")
207
283
 
208
- ## Result Object Context Passing
284
+ process_order
209
285
 
210
- CMDx supports automatic context extraction when Result objects are passed to task
211
- `new` or `call` methods. This enables seamless task chaining where the output of
212
- one task becomes the input for the next, creating powerful workflow compositions.
286
+ log_context_snapshot("complete")
287
+ end
213
288
 
214
- ```ruby
215
- # Chain tasks by passing Result objects
216
- extraction_result = ExtractDataTask.call(source_id: 123)
217
- processing_result = ProcessDataTask.call(extraction_result)
289
+ private
218
290
 
219
- # Context flows automatically between tasks
220
- processing_result.context.source_id #=> 123 (from first task)
221
- processing_result.context.extracted_data #=> [data...] (from first task)
222
- processing_result.context.extraction_time #=> 2024-01-01 10:00:00 (from first task)
223
- processing_result.context.processed_data #=> [processed...] (from second task)
224
- processing_result.context.processing_time #=> 2024-01-01 10:00:05 (from second task)
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
225
299
  ```
226
300
 
227
- ### Error Handling in Chains
301
+ ## Error Handling
302
+
303
+ > [!WARNING]
304
+ > Context operations are generally safe, but understanding error scenarios helps build robust applications.
228
305
 
229
- Result object chaining works seamlessly with both `call` and `call!` methods:
306
+ ### Safe Access Patterns
230
307
 
231
308
  ```ruby
232
- # Non-raising version (returns failed results)
233
- extraction_result = ExtractDataTask.call(source_id: 123)
234
- if extraction_result.failed?
235
- # Handle failure, but can still pass context to error handler
236
- error_result = HandleErrorTask.call(extraction_result)
237
- end
309
+ class RobustTask < CMDx::Task
310
+ def call
311
+ # Safe: returns nil for missing attributes
312
+ user_id = context.user_id || 'anonymous'
238
313
 
239
- # Raising version (propagates exceptions)
240
- begin
241
- extraction_result = ExtractDataTask.call!(source_id: 123)
242
- processing_result = ProcessDataTask.call!(extraction_result)
243
- rescue CMDx::Failed => e
244
- # Handle any failure in the chain
245
- error_result = HandleErrorTask.call(e.result)
246
- end
247
- ```
314
+ # Safe: fetch with default
315
+ timeout = context.fetch!(:timeout, 30)
248
316
 
249
- > [!TIP]
250
- > Result object chaining is particularly powerful when combined with [Workflows](../workflows.md)
251
- > processing, where multiple tasks can operate on shared context while maintaining
252
- > individual result tracking.
317
+ # Safe: deep access with nil protection
318
+ api_key = context.dig(:credentials, :api_key)
253
319
 
254
- ## Context Inspection
320
+ # Safe: conditional assignment
321
+ context.processed_at ||= Time.now
322
+ end
323
+ end
324
+ ```
255
325
 
256
- Context provides inspection methods for debugging and logging:
326
+ ### Common Error Scenarios
257
327
 
258
328
  ```ruby
259
- # Hash representation
260
- context.to_h #=> { user_id: 123, order_id: 456, ... }
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
261
337
 
262
- # Human-readable inspection
263
- context.inspect #=> "#<CMDx::Context :user_id=123 :order_id=456>"
338
+ process_payment
339
+ end
340
+ end
264
341
 
265
- # Iteration
266
- context.each_pair { |key, value| puts "#{key}: #{value}" }
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
267
353
  ```
268
354
 
269
- [Learn more](../../lib/cmdx/lazy_struct.rb)
270
- about the `LazyStruct` public API that powers context functionality.
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.
271
359
 
272
360
  ---
273
361