cmdx 1.1.2 → 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 (192) 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/CHANGELOG.md +5 -133
  11. data/LLM.md +3317 -0
  12. data/README.md +68 -44
  13. data/docs/attributes/coercions.md +162 -0
  14. data/docs/attributes/defaults.md +90 -0
  15. data/docs/attributes/definitions.md +281 -0
  16. data/docs/attributes/naming.md +78 -0
  17. data/docs/attributes/validations.md +309 -0
  18. data/docs/basics/chain.md +56 -249
  19. data/docs/basics/context.md +56 -289
  20. data/docs/basics/execution.md +114 -0
  21. data/docs/basics/setup.md +37 -334
  22. data/docs/callbacks.md +89 -467
  23. data/docs/deprecation.md +91 -174
  24. data/docs/getting_started.md +212 -202
  25. data/docs/internationalization.md +11 -647
  26. data/docs/interruptions/exceptions.md +23 -198
  27. data/docs/interruptions/faults.md +71 -151
  28. data/docs/interruptions/halt.md +109 -186
  29. data/docs/logging.md +44 -256
  30. data/docs/middlewares.md +113 -426
  31. data/docs/outcomes/result.md +81 -228
  32. data/docs/outcomes/states.md +33 -221
  33. data/docs/outcomes/statuses.md +21 -311
  34. data/docs/tips_and_tricks.md +120 -70
  35. data/docs/workflows.md +99 -283
  36. data/lib/cmdx/.DS_Store +0 -0
  37. data/lib/cmdx/attribute.rb +229 -0
  38. data/lib/cmdx/attribute_registry.rb +94 -0
  39. data/lib/cmdx/attribute_value.rb +193 -0
  40. data/lib/cmdx/callback_registry.rb +69 -77
  41. data/lib/cmdx/chain.rb +56 -73
  42. data/lib/cmdx/coercion_registry.rb +52 -68
  43. data/lib/cmdx/coercions/array.rb +19 -18
  44. data/lib/cmdx/coercions/big_decimal.rb +20 -24
  45. data/lib/cmdx/coercions/boolean.rb +26 -25
  46. data/lib/cmdx/coercions/complex.rb +21 -22
  47. data/lib/cmdx/coercions/date.rb +25 -23
  48. data/lib/cmdx/coercions/date_time.rb +24 -25
  49. data/lib/cmdx/coercions/float.rb +25 -22
  50. data/lib/cmdx/coercions/hash.rb +31 -32
  51. data/lib/cmdx/coercions/integer.rb +30 -24
  52. data/lib/cmdx/coercions/rational.rb +29 -24
  53. data/lib/cmdx/coercions/string.rb +19 -22
  54. data/lib/cmdx/coercions/symbol.rb +37 -0
  55. data/lib/cmdx/coercions/time.rb +26 -25
  56. data/lib/cmdx/configuration.rb +49 -108
  57. data/lib/cmdx/context.rb +222 -44
  58. data/lib/cmdx/deprecator.rb +61 -0
  59. data/lib/cmdx/errors.rb +42 -252
  60. data/lib/cmdx/exceptions.rb +39 -0
  61. data/lib/cmdx/faults.rb +78 -39
  62. data/lib/cmdx/freezer.rb +51 -0
  63. data/lib/cmdx/identifier.rb +30 -0
  64. data/lib/cmdx/locale.rb +52 -0
  65. data/lib/cmdx/log_formatters/json.rb +21 -22
  66. data/lib/cmdx/log_formatters/key_value.rb +20 -22
  67. data/lib/cmdx/log_formatters/line.rb +15 -22
  68. data/lib/cmdx/log_formatters/logstash.rb +22 -23
  69. data/lib/cmdx/log_formatters/raw.rb +16 -22
  70. data/lib/cmdx/middleware_registry.rb +70 -74
  71. data/lib/cmdx/middlewares/correlate.rb +90 -54
  72. data/lib/cmdx/middlewares/runtime.rb +58 -0
  73. data/lib/cmdx/middlewares/timeout.rb +48 -68
  74. data/lib/cmdx/railtie.rb +12 -45
  75. data/lib/cmdx/result.rb +229 -314
  76. data/lib/cmdx/task.rb +194 -366
  77. data/lib/cmdx/utils/call.rb +49 -0
  78. data/lib/cmdx/utils/condition.rb +71 -0
  79. data/lib/cmdx/utils/format.rb +61 -0
  80. data/lib/cmdx/validator_registry.rb +63 -72
  81. data/lib/cmdx/validators/exclusion.rb +38 -67
  82. data/lib/cmdx/validators/format.rb +48 -49
  83. data/lib/cmdx/validators/inclusion.rb +43 -74
  84. data/lib/cmdx/validators/length.rb +91 -154
  85. data/lib/cmdx/validators/numeric.rb +87 -162
  86. data/lib/cmdx/validators/presence.rb +37 -50
  87. data/lib/cmdx/version.rb +1 -1
  88. data/lib/cmdx/worker.rb +178 -0
  89. data/lib/cmdx/workflow.rb +85 -81
  90. data/lib/cmdx.rb +19 -13
  91. data/lib/generators/cmdx/install_generator.rb +14 -13
  92. data/lib/generators/cmdx/task_generator.rb +25 -50
  93. data/lib/generators/cmdx/templates/install.rb +11 -46
  94. data/lib/generators/cmdx/templates/task.rb.tt +3 -2
  95. data/lib/locales/en.yml +18 -4
  96. data/src/cmdx-logo.png +0 -0
  97. metadata +32 -116
  98. data/docs/ai_prompts.md +0 -393
  99. data/docs/basics/call.md +0 -317
  100. data/docs/configuration.md +0 -344
  101. data/docs/parameters/coercions.md +0 -396
  102. data/docs/parameters/defaults.md +0 -335
  103. data/docs/parameters/definitions.md +0 -446
  104. data/docs/parameters/namespacing.md +0 -378
  105. data/docs/parameters/validations.md +0 -405
  106. data/docs/testing.md +0 -553
  107. data/lib/cmdx/callback.rb +0 -53
  108. data/lib/cmdx/chain_inspector.rb +0 -56
  109. data/lib/cmdx/chain_serializer.rb +0 -63
  110. data/lib/cmdx/coercion.rb +0 -57
  111. data/lib/cmdx/coercions/virtual.rb +0 -29
  112. data/lib/cmdx/core_ext/hash.rb +0 -83
  113. data/lib/cmdx/core_ext/module.rb +0 -98
  114. data/lib/cmdx/core_ext/object.rb +0 -125
  115. data/lib/cmdx/correlator.rb +0 -122
  116. data/lib/cmdx/error.rb +0 -67
  117. data/lib/cmdx/fault.rb +0 -140
  118. data/lib/cmdx/immutator.rb +0 -52
  119. data/lib/cmdx/lazy_struct.rb +0 -246
  120. data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
  121. data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
  122. data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
  123. data/lib/cmdx/logger.rb +0 -49
  124. data/lib/cmdx/logger_ansi.rb +0 -68
  125. data/lib/cmdx/logger_serializer.rb +0 -116
  126. data/lib/cmdx/middleware.rb +0 -70
  127. data/lib/cmdx/parameter.rb +0 -312
  128. data/lib/cmdx/parameter_evaluator.rb +0 -231
  129. data/lib/cmdx/parameter_inspector.rb +0 -66
  130. data/lib/cmdx/parameter_registry.rb +0 -106
  131. data/lib/cmdx/parameter_serializer.rb +0 -59
  132. data/lib/cmdx/result_ansi.rb +0 -71
  133. data/lib/cmdx/result_inspector.rb +0 -71
  134. data/lib/cmdx/result_logger.rb +0 -59
  135. data/lib/cmdx/result_serializer.rb +0 -104
  136. data/lib/cmdx/rspec/matchers.rb +0 -28
  137. data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
  138. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
  139. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
  140. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
  141. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
  142. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
  143. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
  144. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
  145. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
  146. data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
  147. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
  148. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
  149. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
  150. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
  151. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
  152. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
  153. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
  154. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
  155. data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
  156. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
  157. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
  158. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
  159. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
  160. data/lib/cmdx/task_deprecator.rb +0 -58
  161. data/lib/cmdx/task_processor.rb +0 -246
  162. data/lib/cmdx/task_serializer.rb +0 -57
  163. data/lib/cmdx/utils/ansi_color.rb +0 -73
  164. data/lib/cmdx/utils/log_timestamp.rb +0 -36
  165. data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
  166. data/lib/cmdx/utils/name_affix.rb +0 -52
  167. data/lib/cmdx/validator.rb +0 -57
  168. data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
  169. data/lib/generators/cmdx/workflow_generator.rb +0 -84
  170. data/lib/locales/ar.yml +0 -35
  171. data/lib/locales/cs.yml +0 -35
  172. data/lib/locales/da.yml +0 -35
  173. data/lib/locales/de.yml +0 -35
  174. data/lib/locales/el.yml +0 -35
  175. data/lib/locales/es.yml +0 -35
  176. data/lib/locales/fi.yml +0 -35
  177. data/lib/locales/fr.yml +0 -35
  178. data/lib/locales/he.yml +0 -35
  179. data/lib/locales/hi.yml +0 -35
  180. data/lib/locales/it.yml +0 -35
  181. data/lib/locales/ja.yml +0 -35
  182. data/lib/locales/ko.yml +0 -35
  183. data/lib/locales/nl.yml +0 -35
  184. data/lib/locales/no.yml +0 -35
  185. data/lib/locales/pl.yml +0 -35
  186. data/lib/locales/pt.yml +0 -35
  187. data/lib/locales/ru.yml +0 -35
  188. data/lib/locales/sv.yml +0 -35
  189. data/lib/locales/th.yml +0 -35
  190. data/lib/locales/tr.yml +0 -35
  191. data/lib/locales/vi.yml +0 -35
  192. data/lib/locales/zh.yml +0 -35
