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.
- checksums.yaml +4 -4
- data/.cursor/prompts/docs.md +9 -0
- data/.cursor/prompts/rspec.md +21 -0
- data/.cursor/prompts/yardoc.md +13 -0
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +29 -3
- data/README.md +2 -1
- data/docs/ai_prompts.md +269 -195
- data/docs/basics/call.md +126 -60
- data/docs/basics/chain.md +190 -160
- data/docs/basics/context.md +242 -154
- data/docs/basics/setup.md +302 -32
- data/docs/callbacks.md +382 -119
- data/docs/configuration.md +211 -49
- data/docs/deprecation.md +245 -0
- data/docs/getting_started.md +161 -39
- data/docs/internationalization.md +590 -70
- data/docs/interruptions/exceptions.md +135 -118
- data/docs/interruptions/faults.md +152 -127
- data/docs/interruptions/halt.md +134 -80
- data/docs/logging.md +183 -120
- data/docs/middlewares.md +165 -392
- data/docs/outcomes/result.md +140 -112
- data/docs/outcomes/states.md +134 -99
- data/docs/outcomes/statuses.md +204 -146
- data/docs/parameters/coercions.md +251 -289
- data/docs/parameters/defaults.md +224 -169
- data/docs/parameters/definitions.md +289 -141
- data/docs/parameters/namespacing.md +250 -161
- data/docs/parameters/validations.md +247 -159
- data/docs/testing.md +196 -203
- data/docs/workflows.md +146 -101
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +39 -55
- data/lib/cmdx/callback_registry.rb +80 -73
- data/lib/cmdx/chain.rb +65 -122
- data/lib/cmdx/chain_inspector.rb +23 -116
- data/lib/cmdx/chain_serializer.rb +34 -146
- data/lib/cmdx/coercion.rb +57 -0
- data/lib/cmdx/coercion_registry.rb +113 -0
- data/lib/cmdx/coercions/array.rb +18 -36
- data/lib/cmdx/coercions/big_decimal.rb +21 -33
- data/lib/cmdx/coercions/boolean.rb +21 -40
- data/lib/cmdx/coercions/complex.rb +18 -31
- data/lib/cmdx/coercions/date.rb +20 -39
- data/lib/cmdx/coercions/date_time.rb +22 -39
- data/lib/cmdx/coercions/float.rb +19 -32
- data/lib/cmdx/coercions/hash.rb +22 -41
- data/lib/cmdx/coercions/integer.rb +20 -33
- data/lib/cmdx/coercions/rational.rb +20 -32
- data/lib/cmdx/coercions/string.rb +23 -31
- data/lib/cmdx/coercions/time.rb +24 -40
- data/lib/cmdx/coercions/virtual.rb +14 -31
- data/lib/cmdx/configuration.rb +101 -162
- data/lib/cmdx/context.rb +34 -166
- data/lib/cmdx/core_ext/hash.rb +42 -67
- data/lib/cmdx/core_ext/module.rb +35 -79
- data/lib/cmdx/core_ext/object.rb +63 -98
- data/lib/cmdx/correlator.rb +59 -154
- data/lib/cmdx/error.rb +37 -202
- data/lib/cmdx/errors.rb +153 -216
- data/lib/cmdx/fault.rb +68 -150
- data/lib/cmdx/faults.rb +26 -137
- data/lib/cmdx/immutator.rb +22 -110
- data/lib/cmdx/lazy_struct.rb +110 -186
- data/lib/cmdx/log_formatters/json.rb +14 -40
- data/lib/cmdx/log_formatters/key_value.rb +14 -40
- data/lib/cmdx/log_formatters/line.rb +14 -48
- data/lib/cmdx/log_formatters/logstash.rb +14 -57
- data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
- data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
- data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
- data/lib/cmdx/log_formatters/raw.rb +19 -49
- data/lib/cmdx/logger.rb +22 -79
- data/lib/cmdx/logger_ansi.rb +31 -72
- data/lib/cmdx/logger_serializer.rb +74 -103
- data/lib/cmdx/middleware.rb +56 -60
- data/lib/cmdx/middleware_registry.rb +82 -77
- data/lib/cmdx/middlewares/correlate.rb +41 -226
- data/lib/cmdx/middlewares/timeout.rb +46 -185
- data/lib/cmdx/parameter.rb +167 -183
- data/lib/cmdx/parameter_evaluator.rb +231 -0
- data/lib/cmdx/parameter_inspector.rb +37 -55
- data/lib/cmdx/parameter_registry.rb +65 -84
- data/lib/cmdx/parameter_serializer.rb +32 -76
- data/lib/cmdx/railtie.rb +24 -107
- data/lib/cmdx/result.rb +254 -259
- data/lib/cmdx/result_ansi.rb +28 -80
- data/lib/cmdx/result_inspector.rb +34 -70
- data/lib/cmdx/result_logger.rb +23 -77
- data/lib/cmdx/result_serializer.rb +59 -125
- data/lib/cmdx/rspec/matchers.rb +28 -0
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
- data/lib/cmdx/task.rb +336 -427
- data/lib/cmdx/task_deprecator.rb +52 -0
- data/lib/cmdx/task_processor.rb +246 -0
- data/lib/cmdx/task_serializer.rb +34 -69
- data/lib/cmdx/utils/ansi_color.rb +13 -89
- data/lib/cmdx/utils/log_timestamp.rb +13 -42
- data/lib/cmdx/utils/monotonic_runtime.rb +11 -63
- data/lib/cmdx/utils/name_affix.rb +21 -71
- data/lib/cmdx/validator.rb +57 -0
- data/lib/cmdx/validator_registry.rb +108 -0
- data/lib/cmdx/validators/exclusion.rb +55 -94
- data/lib/cmdx/validators/format.rb +31 -85
- data/lib/cmdx/validators/inclusion.rb +65 -110
- data/lib/cmdx/validators/length.rb +117 -133
- data/lib/cmdx/validators/numeric.rb +123 -130
- data/lib/cmdx/validators/presence.rb +38 -79
- data/lib/cmdx/version.rb +1 -7
- data/lib/cmdx/workflow.rb +58 -330
- data/lib/cmdx.rb +1 -1
- data/lib/generators/cmdx/install_generator.rb +14 -31
- data/lib/generators/cmdx/task_generator.rb +39 -55
- data/lib/generators/cmdx/templates/install.rb +24 -6
- data/lib/generators/cmdx/workflow_generator.rb +41 -66
- data/lib/locales/ar.yml +0 -1
- data/lib/locales/cs.yml +0 -1
- data/lib/locales/da.yml +0 -1
- data/lib/locales/de.yml +0 -1
- data/lib/locales/el.yml +0 -1
- data/lib/locales/en.yml +0 -1
- data/lib/locales/es.yml +0 -1
- data/lib/locales/fi.yml +0 -1
- data/lib/locales/fr.yml +0 -1
- data/lib/locales/he.yml +0 -1
- data/lib/locales/hi.yml +0 -1
- data/lib/locales/it.yml +0 -1
- data/lib/locales/ja.yml +0 -1
- data/lib/locales/ko.yml +0 -1
- data/lib/locales/nl.yml +0 -1
- data/lib/locales/no.yml +0 -1
- data/lib/locales/pl.yml +0 -1
- data/lib/locales/pt.yml +0 -1
- data/lib/locales/ru.yml +0 -1
- data/lib/locales/sv.yml +0 -1
- data/lib/locales/th.yml +0 -1
- data/lib/locales/tr.yml +0 -1
- data/lib/locales/vi.yml +0 -1
- data/lib/locales/zh.yml +0 -1
- metadata +36 -8
- data/lib/cmdx/parameter_validator.rb +0 -81
- data/lib/cmdx/parameter_value.rb +0 -244
- data/lib/cmdx/parameters_inspector.rb +0 -72
- data/lib/cmdx/parameters_serializer.rb +0 -115
- data/lib/cmdx/rspec/result_matchers.rb +0 -917
- data/lib/cmdx/rspec/task_matchers.rb +0 -570
- data/lib/cmdx/validators/custom.rb +0 -102
@@ -1,156 +1,158 @@
|
|
1
1
|
# Interruptions - Exceptions
|
2
2
|
|
3
|
-
CMDx provides robust exception handling that differs between the `call` and `call!`
|
4
|
-
methods. Understanding how unhandled exceptions are processed is crucial for
|
5
|
-
building reliable task execution flows and implementing proper error handling strategies.
|
3
|
+
CMDx provides robust exception handling that differs between the `call` and `call!` methods. Understanding how unhandled exceptions are processed is crucial for building reliable task execution flows and implementing proper error handling strategies.
|
6
4
|
|
7
5
|
## Table of Contents
|
8
6
|
|
9
7
|
- [TLDR](#tldr)
|
10
|
-
- [Exception Handling
|
11
|
-
- [
|
8
|
+
- [Exception Handling Methods](#exception-handling-methods)
|
9
|
+
- [Exception Metadata](#exception-metadata)
|
10
|
+
- [Bang Call Behavior](#bang-call-behavior)
|
12
11
|
- [Exception Classification](#exception-classification)
|
12
|
+
- [Error Handling Patterns](#error-handling-patterns)
|
13
13
|
|
14
14
|
## TLDR
|
15
15
|
|
16
|
-
|
17
|
-
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
```ruby
|
17
|
+
# Non-bang call - captures ALL exceptions
|
18
|
+
result = ProcessOrderTask.call # Never raises, always returns result
|
19
|
+
result.failed? # true if exception occurred
|
20
|
+
result.metadata[:original_exception] # Access original exception
|
21
|
+
|
22
|
+
# Bang call - lets exceptions propagate
|
23
|
+
ProcessOrderTask.call! # Raises exceptions (except configured faults)
|
24
|
+
|
25
|
+
# Exception info always available in metadata
|
26
|
+
result.metadata[:reason] # Human-readable error message
|
27
|
+
result.metadata[:original_exception] # Original exception object
|
28
|
+
```
|
21
29
|
|
22
|
-
## Exception Handling
|
30
|
+
## Exception Handling Methods
|
31
|
+
|
32
|
+
> [!IMPORTANT]
|
33
|
+
> The key difference: `call` guarantees a result object, while `call!` allows exceptions to propagate for standard error handling patterns.
|
23
34
|
|
24
35
|
### Non-bang Call (`call`)
|
25
36
|
|
26
|
-
The `call` method captures **all** unhandled exceptions and converts them to
|
27
|
-
failed results, ensuring that no exceptions escape the task execution boundary.
|
28
|
-
This provides consistent, predictable behavior for result processing.
|
37
|
+
The `call` method captures **all** unhandled exceptions and converts them to failed results, ensuring predictable behavior and consistent result processing.
|
29
38
|
|
30
|
-
|
31
|
-
|
39
|
+
| Behavior | Description |
|
40
|
+
|----------|-------------|
|
41
|
+
| **Exception Capture** | All exceptions caught and converted |
|
42
|
+
| **Return Value** | Always returns a result object |
|
43
|
+
| **State** | `"interrupted"` for exception failures |
|
44
|
+
| **Status** | `"failed"` for all captured exceptions |
|
45
|
+
| **Metadata** | Exception details preserved |
|
32
46
|
|
47
|
+
```ruby
|
48
|
+
class ProcessPaymentTask < CMDx::Task
|
33
49
|
def call
|
34
|
-
|
35
|
-
undefined_method_call
|
50
|
+
raise ActiveRecord::RecordNotFound, "Payment method not found"
|
36
51
|
end
|
37
|
-
|
38
52
|
end
|
39
53
|
|
40
|
-
result =
|
54
|
+
result = ProcessPaymentTask.call
|
41
55
|
result.state #=> "interrupted"
|
42
56
|
result.status #=> "failed"
|
43
57
|
result.failed? #=> true
|
44
|
-
result.metadata #=> {
|
45
|
-
#=> reason: "[NoMethodError] undefined method `undefined_method_call`",
|
46
|
-
#=> original_exception: <NoMethodError>
|
47
|
-
#=> }
|
48
58
|
```
|
49
59
|
|
50
|
-
|
51
|
-
> The `call` method ensures no exceptions escape task execution, making it ideal
|
52
|
-
> for workflow processing and scenarios where you need guaranteed result objects.
|
53
|
-
|
54
|
-
### Exception Metadata Structure
|
60
|
+
### Bang Call (`call!`)
|
55
61
|
|
56
|
-
|
62
|
+
The `call!` method allows unhandled exceptions to propagate, enabling standard Ruby exception handling while respecting CMDx fault configuration.
|
57
63
|
|
58
64
|
```ruby
|
59
|
-
class
|
60
|
-
|
65
|
+
class ProcessPaymentTask < CMDx::Task
|
61
66
|
def call
|
62
|
-
|
63
|
-
raise ActiveRecord::ConnectionNotEstablished, "Database unavailable"
|
67
|
+
raise StandardError, "Payment gateway unavailable"
|
64
68
|
end
|
69
|
+
end
|
65
70
|
|
71
|
+
begin
|
72
|
+
ProcessPaymentTask.call!
|
73
|
+
rescue StandardError => e
|
74
|
+
puts "Handle exception: #{e.message}"
|
66
75
|
end
|
76
|
+
```
|
67
77
|
|
68
|
-
|
78
|
+
## Exception Metadata
|
69
79
|
|
70
|
-
|
71
|
-
result
|
72
|
-
result.metadata[:original_exception] #=> <ActiveRecord::ConnectionNotEstablished>
|
73
|
-
result.metadata[:original_exception].class #=> ActiveRecord::ConnectionNotEstablished
|
74
|
-
result.metadata[:original_exception].message #=> "Database unavailable"
|
75
|
-
result.metadata[:original_exception].backtrace #=> ["..."]
|
76
|
-
```
|
80
|
+
> [!NOTE]
|
81
|
+
> Exception information is preserved in result metadata, providing full debugging context while maintaining clean result interfaces.
|
77
82
|
|
78
|
-
###
|
83
|
+
### Metadata Structure
|
79
84
|
|
80
85
|
```ruby
|
81
|
-
result =
|
82
|
-
|
83
|
-
if result.failed? && result.metadata[:original_exception]
|
84
|
-
original = result.metadata[:original_exception]
|
86
|
+
result = ProcessPaymentTask.call
|
85
87
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
puts original.backtrace.first(5).join("\n")
|
88
|
+
# Exception metadata always includes:
|
89
|
+
result.metadata[:reason] #=> "[StandardError] Payment gateway unavailable"
|
90
|
+
result.metadata[:original_exception] #=> <StandardError instance>
|
90
91
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
handle_timeout_error(original)
|
97
|
-
when StandardError
|
98
|
-
handle_generic_error(original)
|
99
|
-
end
|
100
|
-
end
|
92
|
+
# Access original exception properties
|
93
|
+
exception = result.metadata[:original_exception]
|
94
|
+
exception.class #=> StandardError
|
95
|
+
exception.message #=> "Payment gateway unavailable"
|
96
|
+
exception.backtrace #=> ["lib/tasks/payment.rb:15:in `call'", ...]
|
101
97
|
```
|
102
98
|
|
103
|
-
|
104
|
-
|
105
|
-
The `call!` method allows unhandled exceptions to propagate **unless** they are
|
106
|
-
CMDx faults that match the `task_halt` configuration. This enables exception-based
|
107
|
-
control flow while still providing structured fault handling.
|
99
|
+
### Exception Type Checking
|
108
100
|
|
109
101
|
```ruby
|
110
|
-
class
|
111
|
-
|
102
|
+
class DatabaseTask < CMDx::Task
|
112
103
|
def call
|
113
|
-
|
114
|
-
undefined_method_call
|
104
|
+
raise ActiveRecord::ConnectionNotEstablished, "Database unavailable"
|
115
105
|
end
|
116
|
-
|
117
106
|
end
|
118
107
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
108
|
+
result = DatabaseTask.call
|
109
|
+
|
110
|
+
if result.failed? && result.metadata[:original_exception]
|
111
|
+
case result.metadata[:original_exception]
|
112
|
+
when ActiveRecord::ConnectionNotEstablished
|
113
|
+
retry_with_fallback_database
|
114
|
+
when Net::TimeoutError
|
115
|
+
retry_with_increased_timeout
|
116
|
+
when StandardError
|
117
|
+
log_and_alert_administrators
|
118
|
+
end
|
124
119
|
end
|
125
120
|
```
|
126
121
|
|
127
|
-
|
122
|
+
## Bang Call Behavior
|
123
|
+
|
124
|
+
> [!WARNING]
|
125
|
+
> `call!` propagates exceptions immediately, bypassing result object creation. Only use when you need direct exception handling or integration with exception-based error handling systems.
|
126
|
+
|
127
|
+
### Fault vs Exception Handling
|
128
|
+
|
129
|
+
CMDx faults receive special treatment based on `task_halt` configuration:
|
128
130
|
|
129
131
|
```ruby
|
130
|
-
class
|
132
|
+
class ProcessOrderTask < CMDx::Task
|
133
|
+
cmd_settings!(task_halt: [CMDx::Result::FAILED])
|
131
134
|
|
132
135
|
def call
|
133
|
-
if context.
|
134
|
-
fail!(reason: "
|
136
|
+
if context.payment_invalid
|
137
|
+
fail!(reason: "Invalid payment method") # CMDx fault
|
135
138
|
else
|
136
|
-
raise StandardError, "
|
139
|
+
raise StandardError, "System error" # Regular exception
|
137
140
|
end
|
138
141
|
end
|
139
|
-
|
140
142
|
end
|
141
143
|
|
142
|
-
# Fault behavior (
|
144
|
+
# Fault behavior (converted to exception due to task_halt)
|
143
145
|
begin
|
144
|
-
|
146
|
+
ProcessOrderTask.call!(payment_invalid: true)
|
145
147
|
rescue CMDx::Failed => e
|
146
|
-
puts "
|
148
|
+
puts "Controlled fault: #{e.message}"
|
147
149
|
end
|
148
150
|
|
149
|
-
# Exception behavior (
|
151
|
+
# Exception behavior (propagates normally)
|
150
152
|
begin
|
151
|
-
|
153
|
+
ProcessOrderTask.call!(payment_invalid: false)
|
152
154
|
rescue StandardError => e
|
153
|
-
puts "
|
155
|
+
puts "System exception: #{e.message}"
|
154
156
|
end
|
155
157
|
```
|
156
158
|
|
@@ -158,57 +160,72 @@ end
|
|
158
160
|
|
159
161
|
### Protected Exceptions
|
160
162
|
|
161
|
-
|
162
|
-
|
163
|
+
> [!IMPORTANT]
|
164
|
+
> CMDx framework exceptions always propagate regardless of call method, ensuring framework integrity and proper error reporting.
|
165
|
+
|
166
|
+
Certain exceptions are never converted to failed results:
|
163
167
|
|
164
168
|
```ruby
|
165
|
-
class
|
169
|
+
class InvalidTask < CMDx::Task
|
166
170
|
# Intentionally not implementing call method
|
167
171
|
end
|
168
172
|
|
169
|
-
#
|
170
|
-
begin
|
171
|
-
ProcessUndefinedOrderTask.call
|
172
|
-
rescue CMDx::UndefinedCallError => e
|
173
|
-
puts "This exception is never converted to a failed result"
|
174
|
-
end
|
175
|
-
|
173
|
+
# Framework exceptions always propagate
|
176
174
|
begin
|
177
|
-
|
175
|
+
InvalidTask.call # Even non-bang call propagates framework exceptions
|
178
176
|
rescue CMDx::UndefinedCallError => e
|
179
|
-
puts "
|
177
|
+
puts "Framework exception: #{e.message}"
|
180
178
|
end
|
181
179
|
```
|
182
180
|
|
183
|
-
###
|
181
|
+
### Exception Hierarchy
|
184
182
|
|
185
|
-
|
183
|
+
| Exception Type | `call` Behavior | `call!` Behavior |
|
184
|
+
|----------------|-----------------|------------------|
|
185
|
+
| **CMDx Framework** | Propagates | Propagates |
|
186
|
+
| **CMDx Faults** | Converts to result | Respects `task_halt` config |
|
187
|
+
| **Standard Exceptions** | Converts to result | Propagates |
|
188
|
+
| **Custom Exceptions** | Converts to result | Propagates |
|
186
189
|
|
187
|
-
|
188
|
-
class ProcessOrderWithHaltTask < CMDx::Task
|
189
|
-
# Configure to halt on failures
|
190
|
-
task_settings!(task_halt: [CMDx::Result::FAILED])
|
190
|
+
## Error Handling Patterns
|
191
191
|
|
192
|
+
### Graceful Degradation
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
class ProcessUserDataTask < CMDx::Task
|
192
196
|
def call
|
193
|
-
|
197
|
+
user_data = fetch_user_data
|
198
|
+
process_data(user_data)
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
|
203
|
+
def fetch_user_data
|
204
|
+
# May raise various exceptions
|
205
|
+
external_api.get_user_data(context.user_id)
|
194
206
|
end
|
195
207
|
end
|
196
208
|
|
197
|
-
#
|
198
|
-
result =
|
199
|
-
result.failed? #=> true
|
209
|
+
# Handle with graceful degradation
|
210
|
+
result = ProcessUserDataTask.call(user_id: 12345)
|
200
211
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
212
|
+
if result.failed?
|
213
|
+
case result.metadata[:original_exception]
|
214
|
+
when Net::TimeoutError
|
215
|
+
# Retry with cached data
|
216
|
+
fallback_processor.process_cached_data(user_id)
|
217
|
+
when JSON::ParserError
|
218
|
+
# Handle malformed response
|
219
|
+
error_reporter.log_api_format_error
|
220
|
+
else
|
221
|
+
# Generic error handling
|
222
|
+
notify_administrators(result.metadata[:reason])
|
223
|
+
end
|
206
224
|
end
|
207
225
|
```
|
208
226
|
|
209
|
-
> [!
|
210
|
-
>
|
211
|
-
> exceptions manually. This maintains debugging capabilities and error traceability.
|
227
|
+
> [!TIP]
|
228
|
+
> Use `call` for workflow processing where you need guaranteed result objects, and `call!` for direct integration with existing exception-based error handling patterns.
|
212
229
|
|
213
230
|
---
|
214
231
|
|