cmdx 1.0.0 → 1.1.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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/rspec.md +20 -0
  3. data/.cursor/prompts/yardoc.md +8 -0
  4. data/.rubocop.yml +5 -0
  5. data/CHANGELOG.md +101 -49
  6. data/README.md +2 -1
  7. data/docs/ai_prompts.md +10 -0
  8. data/docs/basics/call.md +11 -2
  9. data/docs/basics/chain.md +10 -1
  10. data/docs/basics/context.md +9 -0
  11. data/docs/basics/setup.md +9 -0
  12. data/docs/callbacks.md +14 -37
  13. data/docs/configuration.md +68 -27
  14. data/docs/getting_started.md +11 -0
  15. data/docs/internationalization.md +148 -0
  16. data/docs/interruptions/exceptions.md +10 -1
  17. data/docs/interruptions/faults.md +11 -2
  18. data/docs/interruptions/halt.md +9 -0
  19. data/docs/logging.md +14 -4
  20. data/docs/middlewares.md +53 -43
  21. data/docs/outcomes/result.md +9 -0
  22. data/docs/outcomes/states.md +9 -0
  23. data/docs/outcomes/statuses.md +9 -0
  24. data/docs/parameters/coercions.md +58 -38
  25. data/docs/parameters/defaults.md +10 -1
  26. data/docs/parameters/definitions.md +9 -0
  27. data/docs/parameters/namespacing.md +9 -0
  28. data/docs/parameters/validations.md +8 -67
  29. data/docs/testing.md +22 -13
  30. data/docs/tips_and_tricks.md +9 -0
  31. data/docs/workflows.md +14 -4
  32. data/lib/cmdx/.DS_Store +0 -0
  33. data/lib/cmdx/callback.rb +36 -56
  34. data/lib/cmdx/callback_registry.rb +82 -73
  35. data/lib/cmdx/chain.rb +65 -122
  36. data/lib/cmdx/chain_inspector.rb +22 -115
  37. data/lib/cmdx/chain_serializer.rb +17 -148
  38. data/lib/cmdx/coercion.rb +49 -0
  39. data/lib/cmdx/coercion_registry.rb +94 -0
  40. data/lib/cmdx/coercions/array.rb +18 -36
  41. data/lib/cmdx/coercions/big_decimal.rb +21 -33
  42. data/lib/cmdx/coercions/boolean.rb +21 -40
  43. data/lib/cmdx/coercions/complex.rb +18 -31
  44. data/lib/cmdx/coercions/date.rb +20 -39
  45. data/lib/cmdx/coercions/date_time.rb +22 -39
  46. data/lib/cmdx/coercions/float.rb +19 -32
  47. data/lib/cmdx/coercions/hash.rb +22 -41
  48. data/lib/cmdx/coercions/integer.rb +20 -33
  49. data/lib/cmdx/coercions/rational.rb +20 -32
  50. data/lib/cmdx/coercions/string.rb +23 -31
  51. data/lib/cmdx/coercions/time.rb +24 -40
  52. data/lib/cmdx/coercions/virtual.rb +14 -31
  53. data/lib/cmdx/configuration.rb +57 -171
  54. data/lib/cmdx/context.rb +22 -165
  55. data/lib/cmdx/core_ext/hash.rb +42 -67
  56. data/lib/cmdx/core_ext/module.rb +35 -79
  57. data/lib/cmdx/core_ext/object.rb +63 -98
  58. data/lib/cmdx/correlator.rb +40 -156
  59. data/lib/cmdx/error.rb +37 -202
  60. data/lib/cmdx/errors.rb +165 -202
  61. data/lib/cmdx/fault.rb +55 -158
  62. data/lib/cmdx/faults.rb +26 -137
  63. data/lib/cmdx/immutator.rb +22 -109
  64. data/lib/cmdx/lazy_struct.rb +103 -187
  65. data/lib/cmdx/log_formatters/json.rb +14 -40
  66. data/lib/cmdx/log_formatters/key_value.rb +14 -40
  67. data/lib/cmdx/log_formatters/line.rb +14 -48
  68. data/lib/cmdx/log_formatters/logstash.rb +14 -57
  69. data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
  70. data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
  71. data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
  72. data/lib/cmdx/log_formatters/raw.rb +19 -49
  73. data/lib/cmdx/logger.rb +20 -82
  74. data/lib/cmdx/logger_ansi.rb +18 -75
  75. data/lib/cmdx/logger_serializer.rb +24 -114
  76. data/lib/cmdx/middleware.rb +38 -60
  77. data/lib/cmdx/middleware_registry.rb +81 -77
  78. data/lib/cmdx/middlewares/correlate.rb +41 -226
  79. data/lib/cmdx/middlewares/timeout.rb +46 -185
  80. data/lib/cmdx/parameter.rb +120 -198
  81. data/lib/cmdx/parameter_evaluator.rb +231 -0
  82. data/lib/cmdx/parameter_inspector.rb +25 -56
  83. data/lib/cmdx/parameter_registry.rb +59 -84
  84. data/lib/cmdx/parameter_serializer.rb +23 -74
  85. data/lib/cmdx/railtie.rb +24 -107
  86. data/lib/cmdx/result.rb +254 -260
  87. data/lib/cmdx/result_ansi.rb +19 -85
  88. data/lib/cmdx/result_inspector.rb +27 -68
  89. data/lib/cmdx/result_logger.rb +18 -81
  90. data/lib/cmdx/result_serializer.rb +28 -132
  91. data/lib/cmdx/rspec/matchers.rb +28 -0
  92. data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
  93. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
  94. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
  95. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
  96. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
  97. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
  98. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
  99. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
  100. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
  101. data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
  102. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
  103. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
  104. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
  105. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
  106. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
  107. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
  108. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
  109. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
  110. data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
  111. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
  112. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
  113. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
  114. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
  115. data/lib/cmdx/task.rb +213 -425
  116. data/lib/cmdx/task_deprecator.rb +55 -0
  117. data/lib/cmdx/task_processor.rb +245 -0
  118. data/lib/cmdx/task_serializer.rb +22 -70
  119. data/lib/cmdx/utils/ansi_color.rb +13 -89
  120. data/lib/cmdx/utils/log_timestamp.rb +13 -42
  121. data/lib/cmdx/utils/monotonic_runtime.rb +13 -63
  122. data/lib/cmdx/utils/name_affix.rb +21 -71
  123. data/lib/cmdx/validator.rb +48 -0
  124. data/lib/cmdx/validator_registry.rb +86 -0
  125. data/lib/cmdx/validators/exclusion.rb +55 -94
  126. data/lib/cmdx/validators/format.rb +31 -85
  127. data/lib/cmdx/validators/inclusion.rb +65 -110
  128. data/lib/cmdx/validators/length.rb +117 -133
  129. data/lib/cmdx/validators/numeric.rb +123 -130
  130. data/lib/cmdx/validators/presence.rb +38 -79
  131. data/lib/cmdx/version.rb +1 -7
  132. data/lib/cmdx/workflow.rb +46 -339
  133. data/lib/cmdx.rb +1 -1
  134. data/lib/generators/cmdx/install_generator.rb +14 -31
  135. data/lib/generators/cmdx/task_generator.rb +39 -55
  136. data/lib/generators/cmdx/templates/install.rb +61 -11
  137. data/lib/generators/cmdx/workflow_generator.rb +41 -66
  138. data/lib/locales/ar.yml +35 -0
  139. data/lib/locales/cs.yml +35 -0
  140. data/lib/locales/da.yml +35 -0
  141. data/lib/locales/de.yml +35 -0
  142. data/lib/locales/el.yml +35 -0
  143. data/lib/locales/en.yml +19 -20
  144. data/lib/locales/es.yml +19 -20
  145. data/lib/locales/fi.yml +35 -0
  146. data/lib/locales/fr.yml +35 -0
  147. data/lib/locales/he.yml +35 -0
  148. data/lib/locales/hi.yml +35 -0
  149. data/lib/locales/it.yml +35 -0
  150. data/lib/locales/ja.yml +35 -0
  151. data/lib/locales/ko.yml +35 -0
  152. data/lib/locales/nl.yml +35 -0
  153. data/lib/locales/no.yml +35 -0
  154. data/lib/locales/pl.yml +35 -0
  155. data/lib/locales/pt.yml +35 -0
  156. data/lib/locales/ru.yml +35 -0
  157. data/lib/locales/sv.yml +35 -0
  158. data/lib/locales/th.yml +35 -0
  159. data/lib/locales/tr.yml +35 -0
  160. data/lib/locales/vi.yml +35 -0
  161. data/lib/locales/zh.yml +35 -0
  162. metadata +57 -8
  163. data/lib/cmdx/parameter_validator.rb +0 -81
  164. data/lib/cmdx/parameter_value.rb +0 -244
  165. data/lib/cmdx/parameters_inspector.rb +0 -72
  166. data/lib/cmdx/parameters_serializer.rb +0 -115
  167. data/lib/cmdx/rspec/result_matchers.rb +0 -917
  168. data/lib/cmdx/rspec/task_matchers.rb +0 -570
  169. data/lib/cmdx/validators/custom.rb +0 -102
