cmdx 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.cursor/prompts/docs.md +9 -0
- data/.cursor/prompts/rspec.md +13 -12
- data/.cursor/prompts/yardoc.md +11 -6
- data/CHANGELOG.md +13 -2
- data/README.md +1 -0
- data/docs/ai_prompts.md +269 -195
- data/docs/basics/call.md +124 -58
- 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 +390 -94
- data/docs/configuration.md +181 -65
- 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 +150 -125
- data/docs/interruptions/halt.md +134 -80
- data/docs/logging.md +181 -118
- data/docs/middlewares.md +150 -377
- 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 +232 -281
- 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 +260 -133
- data/docs/testing.md +191 -197
- data/docs/workflows.md +143 -98
- data/lib/cmdx/callback.rb +23 -19
- data/lib/cmdx/callback_registry.rb +1 -3
- data/lib/cmdx/chain_inspector.rb +23 -23
- data/lib/cmdx/chain_serializer.rb +38 -19
- data/lib/cmdx/coercion.rb +20 -12
- data/lib/cmdx/coercion_registry.rb +51 -32
- data/lib/cmdx/configuration.rb +84 -31
- data/lib/cmdx/context.rb +32 -21
- data/lib/cmdx/core_ext/hash.rb +13 -13
- data/lib/cmdx/core_ext/module.rb +1 -1
- data/lib/cmdx/core_ext/object.rb +12 -12
- data/lib/cmdx/correlator.rb +60 -39
- data/lib/cmdx/errors.rb +105 -131
- data/lib/cmdx/fault.rb +66 -45
- data/lib/cmdx/immutator.rb +20 -21
- data/lib/cmdx/lazy_struct.rb +78 -70
- data/lib/cmdx/log_formatters/json.rb +1 -1
- data/lib/cmdx/log_formatters/key_value.rb +1 -1
- data/lib/cmdx/log_formatters/line.rb +1 -1
- data/lib/cmdx/log_formatters/logstash.rb +1 -1
- data/lib/cmdx/log_formatters/pretty_json.rb +1 -1
- data/lib/cmdx/log_formatters/pretty_key_value.rb +1 -1
- data/lib/cmdx/log_formatters/pretty_line.rb +1 -1
- data/lib/cmdx/log_formatters/raw.rb +2 -2
- data/lib/cmdx/logger.rb +19 -14
- data/lib/cmdx/logger_ansi.rb +33 -17
- data/lib/cmdx/logger_serializer.rb +85 -24
- data/lib/cmdx/middleware.rb +39 -21
- data/lib/cmdx/middleware_registry.rb +4 -3
- data/lib/cmdx/parameter.rb +151 -89
- data/lib/cmdx/parameter_inspector.rb +34 -21
- data/lib/cmdx/parameter_registry.rb +36 -30
- data/lib/cmdx/parameter_serializer.rb +21 -14
- data/lib/cmdx/result.rb +136 -135
- data/lib/cmdx/result_ansi.rb +31 -17
- data/lib/cmdx/result_inspector.rb +32 -27
- data/lib/cmdx/result_logger.rb +23 -14
- data/lib/cmdx/result_serializer.rb +65 -27
- data/lib/cmdx/task.rb +234 -113
- data/lib/cmdx/task_deprecator.rb +22 -25
- data/lib/cmdx/task_processor.rb +89 -88
- data/lib/cmdx/task_serializer.rb +27 -14
- data/lib/cmdx/utils/monotonic_runtime.rb +2 -4
- data/lib/cmdx/validator.rb +25 -16
- data/lib/cmdx/validator_registry.rb +53 -31
- data/lib/cmdx/validators/exclusion.rb +1 -1
- data/lib/cmdx/validators/format.rb +2 -2
- data/lib/cmdx/validators/inclusion.rb +2 -2
- data/lib/cmdx/validators/length.rb +2 -2
- data/lib/cmdx/validators/numeric.rb +3 -3
- data/lib/cmdx/validators/presence.rb +2 -2
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx/workflow.rb +54 -33
- data/lib/generators/cmdx/task_generator.rb +6 -6
- data/lib/generators/cmdx/workflow_generator.rb +6 -6
- metadata +3 -1
data/docs/workflows.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Workflow
|
2
2
|
|
3
|
-
|
3
|
+
CMDx::Workflow orchestrates sequential execution of multiple tasks in a linear pipeline. Workflows provide a declarative DSL for composing complex business workflows from individual task components, with support for conditional execution, context propagation, and configurable halt behavior.
|
4
4
|
|
5
5
|
Workflows inherit from Task, gaining all task capabilities including callbacks, parameter validation, result tracking, and configuration. The key difference is that workflows coordinate other tasks rather than implementing business logic directly.
|
6
6
|
|
@@ -17,55 +17,68 @@ Workflows inherit from Task, gaining all task capabilities including callbacks,
|
|
17
17
|
- [Group-Level Configuration](#group-level-configuration)
|
18
18
|
- [Available Result Statuses](#available-result-statuses)
|
19
19
|
- [Process Method Options](#process-method-options)
|
20
|
-
|
20
|
+
- [Error Handling](#error-handling)
|
21
21
|
- [Nested Workflows](#nested-workflows)
|
22
22
|
- [Task Settings Integration](#task-settings-integration)
|
23
23
|
- [Generator](#generator)
|
24
24
|
|
25
25
|
## TLDR
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
```ruby
|
28
|
+
# Basic workflow - sequential task execution
|
29
|
+
class OrderWorkflow < CMDx::Workflow
|
30
|
+
process ValidateOrderTask # Step 1
|
31
|
+
process CalculateTaxTask # Step 2
|
32
|
+
process ChargePaymentTask # Step 3
|
33
|
+
end
|
34
|
+
|
35
|
+
# Conditional execution
|
36
|
+
process SendEmailTask, if: proc { context.notify_user? }
|
37
|
+
process SkipableTask, unless: :should_skip?
|
38
|
+
|
39
|
+
# Halt behavior control
|
40
|
+
process CriticalTask, workflow_halt: [CMDx::Result::FAILED, CMDx::Result::SKIPPED]
|
41
|
+
process OptionalTask, workflow_halt: [] # Never halt
|
42
|
+
|
43
|
+
# Context flows through all tasks automatically
|
44
|
+
result = OrderWorkflow.call(order: order)
|
45
|
+
result.context.tax_amount # Set by CalculateTaxTask
|
46
|
+
result.context.payment_id # Set by ChargePaymentTask
|
47
|
+
```
|
33
48
|
|
34
49
|
## Basic Usage
|
35
50
|
|
36
51
|
> [!WARNING]
|
37
|
-
> Do **NOT** define a `call` method in workflow classes. The workflow
|
52
|
+
> Do **NOT** define a `call` method in workflow classes. The workflow automatically provides execution logic.
|
38
53
|
|
39
54
|
```ruby
|
40
55
|
class OrderProcessingWorkflow < CMDx::Workflow
|
41
|
-
# Sequential task execution
|
42
56
|
process ValidateOrderTask
|
43
57
|
process CalculateTaxTask
|
44
58
|
process ChargePaymentTask
|
45
59
|
process FulfillOrderTask
|
46
60
|
end
|
47
61
|
|
48
|
-
# Execute
|
49
|
-
result =
|
62
|
+
# Execute workflow
|
63
|
+
result = OrderProcessingWorkflow.call(order: order, user: current_user)
|
50
64
|
|
51
65
|
if result.success?
|
52
|
-
redirect_to
|
66
|
+
redirect_to order_path(result.context.order)
|
53
67
|
elsif result.failed?
|
54
|
-
|
55
|
-
redirect_to cart_path
|
68
|
+
handle_error(result.metadata[:reason])
|
56
69
|
end
|
57
70
|
```
|
58
71
|
|
59
72
|
## Task Declaration
|
60
73
|
|
61
|
-
Tasks are declared using the `process` method
|
74
|
+
Tasks are declared using the `process` method in execution order:
|
62
75
|
|
63
76
|
```ruby
|
64
|
-
class
|
65
|
-
# Single task
|
77
|
+
class NotificationWorkflow < CMDx::Workflow
|
78
|
+
# Single task
|
66
79
|
process PrepareNotificationTask
|
67
80
|
|
68
|
-
# Multiple tasks
|
81
|
+
# Multiple tasks (grouped with same options)
|
69
82
|
process SendEmailTask, SendSmsTask, SendPushTask
|
70
83
|
|
71
84
|
# Tasks with conditions
|
@@ -81,34 +94,32 @@ end
|
|
81
94
|
```
|
82
95
|
|
83
96
|
> [!IMPORTANT]
|
84
|
-
>
|
97
|
+
> Tasks execute in declaration order (FIFO). Use grouping to apply the same options to multiple tasks.
|
85
98
|
|
86
99
|
## Context Propagation
|
87
100
|
|
88
|
-
The context object
|
101
|
+
The context object flows through all tasks, creating a data pipeline:
|
89
102
|
|
90
103
|
```ruby
|
91
|
-
class
|
92
|
-
process ValidateOrderTask
|
93
|
-
process CalculateTaxTask
|
94
|
-
process ChargePaymentTask
|
95
|
-
process FulfillOrderTask # Uses context.payment_id, sets context.tracking_number
|
104
|
+
class PaymentWorkflow < CMDx::Workflow
|
105
|
+
process ValidateOrderTask # Sets context.validation_errors
|
106
|
+
process CalculateTaxTask # Uses context.order, sets context.tax_amount
|
107
|
+
process ChargePaymentTask # Uses context.tax_amount, sets context.payment_id
|
96
108
|
end
|
97
109
|
|
98
|
-
result =
|
99
|
-
#
|
100
|
-
result.context.
|
101
|
-
result.context.tax_amount
|
102
|
-
result.context.payment_id
|
103
|
-
result.context.tracking_number # From FulfillOrderTask
|
110
|
+
result = PaymentWorkflow.call(order: order)
|
111
|
+
# Context contains cumulative data from all executed tasks
|
112
|
+
result.context.validation_errors # From ValidateOrderTask
|
113
|
+
result.context.tax_amount # From CalculateTaxTask
|
114
|
+
result.context.payment_id # From ChargePaymentTask
|
104
115
|
```
|
105
116
|
|
106
117
|
## Conditional Execution
|
107
118
|
|
108
|
-
Tasks can
|
119
|
+
Tasks can execute conditionally using `:if` and `:unless` options:
|
109
120
|
|
110
121
|
```ruby
|
111
|
-
class
|
122
|
+
class UserWorkflow < CMDx::Workflow
|
112
123
|
process ValidateUserTask
|
113
124
|
|
114
125
|
# Proc condition
|
@@ -124,7 +135,7 @@ class UserProcessingWorkflow < CMDx::Workflow
|
|
124
135
|
process SendSpecialOfferTask, if: proc {
|
125
136
|
context.user.active? &&
|
126
137
|
context.feature_enabled?(:offers) &&
|
127
|
-
|
138
|
+
business_hours?
|
128
139
|
}
|
129
140
|
|
130
141
|
private
|
@@ -132,31 +143,38 @@ class UserProcessingWorkflow < CMDx::Workflow
|
|
132
143
|
def debug_enabled?
|
133
144
|
Rails.env.development?
|
134
145
|
end
|
146
|
+
|
147
|
+
def business_hours?
|
148
|
+
Time.now.hour.between?(9, 17)
|
149
|
+
end
|
135
150
|
end
|
136
151
|
```
|
137
152
|
|
153
|
+
> [!NOTE]
|
154
|
+
> Conditions are evaluated in the workflow instance context. Skipped tasks return `SKIPPED` status but don't halt execution by default.
|
155
|
+
|
138
156
|
## Halt Behavior
|
139
157
|
|
140
|
-
Workflows control execution flow
|
158
|
+
Workflows control execution flow by halting on specific result statuses.
|
141
159
|
|
142
160
|
### Default Behavior
|
143
161
|
|
144
|
-
By default, workflows halt on `FAILED` status but continue on `SKIPPED
|
162
|
+
By default, workflows halt on `FAILED` status but continue on `SKIPPED`:
|
145
163
|
|
146
164
|
```ruby
|
147
|
-
class
|
148
|
-
process LoadDataTask
|
149
|
-
process ValidateDataTask
|
150
|
-
process SaveDataTask
|
165
|
+
class DataWorkflow < CMDx::Workflow
|
166
|
+
process LoadDataTask # If fails → workflow stops
|
167
|
+
process ValidateDataTask # If skipped → workflow continues
|
168
|
+
process SaveDataTask # Only runs if no failures occurred
|
151
169
|
end
|
152
170
|
```
|
153
171
|
|
154
172
|
### Class-Level Configuration
|
155
173
|
|
156
|
-
Configure halt behavior for the entire workflow
|
174
|
+
Configure halt behavior for the entire workflow:
|
157
175
|
|
158
176
|
```ruby
|
159
|
-
class
|
177
|
+
class CriticalWorkflow < CMDx::Workflow
|
160
178
|
# Halt on both failed and skipped results
|
161
179
|
cmd_settings!(workflow_halt: [CMDx::Result::FAILED, CMDx::Result::SKIPPED])
|
162
180
|
|
@@ -164,7 +182,7 @@ class CriticalDataProcessingWorkflow < CMDx::Workflow
|
|
164
182
|
process ValidateCriticalDataTask
|
165
183
|
end
|
166
184
|
|
167
|
-
class
|
185
|
+
class OptionalWorkflow < CMDx::Workflow
|
168
186
|
# Never halt, always continue
|
169
187
|
cmd_settings!(workflow_halt: [])
|
170
188
|
|
@@ -176,73 +194,102 @@ end
|
|
176
194
|
|
177
195
|
### Group-Level Configuration
|
178
196
|
|
179
|
-
Different groups can have different halt behavior:
|
197
|
+
Different task groups can have different halt behavior:
|
180
198
|
|
181
199
|
```ruby
|
182
|
-
class
|
200
|
+
class AccountWorkflow < CMDx::Workflow
|
183
201
|
# Critical tasks - halt on any failure or skip
|
184
202
|
process CreateUserTask, ValidateUserTask,
|
185
203
|
workflow_halt: [CMDx::Result::FAILED, CMDx::Result::SKIPPED]
|
186
204
|
|
187
|
-
# Optional tasks - never halt
|
188
|
-
process SendWelcomeEmailTask, CreateProfileTask,
|
205
|
+
# Optional tasks - never halt
|
206
|
+
process SendWelcomeEmailTask, CreateProfileTask,
|
207
|
+
workflow_halt: []
|
189
208
|
|
190
|
-
#
|
209
|
+
# Default behavior for remaining tasks
|
191
210
|
process NotifyAdminTask, LogUserCreationTask
|
192
211
|
end
|
193
212
|
```
|
194
213
|
|
195
214
|
### Available Result Statuses
|
196
215
|
|
197
|
-
|
216
|
+
Use these statuses in `workflow_halt` arrays:
|
198
217
|
|
199
|
-
|
200
|
-
|
201
|
-
|
218
|
+
| Status | Description |
|
219
|
+
|--------|-------------|
|
220
|
+
| `CMDx::Result::SUCCESS` | Task completed successfully |
|
221
|
+
| `CMDx::Result::SKIPPED` | Task was skipped intentionally |
|
222
|
+
| `CMDx::Result::FAILED` | Task failed due to error or validation |
|
202
223
|
|
203
224
|
## Process Method Options
|
204
225
|
|
205
|
-
The `process` method supports
|
226
|
+
The `process` method supports these options:
|
206
227
|
|
207
|
-
| Option
|
208
|
-
|
209
|
-
| `:if`
|
210
|
-
| `:unless`
|
211
|
-
| `:workflow_halt` |
|
228
|
+
| Option | Description | Example |
|
229
|
+
|--------|-------------|---------|
|
230
|
+
| `:if` | Execute task if condition is true | `if: proc { context.enabled? }` |
|
231
|
+
| `:unless` | Execute task if condition is false | `unless: :should_skip?` |
|
232
|
+
| `:workflow_halt` | Which statuses should halt execution | `workflow_halt: [CMDx::Result::FAILED]` |
|
212
233
|
|
213
|
-
|
234
|
+
Conditions can be procs, lambdas, symbols, or strings referencing instance methods.
|
214
235
|
|
215
|
-
|
236
|
+
## Error Handling
|
216
237
|
|
217
|
-
|
218
|
-
|
219
|
-
# Proc - executed in workflow instance context
|
220
|
-
process UpgradeAccountTask, if: proc { context.user.admin? }
|
238
|
+
> [!WARNING]
|
239
|
+
> Workflow failures provide detailed information about which task failed and why, enabling precise error handling and debugging.
|
221
240
|
|
222
|
-
|
223
|
-
|
241
|
+
```ruby
|
242
|
+
class OrderWorkflow < CMDx::Workflow
|
243
|
+
process ValidateOrderTask
|
244
|
+
process CalculateTaxTask
|
245
|
+
process ChargePaymentTask
|
246
|
+
end
|
224
247
|
|
225
|
-
|
226
|
-
|
248
|
+
result = OrderWorkflow.call(order: invalid_order)
|
249
|
+
|
250
|
+
if result.failed?
|
251
|
+
result.metadata
|
252
|
+
# {
|
253
|
+
# reason: "ValidateOrderTask failed: Order ID is required",
|
254
|
+
# failed_task: "ValidateOrderTask",
|
255
|
+
# task_index: 0,
|
256
|
+
# executed_tasks: ["ValidateOrderTask"],
|
257
|
+
# skipped_tasks: [],
|
258
|
+
# context_at_failure: { order: {...} }
|
259
|
+
# }
|
260
|
+
end
|
261
|
+
```
|
227
262
|
|
228
|
-
|
229
|
-
process OptionalTask, unless: "skip_task?"
|
263
|
+
### Common Error Scenarios
|
230
264
|
|
231
|
-
|
265
|
+
```ruby
|
266
|
+
# Task raises exception
|
267
|
+
class ProcessDataWorkflow < CMDx::Workflow
|
268
|
+
process ValidateDataTask # Raises validation error
|
269
|
+
process TransformDataTask # Never executes
|
270
|
+
end
|
232
271
|
|
233
|
-
|
234
|
-
|
235
|
-
|
272
|
+
result = ProcessDataWorkflow.call(data: nil)
|
273
|
+
result.failed? # → true
|
274
|
+
result.metadata[:reason] # → "ValidateDataTask failed: Data cannot be nil"
|
236
275
|
|
237
|
-
|
238
|
-
|
239
|
-
|
276
|
+
# Halt on skipped task
|
277
|
+
class StrictWorkflow < CMDx::Workflow
|
278
|
+
process RequiredTask, workflow_halt: [CMDx::Result::SKIPPED]
|
279
|
+
process OptionalTask, if: proc { false } # Always skipped
|
280
|
+
process FinalTask # Never executes
|
240
281
|
end
|
282
|
+
|
283
|
+
result = StrictWorkflow.call
|
284
|
+
result.failed? # → true (halted on skipped task)
|
241
285
|
```
|
242
286
|
|
287
|
+
> [!TIP]
|
288
|
+
> Use specific halt configurations to implement different failure strategies: strict validation, best-effort processing, or fault-tolerant pipelines.
|
289
|
+
|
243
290
|
## Nested Workflows
|
244
291
|
|
245
|
-
Workflows can process other workflows
|
292
|
+
Workflows can process other workflows for hierarchical composition:
|
246
293
|
|
247
294
|
```ruby
|
248
295
|
class DataPreProcessingWorkflow < CMDx::Workflow
|
@@ -255,35 +302,33 @@ class DataProcessingWorkflow < CMDx::Workflow
|
|
255
302
|
process ApplyBusinessLogicTask
|
256
303
|
end
|
257
304
|
|
258
|
-
class
|
259
|
-
process GenerateReportTask
|
260
|
-
process SendNotificationTask
|
261
|
-
end
|
262
|
-
|
263
|
-
class CompleteDataProcessingWorkflow < CMDx::Workflow
|
305
|
+
class CompleteDataWorkflow < CMDx::Workflow
|
264
306
|
process DataPreProcessingWorkflow
|
265
307
|
process DataProcessingWorkflow, if: proc { context.pre_processing_successful? }
|
266
|
-
process
|
308
|
+
process GenerateReportTask
|
267
309
|
end
|
268
310
|
```
|
269
311
|
|
312
|
+
> [!NOTE]
|
313
|
+
> Nested workflows share the same context object, enabling seamless data flow across workflow boundaries.
|
314
|
+
|
270
315
|
## Task Settings Integration
|
271
316
|
|
272
|
-
Workflows support all task
|
317
|
+
Workflows support all task capabilities including parameters, callbacks, and configuration:
|
273
318
|
|
274
319
|
```ruby
|
275
|
-
class
|
276
|
-
#
|
320
|
+
class PaymentWorkflow < CMDx::Workflow
|
321
|
+
# Parameter validation
|
322
|
+
required :order_id, type: :integer
|
323
|
+
optional :notify_user, type: :boolean, default: true
|
324
|
+
|
325
|
+
# Workflow settings
|
277
326
|
cmd_settings!(
|
278
327
|
workflow_halt: [CMDx::Result::FAILED],
|
279
328
|
log_level: :debug,
|
280
329
|
tags: [:critical, :payment]
|
281
330
|
)
|
282
331
|
|
283
|
-
# Parameter validation
|
284
|
-
required :order_id, type: :integer
|
285
|
-
optional :notify_user, type: :boolean, default: true
|
286
|
-
|
287
332
|
# Callbacks
|
288
333
|
before_execution :setup_context
|
289
334
|
after_execution :cleanup_resources
|
@@ -306,22 +351,22 @@ end
|
|
306
351
|
|
307
352
|
## Generator
|
308
353
|
|
309
|
-
Generate
|
354
|
+
Generate workflow scaffolding using the Rails generator:
|
310
355
|
|
311
356
|
```bash
|
312
357
|
rails g cmdx:workflow ProcessOrder
|
313
358
|
```
|
314
359
|
|
315
|
-
|
360
|
+
Creates `app/commands/process_order_workflow.rb`:
|
316
361
|
|
317
362
|
```ruby
|
318
|
-
class
|
319
|
-
process # TODO
|
363
|
+
class ProcessOrderWorkflow < ApplicationWorkflow
|
364
|
+
process # TODO: Add your tasks here
|
320
365
|
end
|
321
366
|
```
|
322
367
|
|
323
368
|
> [!NOTE]
|
324
|
-
> The generator creates workflow files in `app/commands
|
369
|
+
> The generator creates workflow files in `app/commands/`, inherits from `ApplicationWorkflow` if available (otherwise `CMDx::Workflow`), and handles proper naming conventions.
|
325
370
|
|
326
371
|
---
|
327
372
|
|
data/lib/cmdx/callback.rb
CHANGED
@@ -1,47 +1,51 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
# Base class for implementing callback functionality in task
|
4
|
+
# Base class for implementing callback functionality in task processing.
|
5
5
|
#
|
6
|
-
# Callbacks are executed at specific points
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# the abstract call method.
|
6
|
+
# Callbacks are executed at specific points in the task lifecycle, such as
|
7
|
+
# before execution, after success, or on failure. All callback implementations
|
8
|
+
# must inherit from this class and implement the abstract call method.
|
10
9
|
class Callback
|
11
10
|
|
12
11
|
# Executes a callback by creating a new instance and calling it.
|
13
12
|
#
|
14
|
-
# @param task [Task] the task instance
|
15
|
-
# @param type [Symbol] the callback type
|
13
|
+
# @param task [CMDx::Task] the task instance triggering the callback
|
14
|
+
# @param type [Symbol] the callback type being executed (e.g., :before_execution, :on_success, :on_failure)
|
16
15
|
#
|
17
|
-
# @return [
|
16
|
+
# @return [void]
|
18
17
|
#
|
19
18
|
# @raise [UndefinedCallError] when the callback subclass doesn't implement call
|
20
19
|
#
|
21
|
-
# @example Execute a callback
|
22
|
-
#
|
20
|
+
# @example Execute a callback for task success
|
21
|
+
# LogSuccessCallback.call(task, :on_success)
|
22
|
+
#
|
23
|
+
# @example Execute a callback before task execution
|
24
|
+
# SetupCallback.call(task, :before_execution)
|
23
25
|
def self.call(task, type)
|
24
26
|
new.call(task, type)
|
25
27
|
end
|
26
28
|
|
27
29
|
# Abstract method that must be implemented by callback subclasses.
|
28
30
|
#
|
29
|
-
# This method contains the actual callback logic to be executed
|
30
|
-
# Subclasses must override this method
|
31
|
-
# callback implementation.
|
31
|
+
# This method contains the actual callback logic to be executed at the
|
32
|
+
# specified point in the task lifecycle. Subclasses must override this method
|
33
|
+
# to provide their specific callback implementation.
|
32
34
|
#
|
33
|
-
# @param
|
34
|
-
# @param
|
35
|
+
# @param task [CMDx::Task] the task instance triggering the callback
|
36
|
+
# @param type [Symbol] the callback type being executed
|
35
37
|
#
|
36
|
-
# @return [
|
38
|
+
# @return [void]
|
37
39
|
#
|
38
40
|
# @raise [UndefinedCallError] always raised in the base class
|
39
41
|
#
|
40
42
|
# @example Implement in a subclass
|
41
|
-
#
|
42
|
-
#
|
43
|
+
# class NotificationCallback < CMDx::Callback
|
44
|
+
# def call(task, type)
|
45
|
+
# puts "Task #{task.class.name} triggered #{type} callback"
|
46
|
+
# end
|
43
47
|
# end
|
44
|
-
def call(
|
48
|
+
def call(task, type) # rubocop:disable Lint/UnusedMethodArgument
|
45
49
|
raise UndefinedCallError, "call method not defined in #{self.class.name}"
|
46
50
|
end
|
47
51
|
|
@@ -20,8 +20,6 @@ module CMDx
|
|
20
20
|
*Result::STATES.map { |s| :"on_#{s}" }
|
21
21
|
].freeze
|
22
22
|
|
23
|
-
# The internal hash storing callback definitions.
|
24
|
-
#
|
25
23
|
# @return [Hash] hash containing callback type keys and callback definition arrays
|
26
24
|
attr_reader :registry
|
27
25
|
|
@@ -106,7 +104,7 @@ module CMDx
|
|
106
104
|
#
|
107
105
|
# @example Getting registry contents
|
108
106
|
# registry.to_h
|
109
|
-
#
|
107
|
+
# #=> { before_execution: [[:setup, {}]], on_good: [[:notify, { if: -> { true } }]] }
|
110
108
|
def to_h
|
111
109
|
registry.transform_values(&:dup)
|
112
110
|
end
|
data/lib/cmdx/chain_inspector.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
# Provides formatted inspection and display functionality for execution
|
4
|
+
# Provides formatted inspection and display functionality for chain execution results.
|
5
5
|
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
6
|
+
# ChainInspector creates human-readable string representations of execution chains,
|
7
|
+
# displaying chain metadata, individual task results, and execution summary information
|
8
|
+
# in a formatted layout. The inspector processes chain data to provide comprehensive
|
9
|
+
# debugging and monitoring output for task execution sequences.
|
9
10
|
module ChainInspector
|
10
11
|
|
11
12
|
FOOTER_KEYS = %i[
|
@@ -14,31 +15,30 @@ module CMDx
|
|
14
15
|
|
15
16
|
module_function
|
16
17
|
|
17
|
-
# Formats a chain into a human-readable inspection string.
|
18
|
+
# Formats a chain into a human-readable inspection string with headers, results, and summary.
|
18
19
|
#
|
19
|
-
# Creates a
|
20
|
-
#
|
21
|
-
#
|
20
|
+
# Creates a comprehensive string representation of the execution chain including
|
21
|
+
# a header with the chain ID, formatted individual task results, and a footer
|
22
|
+
# summary with key execution metadata. The output uses visual separators for
|
23
|
+
# clear section delineation and consistent formatting.
|
22
24
|
#
|
23
|
-
# @param chain [
|
25
|
+
# @param chain [Chain] the execution chain to format and inspect
|
24
26
|
#
|
25
|
-
# @return [String] formatted multi-line string representation of the chain
|
27
|
+
# @return [String] formatted multi-line string representation of the chain execution
|
26
28
|
#
|
27
|
-
# @
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
# chain
|
33
|
-
#
|
34
|
-
# # Output:
|
35
|
-
# # chain: abc123
|
36
|
-
# # ===================
|
29
|
+
# @example Format a simple chain
|
30
|
+
# chain = MyWorkflow.call(user_id: 123)
|
31
|
+
# output = ChainInspector.call(chain.chain)
|
32
|
+
# puts output
|
33
|
+
# # =>
|
34
|
+
# # chain: abc123-def456-789
|
35
|
+
# # ===============================
|
37
36
|
# #
|
38
|
-
# # {:state=>"complete", :status=>"success"
|
37
|
+
# # {:task=>"MyTask", :state=>"complete", :status=>"success"}
|
38
|
+
# # {:task=>"OtherTask", :state=>"complete", :status=>"success"}
|
39
39
|
# #
|
40
|
-
# #
|
41
|
-
# # state: complete | status: success | outcome:
|
40
|
+
# # ===============================
|
41
|
+
# # state: complete | status: success | outcome: good | runtime: 0.025
|
42
42
|
def call(chain)
|
43
43
|
header = "\nchain: #{chain.id}"
|
44
44
|
footer = FOOTER_KEYS.map { |key| "#{key}: #{chain.send(key)}" }.join(" | ")
|
@@ -1,33 +1,52 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
4
|
+
# Serialization module for converting chain objects to hash representation.
|
5
|
+
#
|
6
|
+
# ChainSerializer provides functionality to serialize chain objects into a
|
7
|
+
# standardized hash format that includes essential metadata about the chain
|
8
|
+
# execution including unique identification, execution state, status, outcome,
|
9
|
+
# runtime, and all contained task results. The serialized format is commonly
|
10
|
+
# used for debugging, logging, introspection, and data exchange throughout
|
11
|
+
# the task execution pipeline.
|
7
12
|
module ChainSerializer
|
8
13
|
|
9
14
|
module_function
|
10
15
|
|
11
|
-
#
|
12
|
-
# Extracts key chain attributes and serializes all contained results for complete
|
13
|
-
# execution state capture.
|
16
|
+
# Serializes a chain object into a hash representation.
|
14
17
|
#
|
15
|
-
#
|
18
|
+
# Converts a chain instance into a standardized hash format containing
|
19
|
+
# key metadata about the chain's execution context and all contained results.
|
20
|
+
# The serialization includes information delegated from the first result in
|
21
|
+
# the chain (state, status, outcome, runtime) along with the chain's unique
|
22
|
+
# identifier and complete collection of task results converted to hashes.
|
16
23
|
#
|
17
|
-
# @
|
24
|
+
# @param chain [CMDx::Chain] the chain object to serialize
|
18
25
|
#
|
19
|
-
# @
|
26
|
+
# @return [Hash] a hash containing the chain's metadata and execution information
|
27
|
+
# @option return [String] :id the unique identifier of the chain
|
28
|
+
# @option return [String] :state the execution state delegated from first result
|
29
|
+
# @option return [String] :status the execution status delegated from first result
|
30
|
+
# @option return [String] :outcome the execution outcome delegated from first result
|
31
|
+
# @option return [Float] :runtime the execution runtime in seconds delegated from first result
|
32
|
+
# @option return [Array<Hash>] :results array of serialized result hashes from all tasks in the chain
|
20
33
|
#
|
21
|
-
# @
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
# #
|
28
|
-
# #
|
29
|
-
# #
|
30
|
-
# #
|
34
|
+
# @raise [NoMethodError] if the chain doesn't respond to required methods (id, state, status, outcome, runtime, results)
|
35
|
+
#
|
36
|
+
# @example Serialize a workflow chain with multiple tasks
|
37
|
+
# workflow = DataProcessingWorkflow.call(input: "data")
|
38
|
+
# ChainSerializer.call(workflow.chain)
|
39
|
+
# #=> {
|
40
|
+
# # id: "def456",
|
41
|
+
# # state: "complete",
|
42
|
+
# # status: "success",
|
43
|
+
# # outcome: "success",
|
44
|
+
# # runtime: 0.123,
|
45
|
+
# # results: [
|
46
|
+
# # { index: 0, class: "ValidateDataTask", status: "success", ... },
|
47
|
+
# # { index: 1, class: "ProcessDataTask", status: "success", ... },
|
48
|
+
# # { index: 2, class: "SaveDataTask", status: "success", ... }
|
49
|
+
# # ]
|
31
50
|
# # }
|
32
51
|
def call(chain)
|
33
52
|
{
|