cmdx 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +21 -0
  4. data/.cursor/prompts/yardoc.md +13 -0
  5. data/.rubocop.yml +2 -0
  6. data/CHANGELOG.md +29 -3
  7. data/README.md +2 -1
  8. data/docs/ai_prompts.md +269 -195
  9. data/docs/basics/call.md +126 -60
  10. data/docs/basics/chain.md +190 -160
  11. data/docs/basics/context.md +242 -154
  12. data/docs/basics/setup.md +302 -32
  13. data/docs/callbacks.md +382 -119
  14. data/docs/configuration.md +211 -49
  15. data/docs/deprecation.md +245 -0
  16. data/docs/getting_started.md +161 -39
  17. data/docs/internationalization.md +590 -70
  18. data/docs/interruptions/exceptions.md +135 -118
  19. data/docs/interruptions/faults.md +152 -127
  20. data/docs/interruptions/halt.md +134 -80
  21. data/docs/logging.md +183 -120
  22. data/docs/middlewares.md +165 -392
  23. data/docs/outcomes/result.md +140 -112
  24. data/docs/outcomes/states.md +134 -99
  25. data/docs/outcomes/statuses.md +204 -146
  26. data/docs/parameters/coercions.md +251 -289
  27. data/docs/parameters/defaults.md +224 -169
  28. data/docs/parameters/definitions.md +289 -141
  29. data/docs/parameters/namespacing.md +250 -161
  30. data/docs/parameters/validations.md +247 -159
  31. data/docs/testing.md +196 -203
  32. data/docs/workflows.md +146 -101
  33. data/lib/cmdx/.DS_Store +0 -0
  34. data/lib/cmdx/callback.rb +39 -55
  35. data/lib/cmdx/callback_registry.rb +80 -73
  36. data/lib/cmdx/chain.rb +65 -122
  37. data/lib/cmdx/chain_inspector.rb +23 -116
  38. data/lib/cmdx/chain_serializer.rb +34 -146
  39. data/lib/cmdx/coercion.rb +57 -0
  40. data/lib/cmdx/coercion_registry.rb +113 -0
  41. data/lib/cmdx/coercions/array.rb +18 -36
  42. data/lib/cmdx/coercions/big_decimal.rb +21 -33
  43. data/lib/cmdx/coercions/boolean.rb +21 -40
  44. data/lib/cmdx/coercions/complex.rb +18 -31
  45. data/lib/cmdx/coercions/date.rb +20 -39
  46. data/lib/cmdx/coercions/date_time.rb +22 -39
  47. data/lib/cmdx/coercions/float.rb +19 -32
  48. data/lib/cmdx/coercions/hash.rb +22 -41
  49. data/lib/cmdx/coercions/integer.rb +20 -33
  50. data/lib/cmdx/coercions/rational.rb +20 -32
  51. data/lib/cmdx/coercions/string.rb +23 -31
  52. data/lib/cmdx/coercions/time.rb +24 -40
  53. data/lib/cmdx/coercions/virtual.rb +14 -31
  54. data/lib/cmdx/configuration.rb +101 -162
  55. data/lib/cmdx/context.rb +34 -166
  56. data/lib/cmdx/core_ext/hash.rb +42 -67
  57. data/lib/cmdx/core_ext/module.rb +35 -79
  58. data/lib/cmdx/core_ext/object.rb +63 -98
  59. data/lib/cmdx/correlator.rb +59 -154
  60. data/lib/cmdx/error.rb +37 -202
  61. data/lib/cmdx/errors.rb +153 -216
  62. data/lib/cmdx/fault.rb +68 -150
  63. data/lib/cmdx/faults.rb +26 -137
  64. data/lib/cmdx/immutator.rb +22 -110
  65. data/lib/cmdx/lazy_struct.rb +110 -186
  66. data/lib/cmdx/log_formatters/json.rb +14 -40
  67. data/lib/cmdx/log_formatters/key_value.rb +14 -40
  68. data/lib/cmdx/log_formatters/line.rb +14 -48
  69. data/lib/cmdx/log_formatters/logstash.rb +14 -57
  70. data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
  71. data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
  72. data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
  73. data/lib/cmdx/log_formatters/raw.rb +19 -49
  74. data/lib/cmdx/logger.rb +22 -79
  75. data/lib/cmdx/logger_ansi.rb +31 -72
  76. data/lib/cmdx/logger_serializer.rb +74 -103
  77. data/lib/cmdx/middleware.rb +56 -60
  78. data/lib/cmdx/middleware_registry.rb +82 -77
  79. data/lib/cmdx/middlewares/correlate.rb +41 -226
  80. data/lib/cmdx/middlewares/timeout.rb +46 -185
  81. data/lib/cmdx/parameter.rb +167 -183
  82. data/lib/cmdx/parameter_evaluator.rb +231 -0
  83. data/lib/cmdx/parameter_inspector.rb +37 -55
  84. data/lib/cmdx/parameter_registry.rb +65 -84
  85. data/lib/cmdx/parameter_serializer.rb +32 -76
  86. data/lib/cmdx/railtie.rb +24 -107
  87. data/lib/cmdx/result.rb +254 -259
  88. data/lib/cmdx/result_ansi.rb +28 -80
  89. data/lib/cmdx/result_inspector.rb +34 -70
  90. data/lib/cmdx/result_logger.rb +23 -77
  91. data/lib/cmdx/result_serializer.rb +59 -125
  92. data/lib/cmdx/rspec/matchers.rb +28 -0
  93. data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
  94. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
  95. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
  96. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
  97. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
  98. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
  99. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
  100. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
  101. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
  102. data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
  103. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
  104. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
  105. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
  106. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
  107. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
  108. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
  109. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
  110. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
  111. data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
  112. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
  113. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
  114. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
  115. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
  116. data/lib/cmdx/task.rb +336 -427
  117. data/lib/cmdx/task_deprecator.rb +52 -0
  118. data/lib/cmdx/task_processor.rb +246 -0
  119. data/lib/cmdx/task_serializer.rb +34 -69
  120. data/lib/cmdx/utils/ansi_color.rb +13 -89
  121. data/lib/cmdx/utils/log_timestamp.rb +13 -42
  122. data/lib/cmdx/utils/monotonic_runtime.rb +11 -63
  123. data/lib/cmdx/utils/name_affix.rb +21 -71
  124. data/lib/cmdx/validator.rb +57 -0
  125. data/lib/cmdx/validator_registry.rb +108 -0
  126. data/lib/cmdx/validators/exclusion.rb +55 -94
  127. data/lib/cmdx/validators/format.rb +31 -85
  128. data/lib/cmdx/validators/inclusion.rb +65 -110
  129. data/lib/cmdx/validators/length.rb +117 -133
  130. data/lib/cmdx/validators/numeric.rb +123 -130
  131. data/lib/cmdx/validators/presence.rb +38 -79
  132. data/lib/cmdx/version.rb +1 -7
  133. data/lib/cmdx/workflow.rb +58 -330
  134. data/lib/cmdx.rb +1 -1
  135. data/lib/generators/cmdx/install_generator.rb +14 -31
  136. data/lib/generators/cmdx/task_generator.rb +39 -55
  137. data/lib/generators/cmdx/templates/install.rb +24 -6
  138. data/lib/generators/cmdx/workflow_generator.rb +41 -66
  139. data/lib/locales/ar.yml +0 -1
  140. data/lib/locales/cs.yml +0 -1
  141. data/lib/locales/da.yml +0 -1
  142. data/lib/locales/de.yml +0 -1
  143. data/lib/locales/el.yml +0 -1
  144. data/lib/locales/en.yml +0 -1
  145. data/lib/locales/es.yml +0 -1
  146. data/lib/locales/fi.yml +0 -1
  147. data/lib/locales/fr.yml +0 -1
  148. data/lib/locales/he.yml +0 -1
  149. data/lib/locales/hi.yml +0 -1
  150. data/lib/locales/it.yml +0 -1
  151. data/lib/locales/ja.yml +0 -1
  152. data/lib/locales/ko.yml +0 -1
  153. data/lib/locales/nl.yml +0 -1
  154. data/lib/locales/no.yml +0 -1
  155. data/lib/locales/pl.yml +0 -1
  156. data/lib/locales/pt.yml +0 -1
  157. data/lib/locales/ru.yml +0 -1
  158. data/lib/locales/sv.yml +0 -1
  159. data/lib/locales/th.yml +0 -1
  160. data/lib/locales/tr.yml +0 -1
  161. data/lib/locales/vi.yml +0 -1
  162. data/lib/locales/zh.yml +0 -1
  163. metadata +36 -8
  164. data/lib/cmdx/parameter_validator.rb +0 -81
  165. data/lib/cmdx/parameter_value.rb +0 -244
  166. data/lib/cmdx/parameters_inspector.rb +0 -72
  167. data/lib/cmdx/parameters_serializer.rb +0 -115
  168. data/lib/cmdx/rspec/result_matchers.rb +0 -917
  169. data/lib/cmdx/rspec/task_matchers.rb +0 -570
  170. data/lib/cmdx/validators/custom.rb +0 -102