data/docs/middlewares.md CHANGED
@@ -4,6 +4,7 @@ Middleware provides Rack-style wrappers around task execution for cross-cutting
4
4
 
5
5
  ## Table of Contents
6
6
 
7
+ - [TLDR](#tldr)
7
8
  - [Using Middleware](#using-middleware)
8
9
  - [Class Middleware](#class-middleware)
9
10
  - [Instance Middleware](#instance-middleware)
@@ -16,15 +17,24 @@ Middleware provides Rack-style wrappers around task execution for cross-cutting
16
17
  - [Correlate Middleware](#correlate-middleware)
17
18
  - [Writing Custom Middleware](#writing-custom-middleware)
18
19
 
20
+ ## TLDR
21
+
22
+ - **Purpose** - Rack-style wrappers for cross-cutting concerns (auth, logging, caching)
23
+ - **Declaration** - Use `use` method with classes, instances, or procs
24
+ - **Execution order** - Nested fashion (first declared wraps all others)
25
+ - **Short-circuiting** - Middleware can halt execution by not calling next callable
26
+ - **Inheritance** - Middleware is inherited from parent classes
27
+ - **Built-in** - Includes Timeout and Correlate middleware
28
+
19
29
  ## Using Middleware
20
30
 
21
31
  Declare middleware using the `use` method in your task classes:
22
32
 
23
33
  ```ruby
24
34
  class ProcessOrderTask < CMDx::Task
25
- use AuthenticationMiddleware
26
- use LoggingMiddleware, level: :info
27
- use CachingMiddleware, ttl: 300
35
+ use :middleware, AuthenticationMiddleware
36
+ use :middleware, LoggingMiddleware, level: :info
37
+ use :middleware, CachingMiddleware, ttl: 300
28
38
 
29
39
  def call
30
40
  context.order = Order.find(order_id)
@@ -61,7 +71,7 @@ class AuditMiddleware < CMDx::Middleware
61
71
  end
62
72
 
63
73
  class ProcessOrderTask < CMDx::Task
64
- use AuditMiddleware, action: 'process', resource_type: 'Order'
74
+ use :middleware, AuditMiddleware, action: 'process', resource_type: 'Order'
65
75
 
66
76
  def call
67
77
  context.order = Order.find(order_id)
@@ -76,7 +86,7 @@ Pre-configured middleware instances for complex initialization:
76
86
 
77
87
  ```ruby
78
88
  class ProcessOrderTask < CMDx::Task
79
- use LoggingMiddleware.new(
89
+ use :middleware, LoggingMiddleware.new(
80
90
  level: :debug,
81
91
  formatter: JSON::JSONFormatter.new,
82
92
  tags: ['order', 'payment']
@@ -94,7 +104,7 @@ Inline middleware for simple cases:
94
104
 
95
105
  ```ruby
96
106
  class ProcessOrderTask < CMDx::Task
97
- use proc { |task, callable|
107
+ use :middleware, proc { |task, callable|
98
108
  start_time = Time.now
99
109
  result = callable.call(task)
100
110
  duration = Time.now - start_time
@@ -115,9 +125,9 @@ Middleware executes in nested fashion - first declared wraps all others:
115
125
 
116
126
  ```ruby
117
127
  class ProcessOrderTask < CMDx::Task
118
- use TimingMiddleware # 1st: outermost
119
- use AuthenticationMiddleware # 2nd: middle
120
- use ValidationMiddleware # 3rd: innermost
128
+ use :middleware, TimingMiddleware # 1st: outermost
129
+ use :middleware, AuthenticationMiddleware # 2nd: middle
130
+ use :middleware, ValidationMiddleware # 3rd: innermost
121
131
 
122
132
  def call
123
133
  # Core logic executes last
@@ -163,7 +173,7 @@ class RateLimitMiddleware < CMDx::Middleware
163
173
  end
164
174
 
165
175
  class SendEmailTask < CMDx::Task
166
- use RateLimitMiddleware, limit: 50, window: 1.hour
176
+ use :middleware, RateLimitMiddleware, limit: 50, window: 1.hour
167
177
 
168
178
  def call
169
179
  # Only executes if rate limit check passes
@@ -182,14 +192,14 @@ Middleware is inherited from parent classes, enabling application-wide patterns:
182
192
 
183
193
  ```ruby
184
194
  class ApplicationTask < CMDx::Task
185
- use RequestIdMiddleware # All tasks get request tracking
186
- use PerformanceMiddleware # All tasks get performance monitoring
187
- use ErrorReportingMiddleware # All tasks get error reporting
195
+ use :middleware, RequestIdMiddleware # All tasks get request tracking
196
+ use :middleware, PerformanceMiddleware # All tasks get performance monitoring
197
+ use :middleware, ErrorReportingMiddleware # All tasks get error reporting
188
198
  end
189
199
 
190
200
  class ProcessOrderTask < ApplicationTask
191
- use AuthenticationMiddleware # Specific to order processing
192
- use OrderValidationMiddleware # Domain-specific validation
201
+ use :middleware, AuthenticationMiddleware # Specific to order processing
202
+ use :middleware, OrderValidationMiddleware # Domain-specific validation
193
203
 
194
204
  def call
195
205
  # Inherits all ApplicationTask middleware plus order-specific ones
@@ -210,7 +220,7 @@ Enforces execution time limits with support for static and dynamic timeout value
210
220
 
211
221
  ```ruby
212
222
  class ProcessLargeReportTask < CMDx::Task
213
- use CMDx::Middlewares::Timeout, seconds: 300 # 5 minutes
223
+ use :middleware, CMDx::Middlewares::Timeout, seconds: 300 # 5 minutes
214
224
 
215
225
  def call
216
226
  # Long-running report generation
@@ -219,7 +229,7 @@ end
219
229
 
220
230
  # Default timeout (3 seconds when no value specified)
221
231
  class QuickValidationTask < CMDx::Task
222
- use CMDx::Middlewares::Timeout # Uses 3 seconds default
232
+ use :middleware, CMDx::Middlewares::Timeout # Uses 3 seconds default
223
233
 
224
234
  def call
225
235
  # Fast validation logic
@@ -234,7 +244,7 @@ The middleware supports dynamic timeout calculation using method names, procs, a
234
244
  ```ruby
235
245
  # Method-based timeout calculation
236
246
  class ProcessOrderTask < CMDx::Task
237
- use CMDx::Middlewares::Timeout, seconds: :calculate_timeout
247
+ use :middleware, CMDx::Middlewares::Timeout, seconds: :calculate_timeout
238
248
 
239
249
  def call
240
250
  # Task execution with dynamic timeout
@@ -255,7 +265,7 @@ end
255
265
 
256
266
  # Proc-based timeout for inline calculation
257
267
  class ProcessWorkflowTask < CMDx::Task
258
- use CMDx::Middlewares::Timeout, seconds: -> {
268
+ use :middleware, CMDx::Middlewares::Timeout, seconds: -> {
259
269
  context.workflow_size > 100 ? 120 : 60
260
270
  }
261
271
 
@@ -267,7 +277,7 @@ end
267
277
 
268
278
  # Context-aware timeout calculation
269
279
  class GenerateReportTask < CMDx::Task
270
- use CMDx::Middlewares::Timeout, seconds: :report_timeout
280
+ use :middleware, CMDx::Middlewares::Timeout, seconds: :report_timeout
271
281
 
272
282
  def call
273
283
  context.report = ReportGenerator.create(report_params)
@@ -299,12 +309,12 @@ The middleware follows this precedence for determining timeout values:
299
309
  ```ruby
300
310
  # Static timeout - highest precedence when specified
301
311
  class ProcessOrderTask < CMDx::Task
302
- use CMDx::Middlewares::Timeout, seconds: 45 # Always 45 seconds
312
+ use :middleware, CMDx::Middlewares::Timeout, seconds: 45 # Always 45 seconds
303
313
  end
304
314
 
305
315
  # Method-based timeout - calls task method
306
316
  class ProcessOrderTask < CMDx::Task
307
- use CMDx::Middlewares::Timeout, seconds: :dynamic_timeout
317
+ use :middleware, CMDx::Middlewares::Timeout, seconds: :dynamic_timeout
308
318
 
309
319
  private
310
320
  def dynamic_timeout
@@ -314,7 +324,7 @@ end
314
324
 
315
325
  # Default fallback when method returns nil
316
326
  class ProcessOrderTask < CMDx::Task
317
- use CMDx::Middlewares::Timeout, seconds: :might_return_nil
327
+ use :middleware, CMDx::Middlewares::Timeout, seconds: :might_return_nil
318
328
 
319
329
  private
320
330
  def might_return_nil
@@ -330,7 +340,7 @@ Apply timeout middleware conditionally based on environment or task state:
330
340
  ```ruby
331
341
  # Environment-based conditional timeout
332
342
  class ProcessOrderTask < CMDx::Task
333
- use CMDx::Middlewares::Timeout,
343
+ use :middleware, CMDx::Middlewares::Timeout,
334
344
  seconds: 60,
335
345
  unless: -> { Rails.env.development? }
336
346
 
@@ -343,7 +353,7 @@ end
343
353
 
344
354
  # Context-based conditional timeout
345
355
  class SendEmailTask < CMDx::Task
346
- use CMDx::Middlewares::Timeout,
356
+ use :middleware, CMDx::Middlewares::Timeout,
347
357
  seconds: 30,
348
358
  if: :timeout_enabled?
349
359
 
@@ -360,7 +370,7 @@ end
360
370
 
361
371
  # Combined dynamic timeout with conditions
362
372
  class ProcessComplexOrderTask < CMDx::Task
363
- use CMDx::Middlewares::Timeout,
373
+ use :middleware, CMDx::Middlewares::Timeout,
364
374
  seconds: :calculate_timeout,
365
375
  unless: :skip_timeout?
366
376
 
@@ -389,11 +399,11 @@ Apply timeout middleware globally with inheritance:
389
399
 
390
400
  ```ruby
391
401
  class ApplicationTask < CMDx::Task
392
- use CMDx::Middlewares::Timeout, seconds: 60 # Default 60 seconds for all tasks
402
+ use :middleware, CMDx::Middlewares::Timeout, seconds: 60 # Default 60 seconds for all tasks
393
403
  end
394
404
 
395
405
  class QuickTask < ApplicationTask
396
- use CMDx::Middlewares::Timeout, seconds: 15 # Override with 15 seconds
406
+ use :middleware, CMDx::Middlewares::Timeout, seconds: 15 # Override with 15 seconds
397
407
 
398
408
  def call
399
409
  # Fast operation with shorter timeout
@@ -401,7 +411,7 @@ class QuickTask < ApplicationTask
401
411
  end
402
412
 
403
413
  class LongRunningTask < ApplicationTask
404
- use CMDx::Middlewares::Timeout, seconds: :dynamic_timeout
414
+ use :middleware, CMDx::Middlewares::Timeout, seconds: :dynamic_timeout
405
415
 
406
416
  def call
407
417
  # Long operation with dynamic timeout
@@ -427,7 +437,7 @@ Manages correlation IDs for request tracing across task boundaries. This middlew
427
437
 
428
438
  ```ruby
429
439
  class ProcessApiRequestTask < CMDx::Task
430
- use CMDx::Middlewares::Correlate
440
+ use :middleware, CMDx::Middlewares::Correlate
431
441
 
432
442
  def call
433
443
  # Correlation ID is automatically managed
@@ -446,19 +456,19 @@ The middleware follows a hierarchical precedence system for determining correlat
446
456
 
447
457
  # 1a. Static string ID
448
458
  class ProcessOrderTask < CMDx::Task
449
- use CMDx::Middlewares::Correlate, id: "fixed-correlation-123"
459
+ use :middleware, CMDx::Middlewares::Correlate, id: "fixed-correlation-123"
450
460
  end
451
461
  ProcessOrderTask.call # Always uses "fixed-correlation-123"
452
462
 
453
463
  # 1b. Dynamic proc/lambda ID
454
464
  class ProcessOrderTask < CMDx::Task
455
- use CMDx::Middlewares::Correlate, id: -> { "order-#{order_id}-#{rand(1000)}" }
465
+ use :middleware, CMDx::Middlewares::Correlate, id: -> { "order-#{order_id}-#{rand(1000)}" }
456
466
  end
457
467
  ProcessOrderTask.call(order_id: 456) # Uses "order-456-847" (random number varies)
458
468
 
459
469
  # 1c. Method-based ID
460
470
  class ProcessOrderTask < CMDx::Task
461
- use CMDx::Middlewares::Correlate, id: :correlation_method
471
+ use :middleware, CMDx::Middlewares::Correlate, id: :correlation_method
462
472
 
463
473
  private
464
474
 
@@ -488,7 +498,7 @@ Set fixed or dynamic correlation IDs for specific tasks or workflows using strin
488
498
  ```ruby
489
499
  # Static string correlation ID
490
500
  class ProcessPaymentTask < CMDx::Task
491
- use CMDx::Middlewares::Correlate, id: "payment-processing"
501
+ use :middleware, CMDx::Middlewares::Correlate, id: "payment-processing"
492
502
 
493
503
  def call
494
504
  # Always uses "payment-processing" as correlation ID
@@ -499,7 +509,7 @@ end
499
509
 
500
510
  # Dynamic correlation ID using proc/lambda
501
511
  class ProcessOrderTask < CMDx::Task
502
- use CMDx::Middlewares::Correlate, id: -> { "order-#{order_id}-#{Time.now.to_i}" }
512
+ use :middleware, CMDx::Middlewares::Correlate, id: -> { "order-#{order_id}-#{Time.now.to_i}" }
503
513
 
504
514
  def call
505
515
  # Dynamic correlation ID based on order and timestamp
@@ -511,7 +521,7 @@ end
511
521
 
512
522
  # Method-based correlation ID
513
523
  class ProcessApiRequestTask < CMDx::Task
514
- use CMDx::Middlewares::Correlate, id: :generate_correlation_id
524
+ use :middleware, CMDx::Middlewares::Correlate, id: :generate_correlation_id
515
525
 
516
526
  def call
517
527
  # Uses correlation ID from generate_correlation_id method
@@ -527,7 +537,7 @@ end
527
537
 
528
538
  # Symbol fallback when method doesn't exist
529
539
  class ProcessWorkflowTask < CMDx::Task
530
- use CMDx::Middlewares::Correlate, id: :workflow_processing
540
+ use :middleware, CMDx::Middlewares::Correlate, id: :workflow_processing
531
541
 
532
542
  def call
533
543
  # Uses :workflow_processing as correlation ID (symbol as-is)
@@ -544,7 +554,7 @@ Apply correlation middleware conditionally based on environment or task state:
544
554
  ```ruby
545
555
  class ProcessOrderTask < CMDx::Task
546
556
  # Only apply correlation in production environments
547
- use CMDx::Middlewares::Correlate, unless: -> { Rails.env.development? }
557
+ use :middleware, CMDx::Middlewares::Correlate, unless: -> { Rails.env.development? }
548
558
 
549
559
  def call
550
560
  context.order = Order.find(order_id)
@@ -554,7 +564,7 @@ end
554
564
 
555
565
  class SendEmailTask < CMDx::Task
556
566
  # Apply correlation only when tracing is enabled
557
- use CMDx::Middlewares::Correlate, if: :tracing_enabled?
567
+ use :middleware, CMDx::Middlewares::Correlate, if: :tracing_enabled?
558
568
 
559
569
  def call
560
570
  EmailService.deliver(email_params)
@@ -574,7 +584,7 @@ Use correlation blocks to establish correlation contexts for groups of related t
574
584
 
575
585
  ```ruby
576
586
  class ProcessOrderWorkflowTask < CMDx::Task
577
- use CMDx::Middlewares::Correlate
587
+ use :middleware, CMDx::Middlewares::Correlate
578
588
 
579
589
  def call
580
590
  # Establish correlation context for entire workflow
@@ -594,7 +604,7 @@ Apply correlation middleware globally to all tasks:
594
604
 
595
605
  ```ruby
596
606
  class ApplicationTask < CMDx::Task
597
- use CMDx::Middlewares::Correlate # All tasks get correlation management
607
+ use :middleware, CMDx::Middlewares::Correlate # All tasks get correlation management
598
608
  end
599
609
 
600
610
  class ProcessOrderTask < ApplicationTask
@@ -638,7 +648,7 @@ class ApiController < ApplicationController
638
648
  end
639
649
 
640
650
  class ProcessOrderTask < CMDx::Task
641
- use CMDx::Middlewares::Correlate
651
+ use :middleware, CMDx::Middlewares::Correlate
642
652
 
643
653
  def call
644
654
  # Inherits correlation ID from controller thread context
@@ -651,7 +661,7 @@ end
651
661
 
652
662
  # Alternative: Task-specific correlation for API endpoints
653
663
  class ProcessApiOrderTask < CMDx::Task
654
- use CMDx::Middlewares::Correlate, id: -> { "api-order-#{context.request_id}" }
664
+ use :middleware, CMDx::Middlewares::Correlate, id: -> { "api-order-#{context.request_id}" }
655
665
 
656
666
  def call
657
667
  # Uses correlation ID specific to this API request
@@ -7,6 +7,7 @@ inspecting task execution outcomes and chaining task operations.
7
7
 
8
8
  ## Table of Contents
9
9
 
10
+ - [TLDR](#tldr)
10
11
  - [Core Result Attributes](#core-result-attributes)
11
12
  - [State and Status Information](#state-and-status-information)
12
13
  - [Execution Outcome Analysis](#execution-outcome-analysis)
@@ -17,6 +18,14 @@ inspecting task execution outcomes and chaining task operations.
17
18
  - [Pattern Matching](#pattern-matching)
18
19
  - [Serialization and Inspection](#serialization-and-inspection)
19
20
 
21
+ ## TLDR
22
+
23
+ - **Result object** - Comprehensive return value from task execution with `task`, `context`, `chain`, `metadata`
24
+ - **Status checking** - Use `result.success?`, `result.failed?`, `result.skipped?` for outcomes
25
+ - **State checking** - Use `result.complete?`, `result.interrupted?`, `result.executed?` for lifecycle
26
+ - **Callbacks** - Chain with `.on_success`, `.on_failed`, `.on_good`, `.on_bad` for conditional logic
27
+ - **Failure analysis** - Use `result.caused_failure`, `result.threw_failure` to trace failure chains
28
+
20
29
  ## Core Result Attributes
21
30
 
22
31
  Every result provides access to essential execution information:
@@ -7,6 +7,7 @@ decision making and monitoring.
7
7
 
8
8
  ## Table of Contents
9
9
 
10
+ - [TLDR](#tldr)
10
11
  - [State Definitions](#state-definitions)
11
12
  - [State Transitions](#state-transitions)
12
13
  - [State Predicates](#state-predicates)
@@ -15,6 +16,14 @@ decision making and monitoring.
15
16
  - [State Inspection and Monitoring](#state-inspection-and-monitoring)
16
17
  - [State Persistence and Logging](#state-persistence-and-logging)
17
18
 
19
+ ## TLDR
20
+
21
+ - **States** - Track execution lifecycle: `initialized` → `executing` → `complete`/`interrupted`
22
+ - **Automatic** - States are managed automatically by the framework, never modify manually
23
+ - **Predicates** - Check with `result.complete?`, `result.interrupted?`, `result.executed?`
24
+ - **Callbacks** - Use `.on_complete`, `.on_interrupted`, `.on_executed` for lifecycle events
25
+ - **vs Status** - State = where in lifecycle, Status = how execution ended
26
+
18
27
  ## State Definitions
19
28
 
20
29
  | State | Description |
@@ -4,6 +4,7 @@ Statuses represent the outcome of task execution logic, indicating how the task'
4
4
 
5
5
  ## Table of Contents
6
6
 
7
+ - [TLDR](#tldr)
7
8
  - [Status Definitions](#status-definitions)
8
9
  - [Status Characteristics](#status-characteristics)
9
10
  - [Status Predicates](#status-predicates)
@@ -14,6 +15,14 @@ Statuses represent the outcome of task execution logic, indicating how the task'
14
15
  - [Status Serialization and Inspection](#status-serialization-and-inspection)
15
16
  - [Status vs State vs Outcome](#status-vs-state-vs-outcome)
16
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
+
17
26
  ## Status Definitions
18
27
 
19
28
  | Status | Description |
@@ -7,6 +7,7 @@ string-to-integer conversion to complex JSON parsing and custom type handling.
7
7
 
8
8
  ## Table of Contents
9
9
 
10
+ - [TLDR](#tldr)
10
11
  - [Coercion Fundamentals](#coercion-fundamentals)
11
12
  - [Available Coercion Types](#available-coercion-types)
12
13
  - [Basic Type Coercion](#basic-type-coercion)
@@ -19,11 +20,19 @@ string-to-integer conversion to complex JSON parsing and custom type handling.
19
20
  - [Numeric Coercion](#numeric-coercion)
20
21
  - [Coercion with Nested Parameters](#coercion-with-nested-parameters)
21
22
  - [Coercion Error Handling](#coercion-error-handling)
22
- - [Single Type Coercion Errors](#single-type-coercion-errors)
23
- - [Multiple Type Coercion Errors](#multiple-type-coercion-errors)
24
23
  - [Custom Coercion Options](#custom-coercion-options)
25
24
  - [Date/Time Format Options](#datetime-format-options)
26
25
  - [BigDecimal Precision Options](#bigdecimal-precision-options)
26
+ - [Custom Coercions](#custom-coercions)
27
+
28
+ ## TLDR
29
+
30
+ - **Type coercion** - Automatic conversion using `type:` option (`:integer`, `:boolean`, `:array`, `:hash`, etc.)
31
+ - **Multiple types** - Fallback with `type: [:float, :integer]` - tries each until one succeeds
32
+ - **No conversion** - Default `:virtual` type returns values unchanged
33
+ - **Before validation** - Coercion happens automatically before parameter validation
34
+ - **Rich types** - Supports all Ruby built-ins plus JSON parsing for arrays/hashes
35
+ - **Custom coercions** - Register custom coercion types
27
36
 
28
37
  ## Coercion Fundamentals
29
38
 
@@ -307,13 +316,11 @@ end
307
316
  > [!WARNING]
308
317
  > When coercion fails, CMDx provides detailed error information including the parameter name, attempted types, and specific failure reasons.
309
318
 
310
- ### Single Type Coercion Errors
311
-
312
319
  ```ruby
313
320
  class ValidateUserProfileTask < CMDx::Task
314
321
 
315
322
  required :age, type: :integer
316
- required :salary, type: :float
323
+ required :salary, type: [:float, :big_decimal]
317
324
  required :is_employed, type: :boolean
318
325
 
319
326
  def call
@@ -332,46 +339,15 @@ result = ValidateUserProfileTask.call(
332
339
  result.failed? #=> true
333
340
  result.metadata
334
341
  #=> {
335
- # reason: "age could not coerce into an integer. salary could not coerce into a float. is_employed could not coerce into a boolean.",
342
+ # reason: "age could not coerce into an integer. could not coerce into one of: float, big_decimal. is_employed could not coerce into a boolean.",
336
343
  # messages: {
337
344
  # age: ["could not coerce into an integer"],
338
- # salary: ["could not coerce into a float"],
345
+ # salary: ["could not coerce into one of: float, big_decimal"],
339
346
  # is_employed: ["could not coerce into a boolean"]
340
347
  # }
341
348
  # }
342
349
  ```
343
350
 
344
- ### Multiple Type Coercion Errors
345
-
346
- ```ruby
347
- class ProcessFlexibleDataTask < CMDx::Task
348
-
349
- required :order_value, type: [:float, :integer]
350
- required :customer_data, type: [:hash, :array, :string]
351
-
352
- def call
353
- # Task logic here
354
- end
355
-
356
- end
357
-
358
- # Failed coercion with multiple types
359
- result = ProcessFlexibleDataTask.call(
360
- order_value: "invalid-number",
361
- customer_data: Object.new
362
- )
363
-
364
- result.failed? #=> true
365
- result.metadata
366
- #=> {
367
- # reason: "order_value could not coerce into one of: float, integer. customer_data could not coerce into one of: hash, array, string.",
368
- # messages: {
369
- # order_value: ["could not coerce into one of: float, integer"],
370
- # customer_data: ["could not coerce into one of: hash, array, string"]
371
- # }
372
- # }
373
- ```
374
-
375
351
  ## Custom Coercion Options
376
352
 
377
353
  ### Date/Time Format Options
@@ -419,6 +395,50 @@ class CalculatePricingTask < CMDx::Task
419
395
  end
420
396
  ```
421
397
 
398
+ ## Custom Coercions
399
+
400
+ > [!NOTE]
401
+ > CMDx allows you to register custom coercions for domain-specific types that aren't covered by the built-in coercions. Custom coercions can be registered globally or per-task basis.
402
+
403
+ ```ruby
404
+ module MoneyCoercion
405
+ module_function
406
+
407
+ def call(value, options = {})
408
+ return value if value.is_a?(BigDecimal)
409
+
410
+ # Handle string amounts like "$123.45"
411
+ if value.is_a?(String)
412
+ clean_value = value.gsub(/[$,]/, '')
413
+ BigDecimal(clean_value)
414
+ else
415
+ BigDecimal(value.to_s)
416
+ end
417
+ end
418
+ end
419
+
420
+ CMDx.configure do |config|
421
+ config.coercions.register(:money, MoneyCoercion)
422
+ config.coercions.register(:slug, proc do |value|
423
+ value.to_s.downcase.gsub(/[^a-z0-9]+/, '-').gsub(/-+/, '-').strip('-')
424
+ end)
425
+ end
426
+
427
+ # Now use in any task
428
+ class ProcessProductTask < CMDx::Task
429
+ required :cost, type: :money
430
+ required :slug, type: :slug
431
+
432
+ def call
433
+ cost #=> 123.45
434
+ slug #=> "my-blog-post-title" (URL-friendly)
435
+ end
436
+ end
437
+ ```
438
+
439
+ > [!TIP]
440
+ > Custom coercions should be idempotent - calling them multiple times with the same input should produce the same result. This ensures predictable behavior when coercions are applied during parameter processing.
441
+
422
442
  ---
423
443
 
424
444
  - **Prev:** [Parameters - Namespacing](namespacing.md)
@@ -7,6 +7,7 @@ Defaults work seamlessly with coercion, validation, and nested parameters.
7
7
 
8
8
  ## Table of Contents
9
9
 
10
+ - [TLDR](#tldr)
10
11
  - [Default Value Fundamentals](#default-value-fundamentals)
11
12
  - [Fixed Value Defaults](#fixed-value-defaults)
12
13
  - [Callable Defaults](#callable-defaults)
@@ -14,6 +15,14 @@ Defaults work seamlessly with coercion, validation, and nested parameters.
14
15
  - [Defaults with Validation](#defaults-with-validation)
15
16
  - [Nested Parameter Defaults](#nested-parameter-defaults)
16
17
 
18
+ ## TLDR
19
+
20
+ - **Defaults** - Provide fallback values when parameters not provided or are `nil`
21
+ - **Fixed values** - `default: "normal"`, `default: true`, `default: []`
22
+ - **Dynamic values** - `default: -> { Time.now }`, `default: :method_name` for callable defaults
23
+ - **With coercion** - Defaults are subject to same type coercion as provided values
24
+ - **With validation** - Defaults must pass same validation rules as provided values
25
+
17
26
  ## Default Value Fundamentals
18
27
 
19
28
  > [!NOTE]
@@ -171,7 +180,7 @@ class ValidateOrderPriorityTask < CMDx::Task
171
180
 
172
181
  # Custom validation with default
173
182
  optional :approval_code, type: :string, default: :generate_approval_code,
174
- custom: { validator: ApprovalCodeValidator }
183
+ presence: true
175
184
 
176
185
  def call
177
186
  priority #=> "standard" (validated against inclusion list)
@@ -4,12 +4,21 @@ Parameters provide a contract to verify that task execution arguments match expe
4
4
 
5
5
  ## Table of Contents
6
6
 
7
+ - [TLDR](#tldr)
7
8
  - [Parameter Fundamentals](#parameter-fundamentals)
8
9
  - [Parameter Sources](#parameter-sources)
9
10
  - [Nested Parameters](#nested-parameters)
10
11
  - [Parameter Method Generation](#parameter-method-generation)
11
12
  - [Error Handling](#error-handling)
12
13
 
14
+ ## TLDR
15
+
16
+ - **Required/Optional** - Define with `required :param` and `optional :param` class methods
17
+ - **Method generation** - Parameters become instance methods for easy access
18
+ - **Sources** - Default `:context` source, or custom with `source: :user`
19
+ - **Nested params** - Complex structures with `required :address do ... end`
20
+ - **Call interface** - Parameters passed as keyword arguments to `TaskClass.call(param: value)`
21
+
13
22
  ## Parameter Fundamentals
14
23
 
15
24
  Parameters are defined using `required` and `optional` class methods that automatically create accessor methods within task instances. Parameters are matched from call arguments and made available as instance methods.
@@ -7,6 +7,7 @@ same name, namespacing ensures clean method resolution within tasks.
7
7
 
8
8
  ## Table of Contents
9
9
 
10
+ - [TLDR](#tldr)
10
11
  - [Namespacing Fundamentals](#namespacing-fundamentals)
11
12
  - [Fixed Value Namespacing](#fixed-value-namespacing)
12
13
  - [Dynamic Source-Based Namespacing](#dynamic-source-based-namespacing)
@@ -14,6 +15,14 @@ same name, namespacing ensures clean method resolution within tasks.
14
15
  - [Advanced Namespacing Patterns](#advanced-namespacing-patterns)
15
16
  - [Error Handling with Namespacing](#error-handling-with-namespacing)
16
17
 
18
+ ## TLDR
19
+
20
+ - **Method naming** - Use `prefix:` and `suffix:` to customize parameter method names
21
+ - **Fixed prefixes** - `prefix: "user_"` creates `user_name` method for `name` parameter
22
+ - **Dynamic prefixes** - `prefix: true` uses source name (e.g., `context_name`)
23
+ - **Conflict resolution** - Avoid conflicts with Ruby methods or multiple same-named parameters
24
+ - **Call arguments** - Always use original parameter names, namespacing only affects method names
25
+
17
26
  ## Namespacing Fundamentals
18
27
 
19
28
  > [!IMPORTANT]