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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +13 -12
  4. data/.cursor/prompts/yardoc.md +11 -6
  5. data/CHANGELOG.md +13 -2
  6. data/README.md +1 -0
  7. data/docs/ai_prompts.md +269 -195
  8. data/docs/basics/call.md +124 -58
  9. data/docs/basics/chain.md +190 -160
  10. data/docs/basics/context.md +242 -154
  11. data/docs/basics/setup.md +302 -32
  12. data/docs/callbacks.md +390 -94
  13. data/docs/configuration.md +181 -65
  14. data/docs/deprecation.md +245 -0
  15. data/docs/getting_started.md +161 -39
  16. data/docs/internationalization.md +590 -70
  17. data/docs/interruptions/exceptions.md +135 -118
  18. data/docs/interruptions/faults.md +150 -125
  19. data/docs/interruptions/halt.md +134 -80
  20. data/docs/logging.md +181 -118
  21. data/docs/middlewares.md +150 -377
  22. data/docs/outcomes/result.md +140 -112
  23. data/docs/outcomes/states.md +134 -99
  24. data/docs/outcomes/statuses.md +204 -146
  25. data/docs/parameters/coercions.md +232 -281
  26. data/docs/parameters/defaults.md +224 -169
  27. data/docs/parameters/definitions.md +289 -141
  28. data/docs/parameters/namespacing.md +250 -161
  29. data/docs/parameters/validations.md +260 -133
  30. data/docs/testing.md +191 -197
  31. data/docs/workflows.md +143 -98
  32. data/lib/cmdx/callback.rb +23 -19
  33. data/lib/cmdx/callback_registry.rb +1 -3
  34. data/lib/cmdx/chain_inspector.rb +23 -23
  35. data/lib/cmdx/chain_serializer.rb +38 -19
  36. data/lib/cmdx/coercion.rb +20 -12
  37. data/lib/cmdx/coercion_registry.rb +51 -32
  38. data/lib/cmdx/configuration.rb +84 -31
  39. data/lib/cmdx/context.rb +32 -21
  40. data/lib/cmdx/core_ext/hash.rb +13 -13
  41. data/lib/cmdx/core_ext/module.rb +1 -1
  42. data/lib/cmdx/core_ext/object.rb +12 -12
  43. data/lib/cmdx/correlator.rb +60 -39
  44. data/lib/cmdx/errors.rb +105 -131
  45. data/lib/cmdx/fault.rb +66 -45
  46. data/lib/cmdx/immutator.rb +20 -21
  47. data/lib/cmdx/lazy_struct.rb +78 -70
  48. data/lib/cmdx/log_formatters/json.rb +1 -1
  49. data/lib/cmdx/log_formatters/key_value.rb +1 -1
  50. data/lib/cmdx/log_formatters/line.rb +1 -1
  51. data/lib/cmdx/log_formatters/logstash.rb +1 -1
  52. data/lib/cmdx/log_formatters/pretty_json.rb +1 -1
  53. data/lib/cmdx/log_formatters/pretty_key_value.rb +1 -1
  54. data/lib/cmdx/log_formatters/pretty_line.rb +1 -1
  55. data/lib/cmdx/log_formatters/raw.rb +2 -2
  56. data/lib/cmdx/logger.rb +19 -14
  57. data/lib/cmdx/logger_ansi.rb +33 -17
  58. data/lib/cmdx/logger_serializer.rb +85 -24
  59. data/lib/cmdx/middleware.rb +39 -21
  60. data/lib/cmdx/middleware_registry.rb +4 -3
  61. data/lib/cmdx/parameter.rb +151 -89
  62. data/lib/cmdx/parameter_inspector.rb +34 -21
  63. data/lib/cmdx/parameter_registry.rb +36 -30
  64. data/lib/cmdx/parameter_serializer.rb +21 -14
  65. data/lib/cmdx/result.rb +136 -135
  66. data/lib/cmdx/result_ansi.rb +31 -17
  67. data/lib/cmdx/result_inspector.rb +32 -27
  68. data/lib/cmdx/result_logger.rb +23 -14
  69. data/lib/cmdx/result_serializer.rb +65 -27
  70. data/lib/cmdx/task.rb +234 -113
  71. data/lib/cmdx/task_deprecator.rb +22 -25
  72. data/lib/cmdx/task_processor.rb +89 -88
  73. data/lib/cmdx/task_serializer.rb +27 -14
  74. data/lib/cmdx/utils/monotonic_runtime.rb +2 -4
  75. data/lib/cmdx/validator.rb +25 -16
  76. data/lib/cmdx/validator_registry.rb +53 -31
  77. data/lib/cmdx/validators/exclusion.rb +1 -1
  78. data/lib/cmdx/validators/format.rb +2 -2
  79. data/lib/cmdx/validators/inclusion.rb +2 -2
  80. data/lib/cmdx/validators/length.rb +2 -2
  81. data/lib/cmdx/validators/numeric.rb +3 -3
  82. data/lib/cmdx/validators/presence.rb +2 -2
  83. data/lib/cmdx/version.rb +1 -1
  84. data/lib/cmdx/workflow.rb +54 -33
  85. data/lib/generators/cmdx/task_generator.rb +6 -6
  86. data/lib/generators/cmdx/workflow_generator.rb +6 -6
  87. metadata +3 -1
