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/getting_started.md
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
# Getting Started
|
2
2
|
|
3
|
-
CMDx is a Ruby framework for building maintainable, observable business logic through composable
|
4
|
-
command objects. Design robust workflows with automatic parameter validation, structured error
|
5
|
-
handling, comprehensive logging, and intelligent execution flow control that scales from simple
|
6
|
-
tasks to complex multi-step processes.
|
3
|
+
CMDx is a Ruby framework for building maintainable, observable business logic through composable command objects. Design robust workflows with automatic parameter validation, structured error handling, comprehensive logging, and intelligent execution flow control that scales from simple tasks to complex multi-step processes.
|
7
4
|
|
8
5
|
## Table of Contents
|
9
6
|
|
@@ -18,13 +15,33 @@ tasks to complex multi-step processes.
|
|
18
15
|
|
19
16
|
## TLDR
|
20
17
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
18
|
+
```ruby
|
19
|
+
# Installation
|
20
|
+
gem 'cmdx' # Add to Gemfile
|
21
|
+
rails g cmdx:install # Generate config
|
22
|
+
|
23
|
+
# Basic task
|
24
|
+
class ProcessOrderTask < CMDx::Task
|
25
|
+
required :order_id, type: :integer
|
26
|
+
optional :send_email, type: :boolean, default: true
|
27
|
+
|
28
|
+
def call
|
29
|
+
context.order = Order.find(order_id)
|
30
|
+
fail!("Order canceled") if context.order.canceled?
|
31
|
+
skip!("Already processed") if context.order.completed?
|
32
|
+
|
33
|
+
context.order.update!(status: 'completed')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Execution
|
38
|
+
result = ProcessOrderTask.call(order_id: 123) # Returns Result
|
39
|
+
result = ProcessOrderTask.call!(order_id: 123) # Raises on failure
|
40
|
+
|
41
|
+
# Check outcomes
|
42
|
+
result.success? && result.context.order # Access data
|
43
|
+
result.failed? && result.metadata[:reason] # Error details
|
44
|
+
```
|
28
45
|
|
29
46
|
## Installation
|
30
47
|
|
@@ -41,10 +58,13 @@ rails generate cmdx:install
|
|
41
58
|
```
|
42
59
|
|
43
60
|
> [!NOTE]
|
44
|
-
> This creates `config/initializers/cmdx.rb` with default settings.
|
61
|
+
> This creates `config/initializers/cmdx.rb` with default settings for logging, error handling, and middleware configuration.
|
45
62
|
|
46
63
|
## Quick Setup
|
47
64
|
|
65
|
+
> [!TIP]
|
66
|
+
> Use **present tense verbs** for task names: `ProcessOrderTask`, `SendEmailTask`, `ValidatePaymentTask`
|
67
|
+
|
48
68
|
```ruby
|
49
69
|
class ProcessOrderTask < CMDx::Task
|
50
70
|
required :order_id, type: :integer
|
@@ -65,106 +85,208 @@ class ProcessOrderTask < CMDx::Task
|
|
65
85
|
end
|
66
86
|
```
|
67
87
|
|
68
|
-
|
69
|
-
|
88
|
+
### Parameter Definition
|
89
|
+
|
90
|
+
Parameters provide automatic type coercion and validation:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
class CreateUserTask < CMDx::Task
|
94
|
+
required :email, type: :string
|
95
|
+
required :age, type: :integer
|
96
|
+
required :active, type: :boolean, default: true
|
97
|
+
|
98
|
+
optional :metadata, type: :hash, default: {}
|
99
|
+
optional :tags, type: :array, default: []
|
100
|
+
|
101
|
+
def call
|
102
|
+
context.user = User.create!(
|
103
|
+
email: email,
|
104
|
+
age: age,
|
105
|
+
active: active,
|
106
|
+
metadata: metadata,
|
107
|
+
tags: tags
|
108
|
+
)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
```
|
70
112
|
|
71
113
|
## Execution
|
72
114
|
|
73
|
-
Execute tasks using class methods:
|
115
|
+
Execute tasks using class methods that return result objects or raise exceptions:
|
74
116
|
|
75
117
|
```ruby
|
76
|
-
#
|
118
|
+
# Safe execution - returns Result object
|
77
119
|
result = ProcessOrderTask.call(order_id: 123)
|
78
120
|
|
79
|
-
#
|
121
|
+
# Exception-based execution - raises on failure/skip
|
80
122
|
result = ProcessOrderTask.call!(order_id: 123, send_email: false)
|
81
123
|
```
|
82
124
|
|
125
|
+
> [!IMPORTANT]
|
126
|
+
> Use `call` for conditional logic based on results, and `call!` for exception-based control flow where failures should halt execution.
|
127
|
+
|
128
|
+
### Input Coercion
|
129
|
+
|
130
|
+
Parameters automatically coerce string inputs to specified types:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
# String inputs automatically converted
|
134
|
+
ProcessOrderTask.call(
|
135
|
+
order_id: "123", # → 123 (Integer)
|
136
|
+
send_email: "false" # → false (Boolean)
|
137
|
+
)
|
138
|
+
```
|
139
|
+
|
83
140
|
## Result Handling
|
84
141
|
|
85
|
-
|
142
|
+
Results provide comprehensive execution information including status, context data, and metadata:
|
86
143
|
|
87
144
|
```ruby
|
88
145
|
result = ProcessOrderTask.call(order_id: 123)
|
89
146
|
|
90
147
|
case result.status
|
91
148
|
when 'success'
|
92
|
-
|
149
|
+
order = result.context.order
|
150
|
+
redirect_to order_path(order), notice: "Order processed successfully!"
|
151
|
+
|
93
152
|
when 'skipped'
|
94
|
-
|
153
|
+
reason = result.metadata[:reason]
|
154
|
+
redirect_to order_path(123), notice: "Skipped: #{reason}"
|
155
|
+
|
95
156
|
when 'failed'
|
96
|
-
|
157
|
+
error_details = result.metadata[:reason]
|
158
|
+
redirect_to orders_path, alert: "Processing failed: #{error_details}"
|
97
159
|
end
|
98
160
|
|
99
161
|
# Access execution metadata
|
100
|
-
puts "Runtime: #{result.runtime}
|
162
|
+
puts "Runtime: #{result.runtime}ms"
|
163
|
+
puts "Task ID: #{result.id}"
|
164
|
+
puts "Executed at: #{result.executed_at}"
|
101
165
|
```
|
102
166
|
|
167
|
+
### Result Properties
|
168
|
+
|
169
|
+
| Property | Description | Example |
|
170
|
+
|----------|-------------|---------|
|
171
|
+
| `status` | Execution outcome | `'success'`, `'failed'`, `'skipped'` |
|
172
|
+
| `context` | Shared data object | `result.context.order` |
|
173
|
+
| `metadata` | Additional details | `result.metadata[:reason]` |
|
174
|
+
| `runtime` | Execution time (ms) | `result.runtime` |
|
175
|
+
| `id` | Unique task execution ID | `result.id` |
|
176
|
+
|
103
177
|
## Exception Handling
|
104
178
|
|
105
|
-
|
179
|
+
> [!WARNING]
|
180
|
+
> `call!` raises exceptions for failed or skipped tasks. Use this pattern when failures should halt program execution.
|
106
181
|
|
107
182
|
```ruby
|
108
183
|
begin
|
109
184
|
result = ProcessOrderTask.call!(order_id: 123)
|
110
|
-
redirect_to order_path(result.context.order), notice: "
|
185
|
+
redirect_to order_path(result.context.order), notice: "Order processed!"
|
186
|
+
|
111
187
|
rescue CMDx::Skipped => e
|
112
|
-
|
188
|
+
reason = e.result.metadata[:reason]
|
189
|
+
redirect_to orders_path, notice: "Skipped: #{reason}"
|
190
|
+
|
113
191
|
rescue CMDx::Failed => e
|
114
|
-
|
192
|
+
error_details = e.result.metadata[:reason]
|
193
|
+
redirect_to order_path(123), alert: "Failed: #{error_details}"
|
194
|
+
|
195
|
+
rescue ActiveRecord::RecordNotFound
|
196
|
+
redirect_to orders_path, alert: "Order not found"
|
115
197
|
end
|
116
198
|
```
|
117
199
|
|
200
|
+
### Exception Types
|
201
|
+
|
202
|
+
- **`CMDx::Skipped`** - Task was skipped intentionally
|
203
|
+
- **`CMDx::Failed`** - Task failed due to business logic or validation errors
|
204
|
+
- **Standard exceptions** - Ruby/Rails exceptions (e.g., `ActiveRecord::RecordNotFound`)
|
205
|
+
|
118
206
|
## Building Workflows
|
119
207
|
|
120
|
-
|
208
|
+
> [!TIP]
|
209
|
+
> Workflows orchestrate multiple tasks with automatic context sharing, error handling, and execution flow control.
|
121
210
|
|
122
211
|
```ruby
|
123
212
|
class OrderProcessingWorkflow < CMDx::Workflow
|
124
213
|
required :order_id, type: :integer
|
214
|
+
optional :priority, type: :string, default: 'standard'
|
125
215
|
|
126
216
|
before_execution :log_workflow_start
|
127
217
|
on_failed :notify_support
|
218
|
+
on_skipped :log_skip_reason
|
128
219
|
|
129
220
|
process ValidateOrderTask
|
130
221
|
process ChargePaymentTask
|
131
222
|
process UpdateInventoryTask
|
132
223
|
process SendConfirmationTask, if: proc { context.payment_successful? }
|
133
|
-
|
134
|
-
# NO call method
|
224
|
+
process ExpressShippingTask, if: proc { priority == 'express' }
|
135
225
|
|
136
226
|
private
|
137
227
|
|
138
228
|
def log_workflow_start
|
139
|
-
Rails.logger.info "Starting order
|
229
|
+
Rails.logger.info "Starting order processing for order #{order_id}"
|
140
230
|
end
|
141
231
|
|
142
232
|
def notify_support
|
143
|
-
SupportNotifier.alert("Order workflow failed",
|
233
|
+
SupportNotifier.alert("Order workflow failed",
|
234
|
+
order_id: order_id,
|
235
|
+
error: result.metadata[:reason]
|
236
|
+
)
|
237
|
+
end
|
238
|
+
|
239
|
+
def log_skip_reason
|
240
|
+
Rails.logger.warn "Workflow skipped: #{result.metadata[:reason]}"
|
144
241
|
end
|
145
242
|
end
|
146
243
|
|
147
|
-
|
244
|
+
# Execute workflow
|
245
|
+
result = OrderProcessingWorkflow.call(order_id: 123, priority: 'express')
|
148
246
|
```
|
149
247
|
|
248
|
+
### Workflow Features
|
249
|
+
|
250
|
+
- **Automatic context sharing** - Tasks access shared `context` object
|
251
|
+
- **Conditional execution** - Use `:if` conditions for optional tasks
|
252
|
+
- **Lifecycle callbacks** - Hook into workflow execution phases
|
253
|
+
- **Error propagation** - Failed tasks halt workflow execution
|
254
|
+
- **Skip handling** - Graceful handling of skipped tasks
|
255
|
+
|
150
256
|
## Code Generation
|
151
257
|
|
152
|
-
Generate tasks and workflows
|
258
|
+
Generate properly structured tasks and workflows:
|
153
259
|
|
154
|
-
```
|
260
|
+
```ruby
|
155
261
|
# Generate individual task
|
156
262
|
rails generate cmdx:task ProcessOrder
|
157
263
|
# Creates: app/cmds/process_order_task.rb
|
158
264
|
|
159
|
-
# Generate
|
160
|
-
rails generate cmdx:workflow
|
161
|
-
# Creates: app/cmds/
|
265
|
+
# Generate workflow
|
266
|
+
rails generate cmdx:workflow OrderProcessing
|
267
|
+
# Creates: app/cmds/order_processing_workflow.rb
|
268
|
+
|
269
|
+
# Generate with parameters
|
270
|
+
rails generate cmdx:task CreateUser email:string age:integer active:boolean
|
162
271
|
```
|
163
272
|
|
164
273
|
> [!NOTE]
|
165
|
-
> Generators automatically handle naming conventions and inherit from `ApplicationTask`/`ApplicationWorkflow` when available.
|
274
|
+
> Generators automatically handle naming conventions and inherit from `ApplicationTask`/`ApplicationWorkflow` when available. Generated files include parameter definitions and basic structure.
|
275
|
+
|
276
|
+
### Generated Task Structure
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
# app/cmds/process_order_task.rb
|
280
|
+
class ProcessOrderTask < ApplicationTask
|
281
|
+
required :order_id, type: :integer
|
282
|
+
|
283
|
+
def call
|
284
|
+
# Task implementation
|
285
|
+
end
|
286
|
+
end
|
287
|
+
```
|
166
288
|
|
167
289
|
---
|
168
290
|
|
169
|
-
- **Prev:** [Tips and Tricks](tips_and_tricks.md)
|
170
291
|
- **Next:** [Configuration](configuration.md)
|
292
|
+
- **See also:** [Parameters - Coercions](parameters/coercions.md) | [Workflows](workflows.md)
|