data/docs/middlewares.md CHANGED
@@ -4,515 +4,202 @@ Middleware provides Rack-style wrappers around task execution for cross-cutting
4
4
 
5
5
  ## Table of Contents
6
6
 
7
- - [TLDR](#tldr)
8
- - [Using Middleware](#using-middleware)
9
- - [Class Middleware](#class-middleware)
10
- - [Instance Middleware](#instance-middleware)
11
- - [Proc Middleware](#proc-middleware)
12
- - [Execution Order](#execution-order)
13
- - [Short-circuiting](#short-circuiting)
14
- - [Inheritance](#inheritance)
15
- - [Built-in Middleware](#built-in-middleware)
16
- - [Timeout Middleware](#timeout-middleware)
17
- - [Correlate Middleware](#correlate-middleware)
18
- - [Writing Custom Middleware](#writing-custom-middleware)
19
- - [Error Handling](#error-handling)
20
-
21
- ## TLDR
7
+ - [Order](#order)
8
+ - [Declarations](#declarations)
9
+ - [Proc or Lambda](#proc-or-lambda)
10
+ - [Class or Module](#class-or-module)
11
+ - [Removals](#removals)
12
+ - [Built-in](#built-in)
13
+ - [Timeout](#timeout)
14
+ - [Correlate](#correlate)
15
+ - [Runtime](#runtime)
22
16
 
23
- ```ruby
24
- # Declare middleware with use method
25
- use :middleware, AuthMiddleware, role: :admin # Class with options
26
- use :middleware, LoggingMiddleware.new(level: :debug) # Instance
27
- use :middleware, proc { |task, callable| ... } # Proc
28
-
29
- # Execution order: first declared wraps all others
30
- use :middleware, OuterMiddleware # Runs first/last
31
- use :middleware, InnerMiddleware # Runs last/first
32
-
33
- # Built-in middleware
34
- use :middleware, CMDx::Middlewares::Timeout, seconds: 30
35
- use :middleware, CMDx::Middlewares::Correlate, id: "request-123"
36
- ```
17
+ ## Order
37
18
 
38
- ## Using Middleware
19
+ Middleware executes in a nested fashion, creating an onion-like execution pattern:
39
20
 
40
21
  > [!NOTE]
41
- > Middleware executes in nested fashion around task execution. Use the `use` method to declare middleware in your task classes.
42
-
43
- ### Class Middleware
44
-
45
- The most common pattern - pass the middleware class with optional initialization arguments:
46
-
47
- ```ruby
48
- class AuditMiddleware < CMDx::Middleware
49
- def initialize(action:, resource_type:)
50
- @action = action
51
- @resource_type = resource_type
52
- end
53
-
54
- def call(task, callable)
55
- result = callable.call(task)
56
-
57
- if result.success?
58
- AuditLog.create!(
59
- action: @action,
60
- resource_type: @resource_type,
61
- resource_id: task.context.id,
62
- user_id: task.context.current_user&.id
63
- )
64
- end
65
-
66
- result
67
- end
68
- end
69
-
70
- class ProcessOrderTask < CMDx::Task
71
- use :middleware, AuditMiddleware, action: 'process', resource_type: 'Order'
72
-
73
- def call
74
- context.order = Order.find(order_id)
75
- context.order.process!
76
- end
77
- end
78
- ```
79
-
80
- ### Instance Middleware
81
-
82
- Pre-configured middleware instances for complex initialization:
83
-
84
- ```ruby
85
- class ProcessOrderTask < CMDx::Task
86
- use :middleware, LoggingMiddleware.new(
87
- level: :debug,
88
- formatter: CustomFormatter.new,
89
- tags: ['order', 'payment']
90
- )
91
-
92
- def call
93
- context.order = Order.find(order_id)
94
- context.order.process!
95
- end
96
- end
97
- ```
98
-
99
- ### Proc Middleware
100
-
101
- Inline middleware for simple cases:
102
-
103
- ```ruby
104
- class ProcessOrderTask < CMDx::Task
105
- use :middleware, proc { |task, callable|
106
- start_time = Time.now
107
- result = callable.call(task)
108
- duration = Time.now - start_time
109
-
110
- Rails.logger.info "#{task.class.name} completed in #{duration.round(3)}s"
111
- result
112
- }
113
-
114
- def call
115
- # Business logic
116
- end
117
- end
118
- ```
119
-
120
- ## Execution Order
121
-
122
- > [!IMPORTANT]
123
- > Middleware executes in nested fashion - first declared wraps all others, creating an onion-like execution pattern.
22
+ > Middleware executes in the order they are registered, with the first registered middleware being the outermost wrapper.
124
23
 
125
24
  ```ruby
126
- class ProcessOrderTask < CMDx::Task
127
- use :middleware, TimingMiddleware # 1st: outermost wrapper
128
- use :middleware, AuthenticationMiddleware # 2nd: middle wrapper
129
- use :middleware, ValidationMiddleware # 3rd: innermost wrapper
25
+ class ProcessCampaign < CMDx::Task
26
+ register :middleware, AuditMiddleware # 1st: outermost wrapper
27
+ register :middleware, AuthorizationMiddleware # 2nd: middle wrapper
28
+ register :middleware, CacheMiddleware # 3rd: innermost wrapper
130
29
 
131
- def call
132
- # Core logic executes here
30
+ def work
31
+ # Your logic here...
133
32
  end
134
33
  end
135
34
 
136
35
  # Execution flow:
137
- # 1. TimingMiddleware (before)
138
- # 2. AuthenticationMiddleware (before)
139
- # 3. ValidationMiddleware (before)
36
+ # 1. AuditMiddleware (before)
37
+ # 2. AuthorizationMiddleware (before)
38
+ # 3. CacheMiddleware (before)
140
39
  # 4. [task execution]
141
- # 5. ValidationMiddleware (after)
142
- # 6. AuthenticationMiddleware (after)
143
- # 7. TimingMiddleware (after)
40
+ # 5. CacheMiddleware (after)
41
+ # 6. AuthorizationMiddleware (after)
42
+ # 7. AuditMiddleware (after)
144
43
  ```
145
44
 
146
- ## Short-circuiting
147
-
148
- > [!WARNING]
149
- > Middleware can halt execution by not calling the next callable. This prevents the task and subsequent middleware from executing.
150
-
151
- ```ruby
152
- class RateLimitMiddleware < CMDx::Middleware
153
- def initialize(limit: 100, window: 1.hour)
154
- @limit = limit
155
- @window = window
156
- end
157
-
158
- def call(task, callable)
159
- key = "rate_limit:#{task.context.current_user&.id}"
160
- current_count = Rails.cache.read(key) || 0
161
-
162
- if current_count >= @limit
163
- task.fail!(reason: "Rate limit exceeded: #{@limit} requests per hour")
164
- return task.result # Short-circuit - task never executes
165
- end
166
-
167
- Rails.cache.write(key, current_count + 1, expires_in: @window)
168
- callable.call(task)
169
- end
170
- end
171
-
172
- class SendEmailTask < CMDx::Task
173
- use :middleware, RateLimitMiddleware, limit: 50
174
-
175
- def call
176
- # Only executes if rate limit check passes
177
- EmailService.deliver(email_params)
178
- end
179
- end
180
- ```
181
-
182
- ## Inheritance
183
-
184
- > [!TIP]
185
- > Middleware is inherited from parent classes, making it ideal for application-wide concerns.
186
-
187
- ```ruby
188
- class ApplicationTask < CMDx::Task
189
- use :middleware, RequestIdMiddleware # All tasks get request tracking
190
- use :middleware, PerformanceMiddleware # All tasks get performance monitoring
191
- use :middleware, ErrorReportingMiddleware # All tasks get error reporting
192
- end
193
-
194
- class ProcessOrderTask < ApplicationTask
195
- use :middleware, AuthenticationMiddleware # Added to inherited middleware
196
- use :middleware, OrderValidationMiddleware # Domain-specific validation
197
-
198
- def call
199
- # Inherits all ApplicationTask middleware plus order-specific ones
200
- context.order = Order.find(order_id)
201
- context.order.process!
202
- end
203
- end
204
- ```
205
-
206
- ## Built-in Middleware
207
-
208
- ### Timeout Middleware
209
-
210
- Enforces execution time limits with support for static and dynamic timeout values.
211
-
212
- #### Basic Usage
213
-
214
- ```ruby
215
- class ProcessLargeReportTask < CMDx::Task
216
- use :middleware, CMDx::Middlewares::Timeout, seconds: 300
217
-
218
- def call
219
- # Long-running report generation with 5-minute timeout
220
- ReportGenerator.create(report_params)
221
- end
222
- end
45
+ ## Declarations
223
46
 
224
- # Default timeout (3 seconds)
225
- class QuickValidationTask < CMDx::Task
226
- use :middleware, CMDx::Middlewares::Timeout
47
+ ### Proc or Lambda
227
48
 
228
- def call
229
- # Fast validation with default 3-second timeout
230
- ValidationService.validate(data)
231
- end
232
- end
233
- ```
234
-
235
- #### Dynamic Timeout Calculation
236
-
237
- > [!NOTE]
238
- > Timeout supports method names, procs, and lambdas for dynamic calculation based on task context.
49
+ Use anonymous functions for simple middleware logic:
239
50
 
240
51
  ```ruby
241
- # Method-based timeout
242
- class ProcessOrderTask < CMDx::Task
243
- use :middleware, CMDx::Middlewares::Timeout, seconds: :calculate_timeout
244
-
245
- def call
246
- context.order = Order.find(order_id)
247
- context.order.process!
52
+ class ProcessCampaign < CMDx::Task
53
+ # Proc
54
+ register :middleware, proc do |task, options, &block|
55
+ result = block.call
56
+ Analytics.track(result.status)
57
+ result
248
58
  end
249
59
 
250
- private
251
-
252
- def calculate_timeout
253
- base_timeout = 30
254
- base_timeout += (context.order_items.count * 2) # 2 seconds per item
255
- base_timeout += 60 if context.payment_method == "bank_transfer"
256
- base_timeout
257
- end
258
- end
259
-
260
- # Proc-based timeout
261
- class ProcessWorkflowTask < CMDx::Task
262
- use :middleware, CMDx::Middlewares::Timeout, seconds: -> {
263
- context.workflow_size > 100 ? 120 : 60
60
+ # Lambda
61
+ register :middleware, ->(task, options, &block) {
62
+ result = block.call
63
+ Analytics.track(result.status)
64
+ result
264
65
  }
265
-
266
- def call
267
- context.workflow_items.each { |item| process_item(item) }
268
- end
269
66
  end
270
67
  ```
271
68
 
272
- #### Timeout Precedence
69
+ ### Class or Module
273
70
 
274
- The middleware determines timeout values using this precedence:
275
-
276
- 1. **Explicit timeout value** (Integer/Float, Symbol, Proc/Lambda)
277
- 2. **Default value** of 3 seconds when no timeout resolves
71
+ For complex middleware logic, use classes or modules:
278
72
 
279
73
  ```ruby
280
- # Static timeout - always 45 seconds
281
- class ProcessOrderTask < CMDx::Task
282
- use :middleware, CMDx::Middlewares::Timeout, seconds: 45
283
- end
284
-
285
- # Method returns nil - falls back to 3 seconds
286
- class ProcessOrderTask < CMDx::Task
287
- use :middleware, CMDx::Middlewares::Timeout, seconds: :might_return_nil
288
-
289
- private
290
- def might_return_nil
291
- nil # Uses 3-second default
74
+ class TelemetryMiddleware
75
+ def call(task, options)
76
+ result = yield
77
+ Telemetry.record(result.status)
78
+ ensure
79
+ result # Always return result
292
80
  end
293
81
  end
294
- ```
295
82
 
296
- #### Conditional Timeout
83
+ class ProcessCampaign < CMDx::Task
84
+ # Class or Module
85
+ register :middleware, TelemetryMiddleware
297
86
 
298
- ```ruby
299
- # Environment-based timeout
300
- class ProcessOrderTask < CMDx::Task
301
- use :middleware, CMDx::Middlewares::Timeout,
302
- seconds: 60,
303
- unless: -> { Rails.env.development? }
304
-
305
- def call
306
- context.order = Order.find(order_id)
307
- context.order.process!
308
- end
309
- end
87
+ # Instance
88
+ register :middleware, TelemetryMiddleware.new
310
89
 
311
- # Context-based timeout
312
- class SendEmailTask < CMDx::Task
313
- use :middleware, CMDx::Middlewares::Timeout,
314
- seconds: 30,
315
- if: :timeout_enabled?
316
-
317
- private
318
-
319
- def timeout_enabled?
320
- !context.background_job?
321
- end
90
+ # With options
91
+ register :middleware, MonitoringMiddleware, service_key: ENV["MONITORING_KEY"]
92
+ register :middleware, MonitoringMiddleware.new(ENV["MONITORING_KEY"])
322
93
  end
323
94
  ```
324
95
 
325
- ### Correlate Middleware
96
+ ## Removals
326
97
 
327
- > [!NOTE]
328
- > Manages correlation IDs for request tracing across task boundaries, enabling distributed system observability.
98
+ Class and Module based declarations can be removed at a global and task level.
329
99
 
330
- ```ruby
331
- class ProcessApiRequestTask < CMDx::Task
332
- use :middleware, CMDx::Middlewares::Correlate
100
+ > [!WARNING]
101
+ > Only one removal operation is allowed per `deregister` call. Multiple removals require separate calls.
333
102
 
334
- def call
335
- # Correlation ID automatically managed and propagated
336
- context.api_response = ExternalService.call(request_data)
337
- end
103
+ ```ruby
104
+ class ProcessCampaign < CMDx::Task
105
+ # Class or Module (no instances)
106
+ deregister :middleware, TelemetryMiddleware
338
107
  end
339
108
  ```
340
109
 
341
- #### Correlation Precedence
110
+ ## Built-in
342
111
 
343
- The middleware determines correlation IDs using this hierarchy:
112
+ ### Timeout
344
113
 
345
- 1. **Explicit correlation ID** (string, proc, method name)
346
- 2. **Thread-local correlation** (CMDx::Correlator.id)
347
- 3. **Existing chain ID** (inherited from parent task)
348
- 4. **Generated UUID** (when none exist)
114
+ Ensures task execution doesn't exceed a specified time limit:
349
115
 
350
116
  ```ruby
351
- # Explicit correlation ID
352
- class ProcessOrderTask < CMDx::Task
353
- use :middleware, CMDx::Middlewares::Correlate, id: "order-processing"
354
- end
355
-
356
- # Dynamic correlation ID
357
- class ProcessOrderTask < CMDx::Task
358
- use :middleware, CMDx::Middlewares::Correlate, id: -> { "order-#{order_id}" }
359
- end
360
-
361
- # Method-based correlation ID
362
- class ProcessApiRequestTask < CMDx::Task
363
- use :middleware, CMDx::Middlewares::Correlate, id: :generate_correlation_id
364
-
365
- private
366
-
367
- def generate_correlation_id
368
- "api-#{context.request_id}-#{context.user_id}"
369
- end
370
- end
371
- ```
117
+ class ProcessReport < CMDx::Task
118
+ # Default timeout: 3 seconds
119
+ register :middleware, CMDx::Middlewares::Timeout
372
120
 
373
- #### Request Tracing Integration
121
+ # Seconds (takes Numeric, Symbol, Proc, Lambda, Class, Module)
122
+ register :middleware, CMDx::Middlewares::Timeout, seconds: :max_processing_time
374
123
 
375
- ```ruby
376
- class ApiController < ApplicationController
377
- before_action :set_correlation_id
124
+ # If or Unless (takes Symbol, Proc, Lambda, Class, Module)
125
+ register :middleware, CMDx::Middlewares::Timeout, unless: -> { self.class.name.include?("Quick") }
378
126
 
379
- def process_order
380
- result = ProcessOrderTask.call(order_params)
381
-
382
- if result.success?
383
- render json: { order: result.context.order, correlation_id: result.chain.id }
384
- else
385
- render json: { error: result.reason }, status: 422
386
- end
127
+ def work
128
+ # Your logic here...
387
129
  end
388
130
 
389
131
  private
390
132
 
391
- def set_correlation_id
392
- correlation_id = request.headers['X-Correlation-ID'] || request.uuid
393
- CMDx::Correlator.id = correlation_id
394
- response.headers['X-Correlation-ID'] = correlation_id
133
+ def max_processing_time
134
+ Rails.env.production? ? 2 : 10
395
135
  end
396
136
  end
397
137
 
398
- class ProcessOrderTask < CMDx::Task
399
- use :middleware, CMDx::Middlewares::Correlate
138
+ # Slow task
139
+ result = ProcessReport.execute
400
140
 
401
- def call
402
- # Inherits correlation ID from controller thread context
403
- ValidateOrderDataTask.call(context)
404
- ChargePaymentTask.call(context)
405
- SendConfirmationEmailTask.call(context)
406
- end
407
- end
141
+ result.state #=> "interrupted"
142
+ result.status #=> "failure"
143
+ result.reason #=> "[CMDx::TimeoutError] execution exceeded 3 seconds"
144
+ result.cause #=> <CMDx::TimeoutError>
145
+ result.metadata #=> { limit: 3 }
408
146
  ```
409
147
 
410
- ## Writing Custom Middleware
148
+ ### Correlate
411
149
 
412
- > [!IMPORTANT]
413
- > Custom middleware must inherit from `CMDx::Middleware` and implement the `call(task, callable)` method.
150
+ Tags tasks with a global correlation ID for distributed tracing:
414
151
 
415
152
  ```ruby
416
- class DatabaseTransactionMiddleware < CMDx::Middleware
417
- def call(task, callable)
418
- ActiveRecord::Base.transaction do
419
- result = callable.call(task)
153
+ class ProcessExport < CMDx::Task
154
+ # Default correlation ID generation
155
+ register :middleware, CMDx::Middlewares::Correlate
420
156
 
421
- # Rollback transaction if task failed
422
- raise ActiveRecord::Rollback if result.failed?
157
+ # Seconds (takes Object, Symbol, Proc, Lambda, Class, Module)
158
+ register :middleware, CMDx::Middlewares::Correlate, id: proc { |task| task.context.session_id }
423
159
 
424
- result
425
- end
426
- end
427
- end
160
+ # If or Unless (takes Symbol, Proc, Lambda, Class, Module)
161
+ register :middleware, CMDx::Middlewares::Correlate, if: :correlation_enabled?
428
162
 
429
- class CacheMiddleware < CMDx::Middleware
430
- def initialize(ttl: 300, key_prefix: nil)
431
- @ttl = ttl
432
- @key_prefix = key_prefix
433
- end
434
-
435
- def call(task, callable)
436
- cache_key = build_cache_key(task)
437
- cached_result = Rails.cache.read(cache_key)
438
-
439
- return cached_result if cached_result
440
-
441
- result = callable.call(task)
442
-
443
- if result.success?
444
- Rails.cache.write(cache_key, result, expires_in: @ttl)
445
- end
446
-
447
- result
163
+ def work
164
+ # Your logic here...
448
165
  end
449
166
 
450
167
  private
451
168
 
452
- def build_cache_key(task)
453
- base_key = task.class.name.underscore
454
- param_hash = Digest::MD5.hexdigest(task.context.to_h.to_json)
455
- [@key_prefix, base_key, param_hash].compact.join(':')
169
+ def correlation_enabled?
170
+ ENV["CORRELATION_ENABLED"] == "true"
456
171
  end
457
172
  end
458
- ```
459
173
 
460
- ## Error Handling
174
+ result = ProcessExport.execute
175
+ result.metadata #=> { correlation_id: "550e8400-e29b-41d4-a716-446655440000" }
176
+ ```
461
177
 
462
- > [!WARNING]
463
- > Middleware errors can prevent task execution. Handle exceptions appropriately and consider their impact on the execution chain.
178
+ ### Runtime
464
179
 
465
- ### Common Error Scenarios
180
+ The runtime middleware tags tasks with how long it took to execute the task.
181
+ The calculation uses a monotonic clock and the time is returned in milliseconds.
466
182
 
467
183
  ```ruby
468
- class ErrorProneMiddleware < CMDx::Middleware
469
- def call(task, callable)
470
- # Middleware error prevents task execution
471
- raise "Configuration missing" unless configured?
472
-
473
- callable.call(task)
474
- rescue StandardError => e
475
- # Handle middleware-specific errors
476
- task.fail!(reason: "Middleware error: #{e.message}")
477
- task.result
184
+ class PerformanceMonitoringCheck
185
+ def call(task)
186
+ task.context.tenant.monitoring_enabled?
478
187
  end
479
188
  end
480
189
 
481
- # Timeout errors are automatically handled
482
- class ProcessOrderTask < CMDx::Task
483
- use :middleware, CMDx::Middlewares::Timeout, seconds: 5
190
+ class ProcessExport < CMDx::Task
191
+ # Default timeout is 3 seconds
192
+ register :middleware, CMDx::Middlewares::Runtime
484
193
 
485
- def call
486
- sleep(10) # Exceeds timeout
487
- end
194
+ # If or Unless (takes Symbol, Proc, Lambda, Class, Module)
195
+ register :middleware, CMDx::Middlewares::Runtime, if: PerformanceMonitoringCheck
488
196
  end
489
197
 
490
- result = ProcessOrderTask.call
491
- result.failed? # true
492
- result.reason # → "Task timed out after 5 seconds"
493
- ```
494
-
495
- ### Middleware Error Recovery
496
-
497
- ```ruby
498
- class ResilientMiddleware < CMDx::Middleware
499
- def call(task, callable)
500
- callable.call(task)
501
- rescue ExternalServiceError => e
502
- # Log error but allow task to complete
503
- Rails.logger.error "External service unavailable: #{e.message}"
504
-
505
- # Continue execution with degraded functionality
506
- task.context.external_service_available = false
507
- callable.call(task)
508
- end
509
- end
198
+ result = ProcessExport.execute
199
+ result.metadata #=> { runtime: 1247 } (ms)
510
200
  ```
511
201
 
512
- > [!TIP]
513
- > Design middleware to fail gracefully when possible. Consider whether middleware failure should prevent task execution or allow degraded operation.
514
-
515
202
  ---
516
203
 
517
204
  - **Prev:** [Callbacks](callbacks.md)
518
- - **Next:** [Workflows](workflows.md)
205
+ - **Next:** [Logging](logging.md)