cmdx 1.8.0 → 1.9.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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/prompts/docs.md +3 -3
  4. data/.cursor/prompts/llms.md +1 -3
  5. data/.cursor/prompts/yardoc.md +1 -0
  6. data/.irbrc +14 -2
  7. data/CHANGELOG.md +64 -45
  8. data/LLM.md +159 -53
  9. data/README.md +26 -83
  10. data/docs/.DS_Store +0 -0
  11. data/docs/assets/favicon.ico +0 -0
  12. data/docs/assets/favicon.svg +1 -0
  13. data/docs/attributes/coercions.md +12 -24
  14. data/docs/attributes/defaults.md +3 -16
  15. data/docs/attributes/definitions.md +16 -30
  16. data/docs/attributes/naming.md +3 -13
  17. data/docs/attributes/transformations.md +63 -0
  18. data/docs/attributes/validations.md +14 -33
  19. data/docs/basics/chain.md +14 -23
  20. data/docs/basics/context.md +13 -22
  21. data/docs/basics/execution.md +8 -26
  22. data/docs/basics/setup.md +8 -19
  23. data/docs/callbacks.md +19 -32
  24. data/docs/deprecation.md +8 -25
  25. data/docs/getting_started.md +109 -76
  26. data/docs/index.md +132 -0
  27. data/docs/internationalization.md +6 -18
  28. data/docs/interruptions/exceptions.md +10 -16
  29. data/docs/interruptions/faults.md +8 -25
  30. data/docs/interruptions/halt.md +12 -27
  31. data/docs/logging.md +7 -17
  32. data/docs/middlewares.md +13 -29
  33. data/docs/outcomes/result.md +21 -38
  34. data/docs/outcomes/states.md +8 -22
  35. data/docs/outcomes/statuses.md +10 -21
  36. data/docs/stylesheets/extra.css +42 -0
  37. data/docs/tips_and_tricks.md +7 -46
  38. data/docs/workflows.md +23 -38
  39. data/examples/active_record_query_tagging.md +46 -0
  40. data/examples/paper_trail_whatdunnit.md +39 -0
  41. data/lib/cmdx/attribute.rb +88 -6
  42. data/lib/cmdx/attribute_registry.rb +20 -0
  43. data/lib/cmdx/attribute_value.rb +56 -10
  44. data/lib/cmdx/callback_registry.rb +31 -2
  45. data/lib/cmdx/chain.rb +34 -1
  46. data/lib/cmdx/coercion_registry.rb +18 -0
  47. data/lib/cmdx/coercions/array.rb +2 -0
  48. data/lib/cmdx/coercions/big_decimal.rb +3 -0
  49. data/lib/cmdx/coercions/boolean.rb +5 -0
  50. data/lib/cmdx/coercions/complex.rb +2 -0
  51. data/lib/cmdx/coercions/date.rb +4 -0
  52. data/lib/cmdx/coercions/date_time.rb +5 -0
  53. data/lib/cmdx/coercions/float.rb +2 -0
  54. data/lib/cmdx/coercions/hash.rb +4 -0
  55. data/lib/cmdx/coercions/integer.rb +2 -0
  56. data/lib/cmdx/coercions/rational.rb +2 -0
  57. data/lib/cmdx/coercions/string.rb +2 -0
  58. data/lib/cmdx/coercions/symbol.rb +2 -0
  59. data/lib/cmdx/coercions/time.rb +5 -0
  60. data/lib/cmdx/configuration.rb +119 -3
  61. data/lib/cmdx/context.rb +36 -0
  62. data/lib/cmdx/deprecator.rb +6 -3
  63. data/lib/cmdx/errors.rb +22 -0
  64. data/lib/cmdx/executor.rb +136 -7
  65. data/lib/cmdx/faults.rb +14 -0
  66. data/lib/cmdx/identifier.rb +2 -0
  67. data/lib/cmdx/locale.rb +3 -0
  68. data/lib/cmdx/log_formatters/json.rb +2 -0
  69. data/lib/cmdx/log_formatters/key_value.rb +2 -0
  70. data/lib/cmdx/log_formatters/line.rb +2 -0
  71. data/lib/cmdx/log_formatters/logstash.rb +2 -0
  72. data/lib/cmdx/log_formatters/raw.rb +2 -0
  73. data/lib/cmdx/middleware_registry.rb +20 -0
  74. data/lib/cmdx/middlewares/correlate.rb +11 -0
  75. data/lib/cmdx/middlewares/runtime.rb +4 -0
  76. data/lib/cmdx/middlewares/timeout.rb +4 -0
  77. data/lib/cmdx/pipeline.rb +24 -5
  78. data/lib/cmdx/railtie.rb +13 -0
  79. data/lib/cmdx/result.rb +133 -2
  80. data/lib/cmdx/task.rb +103 -8
  81. data/lib/cmdx/utils/call.rb +2 -0
  82. data/lib/cmdx/utils/condition.rb +3 -0
  83. data/lib/cmdx/utils/format.rb +5 -0
  84. data/lib/cmdx/validator_registry.rb +18 -0
  85. data/lib/cmdx/validators/exclusion.rb +2 -0
  86. data/lib/cmdx/validators/format.rb +2 -0
  87. data/lib/cmdx/validators/inclusion.rb +2 -0
  88. data/lib/cmdx/validators/length.rb +14 -0
  89. data/lib/cmdx/validators/numeric.rb +14 -0
  90. data/lib/cmdx/validators/presence.rb +2 -0
  91. data/lib/cmdx/version.rb +4 -1
  92. data/lib/cmdx/workflow.rb +10 -0
  93. data/lib/cmdx.rb +9 -0
  94. data/lib/generators/cmdx/locale_generator.rb +0 -1
  95. data/lib/generators/cmdx/templates/install.rb +9 -0
  96. data/mkdocs.yml +122 -0
  97. data/src/cmdx-dark-logo.png +0 -0
  98. data/src/cmdx-favicon.svg +1 -0
  99. data/src/cmdx-light-logo.png +0 -0
  100. data/src/cmdx-logo.svg +1 -0
  101. metadata +14 -3
  102. data/lib/cmdx/freezer.rb +0 -51
  103. data/src/cmdx-logo.png +0 -0