data/docs/basics/chain.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Basics - Chain
2
2
 
3
- A chain represents a collection of related task executions that share a common execution context. Chains provide unified tracking, indexing, and reporting for task workflows using thread-local storage to automatically group related tasks without manual coordination.
3
+ Chains automatically group related task executions within a thread, providing unified tracking, correlation, and execution context management. Each thread maintains its own chain through thread-local storage, eliminating the need for manual coordination.
4
4
 
5
5
  ## Table of Contents
6
6
 
@@ -12,266 +12,296 @@ A chain represents a collection of related task executions that share a common e
12
12
  - [Correlation ID Integration](#correlation-id-integration)
13
13
  - [State Delegation](#state-delegation)
14
14
  - [Serialization and Logging](#serialization-and-logging)
15
+ - [Error Handling](#error-handling)
15
16
 
16
17
  ## TLDR
17
18
 
18
- - **Chains** - Automatically group related task executions within the same thread
19
- - **Thread-local** - Each thread gets its own chain, no manual coordination needed
20
- - **Inheritance** - Subtasks automatically join the current thread's chain
21
- - **Correlation** - Chain IDs serve as correlation identifiers for tracing
22
- - **Access** - Use `result.chain.id` and `result.chain.results` to inspect execution trails
19
+ ```ruby
20
+ # Automatic chain creation per thread
21
+ result = ProcessOrderTask.call(order_id: 123)
22
+ result.chain.id # Unique chain ID
23
+ result.chain.results.size # All tasks in this chain
24
+
25
+ # Access current thread's chain
26
+ CMDx::Chain.current # Current chain or nil
27
+ CMDx::Chain.clear # Clear thread's chain
28
+
29
+ # Subtasks automatically inherit chain
30
+ class ProcessOrderTask < CMDx::Task
31
+ def call
32
+ # These inherit the same chain automatically
33
+ ValidateOrderTask.call!(order_id: order_id)
34
+ ChargePaymentTask.call!(order_id: order_id)
35
+ end
36
+ end
37
+ ```
23
38
 
24
39
  ## Thread-Local Chain Management
25
40
 
26
- Chains use thread-local storage to automatically group related task executions within the same thread while maintaining isolation across different threads:
41
+ > [!NOTE]
42
+ > Each thread maintains its own chain context through thread-local storage, providing automatic isolation without manual coordination.
27
43
 
28
44
  ```ruby
29
- # Each thread gets its own chain context
45
+ # Thread A
30
46
  Thread.new do
31
47
  result = ProcessOrderTask.call(order_id: 123)
32
- result.chain.id # => unique ID for this thread
48
+ result.chain.id # "018c2b95-b764-7615-a924-cc5b910ed1e5"
33
49
  end
34
50
 
51
+ # Thread B (completely separate chain)
35
52
  Thread.new do
36
53
  result = ProcessOrderTask.call(order_id: 456)
37
- result.chain.id # => different unique ID
54
+ result.chain.id # "018c2b95-c821-7892-b156-dd7c921fe2a3"
38
55
  end
39
56
 
40
- # Access the current thread's chain
41
- CMDx::Chain.current # => current chain or nil
42
- CMDx::Chain.clear # => clears current thread's chain
57
+ # Access current thread's chain
58
+ CMDx::Chain.current # Returns current chain or nil
59
+ CMDx::Chain.clear # Clears current thread's chain
43
60
  ```
44
61
 
45
62
  ## Automatic Chain Creation
46
63
 
47
- Every task execution automatically creates or joins a thread-local chain context:
64
+ Every task execution automatically creates or joins the current thread's chain:
48
65
 
49
66
  ```ruby
50
- # Single task creates its own chain
51
- result = ProcessUserOrderTask.call(order_id: 123)
52
- result.chain.id #=> "018c2b95-b764-7615-a924-cc5b910ed1e5"
53
- result.chain.results.size #=> 1
54
-
55
- # Subsequent tasks in the same thread join the existing chain
56
- result2 = AnotherTask.call(data: "test")
57
- result2.chain.id == result.chain.id #=> true
58
- result2.chain.results.size #=> 2
67
+ # First task creates new chain
68
+ result1 = ProcessOrderTask.call(order_id: 123)
69
+ result1.chain.id # "018c2b95-b764-7615-a924-cc5b910ed1e5"
70
+ result1.chain.results.size # 1
71
+
72
+ # Second task joins existing chain
73
+ result2 = SendEmailTask.call(to: "user@example.com")
74
+ result2.chain.id == result1.chain.id # true
75
+ result2.chain.results.size # 2
76
+
77
+ # Both results reference the same chain
78
+ result1.chain.results == result2.chain.results # true
59
79
  ```
60
80
 
61
81
  ## Chain Inheritance
62
82
 
63
- When tasks call other tasks within the same thread, they automatically inherit the current chain, creating a cohesive execution trail:
83
+ > [!IMPORTANT]
84
+ > When tasks call subtasks within the same thread, all executions automatically inherit the current chain, creating a unified execution trail.
64
85
 
65
86
  ```ruby
66
- class ProcessUserOrderTask < CMDx::Task
87
+ class ProcessOrderTask < CMDx::Task
67
88
  def call
68
89
  context.order = Order.find(order_id)
69
90
 
70
- # Subtasks automatically inherit the current thread's chain
71
- SendOrderConfirmationTask.call(order_id: order_id)
72
- NotifyWarehousePartnersTask.call(order_id: order_id)
91
+ # Subtasks automatically inherit current chain
92
+ ValidateOrderTask.call!(order_id: order_id)
93
+ ChargePaymentTask.call!(order_id: order_id)
94
+ SendConfirmationTask.call!(order_id: order_id)
73
95
  end
74
96
  end
75
97
 
76
- result = ProcessUserOrderTask.call(order_id: 123)
98
+ result = ProcessOrderTask.call(order_id: 123)
77
99
  chain = result.chain
78
100
 
79
- # All related tasks share the same chain automatically
80
- chain.results.size #=> 3
101
+ # All tasks share the same chain
102
+ chain.results.size # 4 (main task + 3 subtasks)
81
103
  chain.results.map(&:task).map(&:class)
82
- #=> [ProcessUserOrderTask, SendOrderConfirmationTask, NotifyWarehousePartnersTask]
104
+ # [ProcessOrderTask, ValidateOrderTask, ChargePaymentTask, SendConfirmationTask]
83
105
  ```
84
106
 
85
- > [!NOTE]
86
- > Tasks automatically inherit the current thread's chain, creating a unified execution trail for debugging and monitoring purposes without any manual chain management.
87
-
88
107
  ## Chain Structure and Metadata
89
108
 
90
- Chains provide comprehensive execution information:
109
+ Chains provide comprehensive execution information with state delegation:
91
110
 
92
111
  ```ruby
93
- result = ProcessUserOrderTask.call(order_id: 123)
112
+ result = ProcessOrderTask.call(order_id: 123)
94
113
  chain = result.chain
95
114
 
96
115
  # Chain identification
97
- chain.id #=> "018c2b95-b764-7615-a924-cc5b910ed1e5"
98
- chain.results #=> [<CMDx::Result ...>, <CMDx::Result ...>]
99
-
100
- # Execution state (delegates to outer most result)
101
- chain.state #=> "complete"
102
- chain.status #=> "success"
103
- chain.outcome #=> "success"
104
- chain.runtime #=> 0.5
116
+ chain.id # "018c2b95-b764-7615-a924-cc5b910ed1e5"
117
+ chain.results # Array of all results in execution order
118
+
119
+ # State delegation (from first/outer-most result)
120
+ chain.state # "complete"
121
+ chain.status # "success"
122
+ chain.outcome # "success"
123
+ chain.runtime # 1.2 (total execution time)
124
+
125
+ # Access individual results
126
+ chain.results.each_with_index do |result, index|
127
+ puts "#{index}: #{result.task.class} - #{result.status}"
128
+ end
105
129
  ```
106
130
 
107
131
  ## Correlation ID Integration
108
132
 
109
- Chains automatically integrate with the correlation tracking system through thread-local storage, providing seamless request tracing across task boundaries. The chain ID serves as the correlation identifier, enabling you to trace execution flows through distributed systems and complex business logic.
110
-
111
- ### Custom Chain IDs
112
-
113
- You can specify custom chain IDs for specific correlation contexts:
114
-
115
- ```ruby
116
- # Create a chain with custom ID
117
- chain = CMDx::Chain.new(id: "user-session-123")
118
- CMDx::Chain.current = chain
119
-
120
- result = ProcessUserOrderTask.call(order_id: 123)
121
- result.chain.id #=> "user-session-123"
122
- ```
133
+ > [!TIP]
134
+ > Chain IDs serve as correlation identifiers, enabling request tracing across distributed systems and complex workflows.
123
135
 
124
- ### Automatic Correlation Inheritance
136
+ ### Automatic Correlation
125
137
 
126
- Chains inherit correlation IDs using a hierarchical precedence system:
138
+ Chains integrate with the correlation system using hierarchical precedence:
127
139
 
128
140
  ```ruby
129
141
  # 1. Existing chain ID takes precedence
130
- CMDx::Chain.current = CMDx::Chain.new(id: "custom-correlation-123")
131
- result = ProcessUserOrderTask.call(order_id: 123)
132
- result.chain.id #=> "custom-correlation-123"
142
+ CMDx::Chain.current = CMDx::Chain.new(id: "request-123")
143
+ result = ProcessOrderTask.call(order_id: 456)
144
+ result.chain.id # "request-123"
133
145
 
134
- # 2. Thread-local correlation ID is used if no chain exists
146
+ # 2. Thread-local correlation used if no chain exists
135
147
  CMDx::Chain.clear
136
- CMDx::Correlator.id = "thread-correlation-456"
137
- result = ProcessUserOrderTask.call
138
- result.chain.id #=> "thread-correlation-456"
148
+ CMDx::Correlator.id = "session-456"
149
+ result = ProcessOrderTask.call(order_id: 789)
150
+ result.chain.id # "session-456"
139
151
 
140
152
  # 3. Generated UUID when no correlation exists
141
153
  CMDx::Correlator.clear
142
- result = ProcessUserOrderTask.call
143
- result.chain.id #=> "018c2b95-b764-7615-a924-cc5b910ed1e5" (generated)
154
+ result = ProcessOrderTask.call(order_id: 101)
155
+ result.chain.id # "018c2b95-b764-7615-a924-cc5b910ed1e5" (generated)
144
156
  ```
145
157
 
146
- ### Cross-Task Correlation Propagation
147
-
148
- When tasks call subtasks within the same thread, correlation IDs automatically propagate:
158
+ ### Custom Chain IDs
149
159
 
150
160
  ```ruby
151
- class ProcessUserOrderTask < CMDx::Task
152
- def call
153
- context.order = Order.find(order_id)
154
-
155
- # Subtasks inherit the same correlation ID automatically
156
- SendOrderConfirmationTask.call(order_id: order_id)
157
- NotifyWarehousePartnersTask.call(order_id: order_id)
158
- end
159
- end
160
-
161
- # Set correlation for this execution context
162
- CMDx::Chain.current = CMDx::Chain.new(id: "user-order-correlation-123")
161
+ # Create chain with specific correlation ID
162
+ chain = CMDx::Chain.new(id: "api-request-789")
163
+ CMDx::Chain.current = chain
163
164
 
164
- result = ProcessUserOrderTask.call(order_id: 456)
165
- chain = result.chain
165
+ result = ProcessApiRequestTask.call(data: payload)
166
+ result.chain.id # "api-request-789"
166
167
 
167
- # All tasks share the same correlation ID
168
- chain.id #=> "user-order-correlation-123"
169
- chain.results.all? { |r| r.chain.id == "user-order-correlation-123" } #=> true
168
+ # All subtasks inherit the same correlation ID
169
+ result.chain.results.all? { |r| r.chain.id == "api-request-789" } # true
170
170
  ```
171
171
 
172
172
  ### Correlation Context Management
173
173
 
174
- Use correlation blocks to manage correlation scope:
175
-
176
174
  ```ruby
177
- # Correlation applies only within the block
178
- CMDx::Correlator.use("api-request-789") do
179
- result = ProcessApiRequestTask.call(request_data: data)
180
- result.chain.id #=> "api-request-789"
175
+ # Scoped correlation context
176
+ CMDx::Correlator.use("user-session-123") do
177
+ result = ProcessUserActionTask.call(action: "purchase")
178
+ result.chain.id # "user-session-123"
181
179
 
182
- # Nested task calls inherit the same correlation
183
- AuditLogTask.call(audit_data: data)
180
+ # Nested operations inherit correlation
181
+ AuditLogTask.call(event: "purchase_completed")
184
182
  end
185
183
 
186
- # Outside the block, correlation context is restored
187
- result = AnotherTask.call
188
- result.chain.id #=> different correlation ID
184
+ # Outside block, correlation context restored
185
+ result = OtherTask.call
186
+ result.chain.id # Different correlation ID
189
187
  ```
190
188
 
191
- ### Middleware Integration
189
+ ## State Delegation
192
190
 
193
- The `CMDx::Middlewares::Correlate` middleware automatically manages correlation contexts during task execution:
191
+ > [!WARNING]
192
+ > Chain state always reflects the first (outer-most) task result, not individual subtask outcomes. Subtasks maintain their own success/failure states.
194
193
 
195
194
  ```ruby
196
195
  class ProcessOrderTask < CMDx::Task
197
- # Apply correlate middleware globally or per-task
198
- use CMDx::Middlewares::Correlate
199
-
200
196
  def call
201
- # Correlation is automatically managed
202
- # Chain ID reflects the established correlation context
197
+ ValidateOrderTask.call!(order_id: order_id) # Success
198
+ ChargePaymentTask.call!(order_id: order_id) # Failure
203
199
  end
204
200
  end
205
- ```
206
201
 
207
- > [!TIP]
208
- > Chain IDs serve as correlation identifiers, making it easy to trace related operations across your application. The thread-local storage ensures automatic correlation without manual chain management.
202
+ result = ProcessOrderTask.call(order_id: 123)
203
+ chain = result.chain
209
204
 
210
- > [!NOTE]
211
- > Correlation IDs are particularly useful for debugging distributed systems, API request tracing, and understanding complex business workflows. All logs and results automatically include the chain ID for correlation.
205
+ # Chain delegates to main task (first result)
206
+ chain.status # "failed" (ProcessOrderTask failed due to subtask)
207
+ chain.state # "interrupted"
212
208
 
213
- ## State Delegation
209
+ # Individual results maintain their own state
210
+ chain.results[0].status # "failed" (ProcessOrderTask - main)
211
+ chain.results[1].status # "success" (ValidateOrderTask)
212
+ chain.results[2].status # "failed" (ChargePaymentTask)
213
+ ```
214
214
 
215
- Chain state information delegates to the first (outer most) result, representing the overall execution outcome:
215
+ ## Serialization and Logging
216
216
 
217
- ```ruby
218
- class ProcessOrderTask < CMDx::Task
219
- def call
220
- ValidateOrderDataTask.call!(order_id: order_id) # Success
221
- ProcessOrderPaymentTask.call!(order_id: order_id) # Failed
222
- end
223
- end
217
+ Chains provide comprehensive serialization for monitoring and debugging:
224
218
 
225
- result = ProcessOrderTask.call
219
+ ```ruby
220
+ result = ProcessOrderTask.call(order_id: 123)
226
221
  chain = result.chain
227
222
 
228
- # Chain status reflects the main task, not subtasks
229
- chain.status #=> "failed" (ProcessOrderPaymentTask failed)
230
- chain.state #=> "interrupted"
223
+ # Structured data representation
224
+ chain.to_h
225
+ # {
226
+ # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
227
+ # state: "complete",
228
+ # status: "success",
229
+ # outcome: "success",
230
+ # runtime: 0.8,
231
+ # results: [
232
+ # { class: "ProcessOrderTask", state: "complete", status: "success", ... },
233
+ # { class: "ValidateOrderTask", state: "complete", status: "success", ... },
234
+ # { class: "ChargePaymentTask", state: "complete", status: "success", ... }
235
+ # ]
236
+ # }
237
+
238
+ # Human-readable execution summary
239
+ puts chain.to_s
240
+ # chain: 018c2b95-b764-7615-a924-cc5b910ed1e5
241
+ # ================================================
242
+ #
243
+ # ProcessOrderTask: index=0 state=complete status=success runtime=0.8
244
+ # ValidateOrderTask: index=1 state=complete status=success runtime=0.1
245
+ # ChargePaymentTask: index=2 state=complete status=success runtime=0.5
246
+ #
247
+ # ================================================
248
+ # state: complete | status: success | outcome: success | runtime: 0.8
249
+ ```
250
+
251
+ ## Error Handling
252
+
253
+ ### Chain Access Patterns
231
254
 
232
- # Individual task results maintain their own state
233
- chain.results[0].status #=> "failed" (ProcessOrderTask)
234
- chain.results[1].status #=> "success" (ValidateOrderDataTask)
235
- chain.results[2].status #=> "failed" (ProcessOrderPaymentTask)
255
+ ```ruby
256
+ # Safe chain access
257
+ result = ProcessOrderTask.call(order_id: 123)
258
+
259
+ if result.chain
260
+ correlation_id = result.chain.id
261
+ execution_count = result.chain.results.size
262
+ else
263
+ # Handle missing chain (shouldn't happen in normal execution)
264
+ correlation_id = "unknown"
265
+ end
236
266
  ```
237
267
 
268
+ ### Thread Safety
269
+
238
270
  > [!IMPORTANT]
239
- > Chain state always reflects the first (outer most) task outcome, not the subtasks. Individual subtask results maintain their own success/failure states.
271
+ > Chain operations are thread-safe within individual threads but chains should not be shared across threads. Each thread maintains its own isolated chain context.
240
272
 
241
- ## Serialization and Logging
273
+ ```ruby
274
+ # Safe: Each thread has its own chain
275
+ threads = 3.times.map do |i|
276
+ Thread.new do
277
+ result = ProcessOrderTask.call(order_id: 100 + i)
278
+ result.chain.id # Unique per thread
279
+ end
280
+ end
281
+
282
+ # Collect results safely
283
+ chain_ids = threads.map(&:value)
284
+ chain_ids.uniq.size # 3 (all different)
285
+ ```
242
286
 
243
- Chains provide comprehensive serialization capabilities for monitoring and debugging:
287
+ ### Chain State Validation
244
288
 
245
289
  ```ruby
246
- result = ProcessUserOrderTask.call(order_id: 123)
290
+ result = ProcessOrderTask.call(order_id: 123)
247
291
  chain = result.chain
248
292
 
249
- # Hash representation with all execution data
250
- chain.to_h
251
- #=> {
252
- # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
253
- # state: "complete",
254
- # status: "success",
255
- # outcome: "success",
256
- # runtime: 0.5,
257
- # results: [
258
- # { class: "ProcessUserOrderTask", state: "complete", status: "success", ... },
259
- # { class: "SendOrderConfirmationTask", state: "complete", status: "success", ... },
260
- # { class: "NotifyWarehousePartnersTask", state: "complete", status: "success", ... }
261
- # ]
262
- # }
263
-
264
- # Human-readable summary
265
- puts chain.to_s
266
- # chain: 018c2b95-b764-7615-a924-cc5b910ed1e5
267
- # ================================================
268
- #
269
- # ProcessUserOrderTask: index=0 state=complete status=success ...
270
- # SendOrderConfirmationTask: index=1 state=complete status=success ...
271
- # NotifyWarehousePartnersTask: index=2 state=complete status=success ...
272
- #
273
- # ================================================
274
- # state: complete | status: success | outcome: success | runtime: 0.5
293
+ # Validate chain integrity
294
+ case chain.state
295
+ when "complete"
296
+ # All tasks finished normally
297
+ process_successful_chain(chain)
298
+ when "interrupted"
299
+ # Task was halted or failed
300
+ handle_chain_interruption(chain)
301
+ else
302
+ # Unexpected state
303
+ log_chain_anomaly(chain)
304
+ end
275
305
  ```
276
306
 
277
307
  ---