@@ -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
- - **Installation** - Add `gem 'cmdx'` to Gemfile, run `rails g cmdx:install`
22
- - **Tasks** - Ruby classes inheriting from `CMDx::Task` with `call` method
23
- - **Execution** - Use `call` (returns result) or `call!` (raises on failure/skip)
24
- - **Parameters** - Define with `required`/`optional` with type coercion and validation
25
- - **Results** - Check `result.status` for success/skipped/failed outcomes
26
- - **Workflows** - Orchestrate multiple tasks with `CMDx::Workflow`
27
- - **Generators** - Use `rails g cmdx:task` and `rails g cmdx:workflow` for scaffolding
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
- > [!TIP]
69
- > Use **present tense verbs** for clarity: `ProcessOrderTask`, `SendEmailTask`, `ValidatePaymentTask`
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
- # Returns Result object
118
+ # Safe execution - returns Result object
77
119
  result = ProcessOrderTask.call(order_id: 123)
78
120
 
79
- # Raises exceptions on failure/skip, else returns Result object
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
- Check execution outcomes:
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
- redirect_to order_path(result.context.order), notice: "Order processed!"
149
+ order = result.context.order
150
+ redirect_to order_path(order), notice: "Order processed successfully!"
151
+
93
152
  when 'skipped'
94
- redirect_to order_path(result.context.order), notice: result.metadata[:reason]
153
+ reason = result.metadata[:reason]
154
+ redirect_to order_path(123), notice: "Skipped: #{reason}"
155
+
95
156
  when 'failed'
96
- redirect_to orders_path, alert: "Error: #{result.metadata[:reason]}"
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}s, Task ID: #{result.id}"
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
- Use `call!` for exception-based control flow:
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: "Success!"
185
+ redirect_to order_path(result.context.order), notice: "Order processed!"
186
+
111
187
  rescue CMDx::Skipped => e
112
- redirect_to orders_path, notice: e.result.metadata[:reason]
188
+ reason = e.result.metadata[:reason]
189
+ redirect_to orders_path, notice: "Skipped: #{reason}"
190
+
113
191
  rescue CMDx::Failed => e
114
- redirect_to order_path(123), alert: e.result.metadata[:reason]
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
- Combine tasks using workflows:
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 workflow for order #{order_id}"
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", context: context.to_h)
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
- result = ProcessOrderWorkflow.call(order_id: 123)
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 with proper structure:
258
+ Generate properly structured tasks and workflows:
153
259
 
154
- ```bash
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 task workflow
160
- rails generate cmdx:workflow OrderDeliveryWorkflow
161
- # Creates: app/cmds/order_delivery_workflow.rb
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)