@@ -1,17 +1,10 @@
1
1
  # Basics - Context
2
2
 
3
- Task context provides flexible data storage, access, and sharing within task execution. It serves as the primary data container for all task inputs, intermediate results, and outputs.
4
-
5
- ## Table of Contents
6
-
7
- - [Assigning Data](#assigning-data)
8
- - [Accessing Data](#accessing-data)
9
- - [Modifying Context](#modifying-context)
10
- - [Data Sharing](#data-sharing)
3
+ Context is your data container for inputs, intermediate values, and outputs. It makes sharing data between tasks effortless.
11
4
 
12
5
  ## Assigning Data
13
6
 
14
- Context is automatically populated with all inputs passed to a task. All keys are normalized to symbols for consistent access:
7
+ Context automatically captures all task inputs, normalizing keys to symbols:
15
8
 
16
9
  ```ruby
17
10
  # Direct execution
@@ -21,12 +14,13 @@ CalculateShipping.execute(weight: 2.5, destination: "CA")
21
14
  CalculateShipping.new(weight: 2.5, "destination" => "CA")
22
15
  ```
23
16
 
24
- > [!IMPORTANT]
25
- > String keys are automatically converted to symbols. Use symbols for consistency in your code.
17
+ !!! warning "Important"
18
+
19
+ String keys convert to symbols automatically. Prefer symbols for consistency.
26
20
 
27
21
  ## Accessing Data
28
22
 
29
- Context provides multiple access patterns with automatic nil safety:
23
+ Access context data using method notation, hash keys, or safe accessors:
30
24
 
31
25
  ```ruby
32
26
  class CalculateShipping < CMDx::Task
@@ -49,8 +43,9 @@ class CalculateShipping < CMDx::Task
49
43
  end
50
44
  ```
51
45
 
52
- > [!IMPORTANT]
53
- > Accessing undefined context attributes returns `nil` instead of raising errors, enabling graceful handling of optional attributes.
46
+ !!! warning "Important"
47
+
48
+ Undefined attributes return `nil` instead of raising errors—perfect for optional data.
54
49
 
55
50
  ## Modifying Context
56
51
 
@@ -91,12 +86,13 @@ class CalculateShipping < CMDx::Task
91
86
  end
92
87
  ```
93
88
 
94
- > [!TIP]
95
- > Use context for both input values and intermediate results. This creates natural data flow through your task execution pipeline.
89
+ !!! tip
90
+
91
+ Use context for both input values and intermediate results. This creates natural data flow through your task execution pipeline.
96
92
 
97
93
  ## Data Sharing
98
94
 
99
- Context enables seamless data flow between related tasks in complex workflows:
95
+ Share context across tasks for seamless data flow:
100
96
 
101
97
  ```ruby
102
98
  # During execution
@@ -123,8 +119,3 @@ result = CalculateShipping.execute(destination: "New York, NY")
123
119
 
124
120
  CreateShippingLabel.execute(result)
125
121
  ```
126
-
127
- ---
128
-
129
- - **Prev:** [Basics - Execution](execution.md)
130
- - **Next:** [Basics - Chain](chain.md)
@@ -1,19 +1,10 @@
1
1
  # Basics - Execution
2
2
 
3
- Task execution in CMDx provides two distinct methods that handle success and halt scenarios differently. Understanding when to use each method is crucial for proper error handling and control flow in your application workflows.
3
+ CMDx offers two execution methods with different error handling approaches. Choose based on your needs: safe result handling or exception-based control flow.
4
4
 
5
- ## Table of Contents
5
+ ## Execution Methods
6
6
 
7
- - [Methods Overview](#methods-overview)
8
- - [Non-bang Execution](#non-bang-execution)
9
- - [Bang Execution](#bang-execution)
10
- - [Direct Instantiation](#direct-instantiation)
11
- - [Result Details](#result-details)
12
-
13
- ## Methods Overview
14
-
15
- Tasks are single-use objects. Once executed, they are frozen and cannot be executed again.
16
- Create a new instance for subsequent executions.
7
+ Both methods return results, but handle failures differently:
17
8
 
18
9
  | Method | Returns | Exceptions | Use Case |
19
10
  |--------|---------|------------|----------|
@@ -22,10 +13,7 @@ Create a new instance for subsequent executions.
22
13
 
23
14
  ## Non-bang Execution
24
15
 
25
- The `execute` method always returns a `CMDx::Result` object regardless of execution outcome.
26
- This is the preferred method for most use cases.
27
-
28
- Any unhandled exceptions will be caught and returned as a task failure.
16
+ Always returns a `CMDx::Result`, never raises exceptions. Perfect for most use cases.
29
17
 
30
18
  ```ruby
31
19
  result = CreateAccount.execute(email: "user@example.com")
@@ -43,17 +31,16 @@ result.status #=> "success"
43
31
 
44
32
  ## Bang Execution
45
33
 
46
- The bang `execute!` method raises a `CMDx::Fault` based exception when tasks fail or are skipped, and returns a `CMDx::Result` object only on success.
47
-
48
- It raises any unhandled non-fault exceptions caused during execution.
34
+ Raises `CMDx::Fault` exceptions on failure or skip. Returns results only on success.
49
35
 
50
36
  | Exception | Raised When |
51
37
  |-----------|-------------|
52
38
  | `CMDx::FailFault` | Task execution fails |
53
39
  | `CMDx::SkipFault` | Task execution is skipped |
54
40
 
55
- > [!IMPORTANT]
56
- > `execute!` behavior depends on the `task_breakpoints` or `workflow_breakpoints` configuration. By default, it raises exceptions only on failures.
41
+ !!! warning "Important"
42
+
43
+ Behavior depends on `task_breakpoints` or `workflow_breakpoints` config. Default: only failures raise exceptions.
57
44
 
58
45
  ```ruby
59
46
  begin
@@ -107,8 +94,3 @@ result.chain #=> Task execution chain
107
94
  result.context #=> Context with all task data
108
95
  result.metadata #=> Hash with execution metadata
109
96
  ```
110
-
111
- ---
112
-
113
- - **Prev:** [Basics - Setup](setup.md)
114
- - **Next:** [Basics - Context](context.md)
data/docs/basics/setup.md CHANGED
@@ -1,16 +1,10 @@
1
1
  # Basics - Setup
2
2
 
3
- Tasks are the core building blocks of CMDx, encapsulating business logic within structured, reusable objects. Each task represents a unit of work with automatic attribute validation, error handling, and execution tracking.
4
-
5
- ## Table of Contents
6
-
7
- - [Structure](#structure)
8
- - [Inheritance](#inheritance)
9
- - [Lifecycle](#lifecycle)
3
+ Tasks are the heart of CMDx—self-contained units of business logic with built-in validation, error handling, and execution tracking.
10
4
 
11
5
  ## Structure
12
6
 
13
- Tasks inherit from `CMDx::Task` and require only a `work` method:
7
+ Tasks need only two things: inherit from `CMDx::Task` and define a `work` method:
14
8
 
15
9
  ```ruby
16
10
  class ValidateDocument < CMDx::Task
@@ -20,7 +14,7 @@ class ValidateDocument < CMDx::Task
20
14
  end
21
15
  ```
22
16
 
23
- An exception will be raised if a work method is not defined.
17
+ Without a `work` method, execution raises `CMDx::UndefinedMethodError`.
24
18
 
25
19
  ```ruby
26
20
  class IncompleteTask < CMDx::Task
@@ -32,8 +26,7 @@ IncompleteTask.execute #=> raises CMDx::UndefinedMethodError
32
26
 
33
27
  ## Inheritance
34
28
 
35
- All configuration options are inheritable by any child classes.
36
- Create a base class to share common configuration across tasks:
29
+ Share configuration across tasks using inheritance:
37
30
 
38
31
  ```ruby
39
32
  class ApplicationTask < CMDx::Task
@@ -59,10 +52,11 @@ end
59
52
 
60
53
  ## Lifecycle
61
54
 
62
- Tasks follow a predictable call pattern with specific states and statuses:
55
+ Tasks follow a predictable execution pattern:
56
+
57
+ !!! danger "Caution"
63
58
 
64
- > [!CAUTION]
65
- > Tasks are single-use objects. Once executed, they are frozen and cannot be executed again.
59
+ Tasks are single-use objects. Once executed, they're frozen and immutable.
66
60
 
67
61
  | Stage | State | Status | Description |
68
62
  |-------|-------|--------|-------------|
@@ -71,8 +65,3 @@ Tasks follow a predictable call pattern with specific states and statuses:
71
65
  | **Execution** | `executing` | `success`/`failed`/`skipped` | `work` method runs |
72
66
  | **Completion** | `executed` | `success`/`failed`/`skipped` | Result finalized |
73
67
  | **Freezing** | `executed` | `success`/`failed`/`skipped` | Task becomes immutable |
74
-
75
- ---
76
-
77
- - **Prev:** [Getting Started](../getting_started.md)
78
- - **Next:** [Basics - Execution](execution.md)
data/docs/callbacks.md CHANGED
@@ -1,36 +1,27 @@
1
1
  # Callbacks
2
2
 
3
- Callbacks provide precise control over task execution lifecycle, running custom logic at specific transition points. Callback callables have access to the same context and result information as the `execute` method, enabling rich integration patterns.
3
+ Run custom logic at specific points during task execution. Callbacks have full access to task context and results, making them perfect for logging, notifications, cleanup, and more.
4
4
 
5
- Check out the [Getting Started](https://github.com/drexed/cmdx/blob/main/docs/getting_started.md#callbacks) docs for global configuration.
5
+ See [Global Configuration](getting_started.md#callbacks) for framework-wide callback setup.
6
6
 
7
- > [!IMPORTANT]
8
- > Callbacks execute in the order they are declared within each hook type. Multiple callbacks of the same type execute in declaration order (FIFO: first in, first out).
7
+ !!! warning "Important"
9
8
 
10
- ## Table of Contents
11
-
12
- - [Available Callbacks](#available-callbacks)
13
- - [Declarations](#declarations)
14
- - [Symbol References](#symbol-references)
15
- - [Proc or Lambda](#proc-or-lambda)
16
- - [Class or Module](#class-or-module)
17
- - [Conditional Execution](#conditional-execution)
18
- - [Callback Removal](#callback-removal)
9
+ Callbacks execute in declaration order (FIFO). Multiple callbacks of the same type run sequentially.
19
10
 
20
11
  ## Available Callbacks
21
12
 
22
- Callbacks execute in precise lifecycle order. Here is the complete execution sequence:
13
+ Callbacks execute in a predictable lifecycle order:
23
14
 
24
15
  ```ruby
25
16
  1. before_validation # Pre-validation setup
26
- 2. before_execution # Setup and preparation
17
+ 2. before_execution # Prepare for execution
27
18
 
28
- # --- Task#work executed ---
19
+ # --- Task#work executes ---
29
20
 
30
- 3. on_[complete|interrupted] # Based on execution state
31
- 4. on_executed # Task finished (any outcome)
32
- 5. on_[success|skipped|failed] # Based on execution status
33
- 6. on_[good|bad] # Based on outcome classification
21
+ 3. on_[complete|interrupted] # State-based (execution lifecycle)
22
+ 4. on_executed # Always runs after work completes
23
+ 5. on_[success|skipped|failed] # Status-based (business outcome)
24
+ 6. on_[good|bad] # Outcome-based (success/skip vs fail)
34
25
  ```
35
26
 
36
27
  ## Declarations
@@ -73,7 +64,7 @@ Use anonymous functions for inline callback logic:
73
64
  ```ruby
74
65
  class ProcessBooking < CMDx::Task
75
66
  # Proc
76
- on_interrupted proc { |task| ReservationSystem.pause! }
67
+ on_interrupted proc { ReservationSystem.pause! }
77
68
 
78
69
  # Lambda
79
70
  on_complete -> { ReservationSystem.resume! }
@@ -120,10 +111,10 @@ class ProcessBooking < CMDx::Task
120
111
  before_execution :notify_guest, if: :messaging_enabled?, unless: :messaging_blocked?
121
112
 
122
113
  # Proc
123
- on_failure :increment_failure, if: ->(task) { Rails.env.production? && task.class.name.include?("Legacy") }
114
+ on_failure :increment_failure, if: -> { Rails.env.production? && self.class.name.include?("Legacy") }
124
115
 
125
116
  # Lambda
126
- on_success :ping_housekeeping, if: proc { |task| task.context.rooms_need_cleaning? }
117
+ on_success :ping_housekeeping, if: proc { context.rooms_need_cleaning? }
127
118
 
128
119
  # Class or Module
129
120
  on_complete :send_confirmation, unless: MessagingPermissionCheck
@@ -138,7 +129,7 @@ class ProcessBooking < CMDx::Task
138
129
  private
139
130
 
140
131
  def messaging_enabled?
141
- context.guest.messaging_preference.present?
132
+ context.guest.messaging_preference == true
142
133
  end
143
134
 
144
135
  def messaging_blocked?
@@ -149,10 +140,11 @@ end
149
140
 
150
141
  ## Callback Removal
151
142
 
152
- Remove callbacks at runtime for dynamic behavior control:
143
+ Remove unwanted callbacks dynamically:
144
+
145
+ !!! warning "Important"
153
146
 
154
- > [!IMPORTANT]
155
- > Only one removal operation is allowed per `deregister` call. Multiple removals require separate calls.
147
+ Each `deregister` call removes one callback. Use multiple calls for batch removals.
156
148
 
157
149
  ```ruby
158
150
  class ProcessBooking < CMDx::Task
@@ -163,8 +155,3 @@ class ProcessBooking < CMDx::Task
163
155
  deregister :callback, :on_complete, BookingConfirmationCallback
164
156
  end
165
157
  ```
166
-
167
- ---
168
-
169
- - **Prev:** [Attributes - Defaults](attributes/defaults.md)
170
- - **Next:** [Middlewares](middlewares.md)
data/docs/deprecation.md CHANGED
@@ -1,28 +1,16 @@
1
1
  # Task Deprecation
2
2
 
3
- Task deprecation provides a systematic approach to managing legacy tasks in CMDx applications. The deprecation system enables controlled migration paths by issuing warnings, logging messages, or preventing execution of deprecated tasks entirely, helping teams maintain code quality while providing clear upgrade paths.
4
-
5
- ## Table of Contents
6
-
7
- - [Modes](#modes)
8
- - [Raise](#raise)
9
- - [Log](#log)
10
- - [Warn](#warn)
11
- - [Declarations](#declarations)
12
- - [Symbol or String](#symbol-or-string)
13
- - [Boolean or Nil](#boolean-or-nil)
14
- - [Method](#method)
15
- - [Proc or Lambda](#proc-or-lambda)
16
- - [Class or Module](#class-or-module)
3
+ Manage legacy tasks gracefully with built-in deprecation support. Choose how to handle deprecated tasks—log warnings for awareness, issue Ruby warnings for development, or prevent execution entirely.
17
4
 
18
5
  ## Modes
19
6
 
20
7
  ### Raise
21
8
 
22
- `:raise` mode prevents task execution entirely. Use this for tasks that should no longer be used under any circumstances.
9
+ Prevent task execution completely. Perfect for tasks that must no longer run.
10
+
11
+ !!! warning
23
12
 
24
- > [!WARNING]
25
- > Use `:raise` mode carefully in production environments as it will break existing workflows immediately.
13
+ Use `:raise` mode carefully—it will break existing workflows immediately.
26
14
 
27
15
  ```ruby
28
16
  class ProcessObsoleteAPI < CMDx::Task
@@ -39,7 +27,7 @@ result = ProcessObsoleteAPI.execute
39
27
 
40
28
  ### Log
41
29
 
42
- `:log` mode allows continued usage while tracking deprecation warnings. Perfect for gradual migration scenarios where immediate replacement isn't feasible.
30
+ Allow execution while tracking deprecation in logs. Ideal for gradual migrations.
43
31
 
44
32
  ```ruby
45
33
  class ProcessLegacyFormat < CMDx::Task
@@ -62,7 +50,7 @@ result.successful? #=> true
62
50
 
63
51
  ### Warn
64
52
 
65
- `:warn` mode issues Ruby warnings visible in development and testing environments. Useful for alerting developers without affecting production logging.
53
+ Issue Ruby warnings visible during development and testing. Keeps production logs clean while alerting developers.
66
54
 
67
55
  ```ruby
68
56
  class ProcessOldData < CMDx::Task
@@ -77,7 +65,7 @@ result = ProcessOldData.execute
77
65
  result.successful? #=> true
78
66
 
79
67
  # Ruby warning appears in stderr:
80
- # [ProcessOldData] DEPRECATED: migrate to replacement or discontinue use
68
+ # [ProcessOldData] DEPRECATED: migrate to a replacement or discontinue use
81
69
  ```
82
70
 
83
71
  ## Declarations
@@ -155,8 +143,3 @@ class OutdatedConnector < CMDx::Task
155
143
  settings(deprecated: OutdatedTaskDeprecator.new)
156
144
  end
157
145
  ```
158
-
159
- ---
160
-
161
- - **Prev:** [Internationalization (i18n)](internationalization.md)
162
- - **Next:** [Workflows](workflows.md)
@@ -1,51 +1,33 @@
1
1
  # Getting Started
2
2
 
3
- CMDx is a Ruby framework for building maintainable, observable business logic through composable command objects. Design robust workflows with automatic attribute validation, structured error handling, comprehensive logging, and intelligent execution flow control.
4
-
5
- **Common Challenges:**
6
-
7
- - Inconsistent patterns across implementations
8
- - Minimal or no logging, making debugging painful
9
- - Fragile designs that erode developer confidence
10
-
11
- **CMDx Solutions:**
12
-
13
- - Establishes a consistent, standardized design
14
- - Provides flow control and error handling
15
- - Supports composable, reusable workflows
16
- - Includes detailed logging for observability
17
- - Defines input attributes with fallback defaults
18
- - Adds validations and type coercions
19
- - Plus many other developer-friendly tools
20
-
21
- ## Table of Contents
22
-
23
- - [Compose, Execute, React, Observe pattern](#compose-execute-react-observe-pattern)
24
- - [Installation](#installation)
25
- - [Configuration Hierarchy](#configuration-hierarchy)
26
- - [Global Configuration](#global-configuration)
27
- - [Breakpoints](#breakpoints)
28
- - [Logging](#logging)
29
- - [Middlewares](#middlewares)
30
- - [Callbacks](#callbacks)
31
- - [Coercions](#coercions)
32
- - [Validators](#validators)
33
- - [Task Configuration](#task-configuration)
34
- - [Settings](#settings)
35
- - [Registrations](#registrations)
36
- - [Configuration Management](#configuration-management)
37
- - [Access](#access)
38
- - [Resetting](#resetting)
39
- - [Task Generator](#task-generator)
40
-
41
- ## Compose, Execute, React, Observe pattern
42
-
43
- CMDx encourages breaking business logic into composable tasks. Each task can be combined into larger workflows, executed with standardized flow control, and fully observed through logging, validations, and context.
44
-
45
- - *Compose* → Define small, contract-driven tasks with typed attributes, validations, and natural workflow composition.
46
- - *Execute* → Run tasks with clear outcomes, intentional halts, and pluggable behaviors via middlewares and callbacks.
47
- - *React* → Adapt to outcomes by chaining follow-up tasks, handling faults, or shaping future flows.
48
- - *Observe* → Capture immutable results, structured logs, and full execution chains for reliable tracing and insight.
3
+ CMDx is a Ruby framework for building maintainable, observable business logic through composable command objects. It brings structure, consistency, and powerful developer tools to your business processes.
4
+
5
+ **Common challenges it solves:**
6
+
7
+ - Inconsistent service object patterns across your codebase
8
+ - Limited logging makes debugging a nightmare
9
+ - Fragile error handling erodes confidence
10
+
11
+ **What you get:**
12
+
13
+ - Consistent, standardized architecture
14
+ - Built-in flow control and error handling
15
+ - Composable, reusable workflows
16
+ - Comprehensive logging for observability
17
+ - Attribute validation with type coercions
18
+ - Sensible defaults and developer-friendly APIs
19
+
20
+ ## The CERO Pattern
21
+
22
+ CMDx embraces the Compose, Execute, React, Observe (CERO) pattern—a simple yet powerful approach to building reliable business logic.
23
+
24
+ 🧩 **Compose** — Define small, focused tasks with typed attributes and validations
25
+
26
+ **Execute** — Run tasks with clear outcomes and pluggable behaviors
27
+
28
+ 🔄 **React** — Adapt to outcomes by chaining follow-up tasks or handling faults
29
+
30
+ 🔍 **Observe** — Capture structured logs and execution chains for debugging
49
31
 
50
32
  ## Installation
51
33
 
@@ -65,39 +47,78 @@ This creates `config/initializers/cmdx.rb` file.
65
47
 
66
48
  ## Configuration Hierarchy
67
49
 
68
- CMDx follows a two-tier configuration hierarchy:
50
+ CMDx uses a straightforward two-tier configuration system:
69
51
 
70
- 1. **Global Configuration**: Framework-wide defaults
71
- 2. **Task Settings**: Class-level overrides via `settings`
52
+ 1. **Global Configuration** Framework-wide defaults
53
+ 2. **Task Settings** Class-level overrides using `settings`
72
54
 
73
- > [!IMPORTANT]
74
- > Task-level settings take precedence over global configuration. Settings are inherited from superclasses and can be overridden in subclasses.
55
+ !!! warning "Important"
56
+
57
+ Task settings take precedence over global config. Settings are inherited from parent classes and can be overridden in subclasses.
75
58
 
76
59
  ## Global Configuration
77
60
 
78
- Global configuration settings apply to all tasks inherited from `CMDx::Task`.
79
- Globally these settings are initialized with sensible defaults.
61
+ Configure framework-wide defaults that apply to all tasks. These settings come with sensible defaults out of the box.
80
62
 
81
63
  ### Breakpoints
82
64
 
83
- Raise `CMDx::Fault` when a task called with `execute!` returns a matching status.
65
+ Control when `execute!` raises a `CMDx::Fault` based on task status.
84
66
 
85
67
  ```ruby
86
68
  CMDx.configure do |config|
87
- # String or Array[String]
88
- config.task_breakpoints = "failed"
69
+ config.task_breakpoints = "failed" # String or Array[String]
89
70
  end
90
71
  ```
91
72
 
92
- Workflow breakpoints stops execution and of workflow pipeline on the first task that returns a matching status and throws its `CMDx::Fault`.
73
+ For workflows, configure which statuses halt the execution pipeline:
93
74
 
94
75
  ```ruby
95
76
  CMDx.configure do |config|
96
- # String or Array[String]
97
77
  config.workflow_breakpoints = ["skipped", "failed"]
98
78
  end
99
79
  ```
100
80
 
81
+ ### Backtraces
82
+
83
+ Enable detailed backtraces for non-fault exceptions to improve debugging. Optionally clean up stack traces to remove framework noise.
84
+
85
+ !!! note
86
+
87
+ In Rails environments, `backtrace_cleaner` defaults to `Rails.backtrace_cleaner.clean`.
88
+
89
+ ```ruby
90
+ CMDx.configure do |config|
91
+ # Truthy
92
+ config.backtrace = true
93
+
94
+ # Via callable (must respond to `call(backtrace)`)
95
+ config.backtrace_cleaner = AdvanceCleaner.new
96
+
97
+ # Via proc or lambda
98
+ config.backtrace_cleaner = ->(backtrace) { backtrace[0..5] }
99
+ end
100
+ ```
101
+
102
+ ### Exception Handlers
103
+
104
+ Register handlers that run when non-fault exceptions occur.
105
+
106
+ !!! tip
107
+
108
+ Use exception handlers to send errors to your APM of choice.
109
+
110
+ ```ruby
111
+ CMDx.configure do |config|
112
+ # Via callable (must respond to `call(task, exception)`)
113
+ config.exception_handler = NewRelicReporter
114
+
115
+ # Via proc or lambda
116
+ config.exception_handler = proc do |task, exception|
117
+ APMService.report(exception, extra_data: { task: task.name, id: task.id })
118
+ end
119
+ end
120
+ ```
121
+
101
122
  ### Logging
102
123
 
103
124
  ```ruby
@@ -108,7 +129,7 @@ end
108
129
 
109
130
  ### Middlewares
110
131
 
111
- See the [Middelwares](#https://github.com/drexed/cmdx/blob/main/docs/middlewares.md#declarations) docs for task level configurations.
132
+ See the [Middlewares](middlewares.md#declarations) docs for task level configurations.
112
133
 
113
134
  ```ruby
114
135
  CMDx.configure do |config|
@@ -132,12 +153,13 @@ CMDx.configure do |config|
132
153
  end
133
154
  ```
134
155
 
135
- > [!NOTE]
136
- > Middlewares are executed in registration order. Each middleware wraps the next, creating an execution chain around task logic.
156
+ !!! note
157
+
158
+ Middlewares are executed in registration order. Each middleware wraps the next, creating an execution chain around task logic.
137
159
 
138
160
  ### Callbacks
139
161
 
140
- See the [Callbacks](#https://github.com/drexed/cmdx/blob/main/docs/callbacks.md#declarations) docs for task level configurations.
162
+ See the [Callbacks](callbacks.md#declarations) docs for task level configurations.
141
163
 
142
164
  ```ruby
143
165
  CMDx.configure do |config|
@@ -163,7 +185,7 @@ end
163
185
 
164
186
  ### Coercions
165
187
 
166
- See the [Attributes - Coercions](#https://github.com/drexed/cmdx/blob/main/docs/attributes/coercions.md#declarations) docs for task level configurations.
188
+ See the [Attributes - Coercions](attributes/coercions.md#declarations) docs for task level configurations.
167
189
 
168
190
  ```ruby
169
191
  CMDx.configure do |config|
@@ -189,7 +211,7 @@ end
189
211
 
190
212
  ### Validators
191
213
 
192
- See the [Attributes - Validations](#https://github.com/drexed/cmdx/blob/main/docs/attributes/validations.md#declarations) docs for task level configurations.
214
+ See the [Attributes - Validations](attributes/validations.md#declarations) docs for task level configurations.
193
215
 
194
216
  ```ruby
195
217
  CMDx.configure do |config|
@@ -224,6 +246,8 @@ class GenerateInvoice < CMDx::Task
224
246
  # Global configuration overrides
225
247
  task_breakpoints: ["failed"], # Breakpoint override
226
248
  workflow_breakpoints: [], # Breakpoint override
249
+ backtrace: true, # Toggle backtrace
250
+ backtrace_cleaner: ->(bt) { bt[0..5] }, # Backtrace cleaner
227
251
  logger: CustomLogger.new($stdout), # Custom logger
228
252
 
229
253
  # Task configuration settings
@@ -231,7 +255,10 @@ class GenerateInvoice < CMDx::Task
231
255
  log_level: :info, # Log level override
232
256
  log_formatter: CMDx::LogFormatters::Json.new # Log formatter override
233
257
  tags: ["billing", "financial"], # Logging tags
234
- deprecated: true # Task deprecations
258
+ deprecated: true, # Task deprecations
259
+ retries: 3, # Non-fault exception retries
260
+ retry_on: [External::ApiError], # List of exceptions to retry on
261
+ retry_jitter: 1 # Space between retry iteration, eg: current retry num + 1
235
262
  )
236
263
 
237
264
  def work
@@ -240,13 +267,13 @@ class GenerateInvoice < CMDx::Task
240
267
  end
241
268
  ```
242
269
 
243
- > [!TIP]
244
- > Use task-level settings for tasks that require special handling, such as financial reporting, external API integrations, or critical system operations.
270
+ !!! warning "Important"
271
+
272
+ Retries reuse the same context. By default, all `StandardError` exceptions are retried unless you specify `retry_on`.
245
273
 
246
274
  ### Registrations
247
275
 
248
- Register middlewares, callbacks, coercions, and validators on a specific task.
249
- Deregister options that should not be available.
276
+ Register or deregister middlewares, callbacks, coercions, and validators for specific tasks:
250
277
 
251
278
  ```ruby
252
279
  class SendCampaignEmail < CMDx::Task
@@ -298,8 +325,9 @@ end
298
325
 
299
326
  ### Resetting
300
327
 
301
- > [!WARNING]
302
- > Resetting configuration affects the entire application. Use primarily in test environments or during application initialization.
328
+ !!! warning
329
+
330
+ Resetting affects your entire application. Use this primarily in test environments.
303
331
 
304
332
  ```ruby
305
333
  # Reset to framework defaults
@@ -336,10 +364,15 @@ class ModerateBlogPost < CMDx::Task
336
364
  end
337
365
  ```
338
366
 
339
- > [!TIP]
340
- > Use **present tense verbs + noun** for task names, eg: `ModerateBlogPost`, `ScheduleAppointment`, `ValidateDocument`
367
+ !!! tip
368
+
369
+ Use **present tense verbs + noun** for task names, eg: `ModerateBlogPost`, `ScheduleAppointment`, `ValidateDocument`
370
+
371
+ ## Type safety
341
372
 
342
- ---
373
+ CMDx includes built-in RBS (Ruby Type Signature) inline annotations throughout the codebase, providing type information for static analysis and editor support.
343
374
 
344
- - **Prev:** [Tips and Tricks](tips_and_tricks.md)
345
- - **Next:** [Basics - Setup](basics/setup.md)
375
+ - **Type checking** Catch type errors before runtime using tools like Steep or TypeProf
376
+ - **Better IDE support** — Enhanced autocomplete, navigation, and inline documentation
377
+ - **Self-documenting code** — Clear method signatures and return types
378
+ - **Refactoring confidence** — Type-aware refactoring reduces bugs