cmdx 1.7.5 → 1.9.0
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/.DS_Store +0 -0
- data/.cursor/prompts/docs.md +3 -3
- data/.cursor/prompts/llms.md +1 -3
- data/.cursor/prompts/rspec.md +1 -1
- data/.irbrc +14 -2
- data/CHANGELOG.md +62 -29
- data/LLM.md +203 -78
- data/README.md +23 -85
- data/docs/.DS_Store +0 -0
- data/docs/assets/favicon.ico +0 -0
- data/docs/assets/favicon.svg +1 -0
- data/docs/attributes/coercions.md +19 -29
- data/docs/attributes/defaults.md +3 -16
- data/docs/attributes/definitions.md +29 -39
- data/docs/attributes/naming.md +3 -13
- data/docs/attributes/transformations.md +63 -0
- data/docs/attributes/validations.md +23 -40
- data/docs/basics/chain.md +14 -23
- data/docs/basics/context.md +13 -22
- data/docs/basics/execution.md +8 -26
- data/docs/basics/setup.md +8 -19
- data/docs/callbacks.md +19 -32
- data/docs/deprecation.md +8 -25
- data/docs/getting_started.md +101 -77
- data/docs/index.md +120 -0
- data/docs/internationalization.md +6 -18
- data/docs/interruptions/exceptions.md +10 -16
- data/docs/interruptions/faults.md +8 -25
- data/docs/interruptions/halt.md +31 -25
- data/docs/logging.md +7 -17
- data/docs/middlewares.md +13 -29
- data/docs/outcomes/result.md +21 -38
- data/docs/outcomes/states.md +8 -22
- data/docs/outcomes/statuses.md +10 -21
- data/docs/stylesheets/extra.css +42 -0
- data/docs/tips_and_tricks.md +7 -46
- data/docs/workflows.md +23 -38
- data/examples/active_record_query_tagging.md +46 -0
- data/examples/paper_trail_whatdunnit.md +39 -0
- data/lib/cmdx/attribute.rb +9 -2
- data/lib/cmdx/attribute_value.rb +31 -10
- data/lib/cmdx/callback_registry.rb +12 -2
- data/lib/cmdx/coercions/hash.rb +6 -1
- data/lib/cmdx/configuration.rb +10 -2
- data/lib/cmdx/deprecator.rb +3 -3
- data/lib/cmdx/errors.rb +1 -1
- data/lib/cmdx/executor.rb +97 -9
- data/lib/cmdx/log_formatters/logstash.rb +4 -4
- data/lib/cmdx/pipeline.rb +4 -4
- data/lib/cmdx/railtie.rb +9 -0
- data/lib/cmdx/result.rb +10 -1
- data/lib/cmdx/task.rb +12 -7
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx.rb +1 -0
- data/lib/generators/cmdx/templates/install.rb +9 -0
- data/lib/locales/af.yml +2 -2
- data/lib/locales/ar.yml +2 -2
- data/lib/locales/az.yml +2 -2
- data/lib/locales/be.yml +2 -2
- data/lib/locales/bg.yml +2 -2
- data/lib/locales/bn.yml +2 -2
- data/lib/locales/bs.yml +2 -2
- data/lib/locales/ca.yml +2 -2
- data/lib/locales/cnr.yml +2 -2
- data/lib/locales/cs.yml +2 -2
- data/lib/locales/cy.yml +2 -2
- data/lib/locales/da.yml +2 -2
- data/lib/locales/de.yml +2 -2
- data/lib/locales/dz.yml +2 -2
- data/lib/locales/el.yml +2 -2
- data/lib/locales/en.yml +2 -2
- data/lib/locales/eo.yml +2 -2
- data/lib/locales/es.yml +2 -2
- data/lib/locales/et.yml +2 -2
- data/lib/locales/eu.yml +2 -2
- data/lib/locales/fa.yml +2 -2
- data/lib/locales/fi.yml +2 -2
- data/lib/locales/fr.yml +2 -2
- data/lib/locales/fy.yml +2 -2
- data/lib/locales/gd.yml +2 -2
- data/lib/locales/gl.yml +2 -2
- data/lib/locales/he.yml +2 -2
- data/lib/locales/hi.yml +2 -2
- data/lib/locales/hr.yml +2 -2
- data/lib/locales/hu.yml +2 -2
- data/lib/locales/hy.yml +2 -2
- data/lib/locales/id.yml +2 -2
- data/lib/locales/is.yml +2 -2
- data/lib/locales/it.yml +2 -2
- data/lib/locales/ja.yml +2 -2
- data/lib/locales/ka.yml +2 -2
- data/lib/locales/kk.yml +2 -2
- data/lib/locales/km.yml +2 -2
- data/lib/locales/kn.yml +2 -2
- data/lib/locales/ko.yml +2 -2
- data/lib/locales/lb.yml +2 -2
- data/lib/locales/lo.yml +2 -2
- data/lib/locales/lt.yml +2 -2
- data/lib/locales/lv.yml +2 -2
- data/lib/locales/mg.yml +2 -2
- data/lib/locales/mk.yml +2 -2
- data/lib/locales/ml.yml +2 -2
- data/lib/locales/mn.yml +2 -2
- data/lib/locales/mr-IN.yml +2 -2
- data/lib/locales/ms.yml +2 -2
- data/lib/locales/nb.yml +2 -2
- data/lib/locales/ne.yml +2 -2
- data/lib/locales/nl.yml +2 -2
- data/lib/locales/nn.yml +2 -2
- data/lib/locales/oc.yml +2 -2
- data/lib/locales/or.yml +2 -2
- data/lib/locales/pa.yml +2 -2
- data/lib/locales/pl.yml +2 -2
- data/lib/locales/pt.yml +2 -2
- data/lib/locales/rm.yml +2 -2
- data/lib/locales/ro.yml +2 -2
- data/lib/locales/ru.yml +2 -2
- data/lib/locales/sc.yml +2 -2
- data/lib/locales/sk.yml +2 -2
- data/lib/locales/sl.yml +2 -2
- data/lib/locales/sq.yml +2 -2
- data/lib/locales/sr.yml +2 -2
- data/lib/locales/st.yml +2 -2
- data/lib/locales/sv.yml +2 -2
- data/lib/locales/sw.yml +2 -2
- data/lib/locales/ta.yml +2 -2
- data/lib/locales/te.yml +2 -2
- data/lib/locales/th.yml +2 -2
- data/lib/locales/tl.yml +2 -2
- data/lib/locales/tr.yml +2 -2
- data/lib/locales/tt.yml +2 -2
- data/lib/locales/ug.yml +2 -2
- data/lib/locales/uk.yml +2 -2
- data/lib/locales/ur.yml +2 -2
- data/lib/locales/uz.yml +2 -2
- data/lib/locales/vi.yml +2 -2
- data/lib/locales/wo.yml +2 -2
- data/lib/locales/zh-CN.yml +2 -2
- data/lib/locales/zh-HK.yml +2 -2
- data/lib/locales/zh-TW.yml +2 -2
- data/lib/locales/zh-YUE.yml +2 -2
- data/mkdocs.yml +122 -0
- data/src/cmdx-dark-logo.png +0 -0
- data/src/cmdx-favicon.svg +1 -0
- data/src/cmdx-light-logo.png +0 -0
- data/src/cmdx-logo.svg +1 -0
- metadata +15 -4
- data/lib/cmdx/freezer.rb +0 -51
- data/src/cmdx-logo.png +0 -0
    
        data/docs/getting_started.md
    CHANGED
    
    | @@ -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.  | 
| 4 | 
            -
             | 
| 5 | 
            -
            **Common  | 
| 6 | 
            -
             | 
| 7 | 
            -
            - Inconsistent patterns across  | 
| 8 | 
            -
            -  | 
| 9 | 
            -
            - Fragile  | 
| 10 | 
            -
             | 
| 11 | 
            -
            ** | 
| 12 | 
            -
             | 
| 13 | 
            -
            -  | 
| 14 | 
            -
            -  | 
| 15 | 
            -
            -  | 
| 16 | 
            -
            -  | 
| 17 | 
            -
            -  | 
| 18 | 
            -
            -  | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 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  | 
| 50 | 
            +
            CMDx uses a straightforward two-tier configuration system:
         | 
| 69 51 |  | 
| 70 | 
            -
            1. **Global Configuration | 
| 71 | 
            -
            2. **Task Settings | 
| 52 | 
            +
            1. **Global Configuration** — Framework-wide defaults
         | 
| 53 | 
            +
            2. **Task Settings** — Class-level overrides using `settings`
         | 
| 72 54 |  | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 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 | 
            -
             | 
| 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 | 
            -
             | 
| 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 | 
            -
             | 
| 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 [ | 
| 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 | 
            -
             | 
| 136 | 
            -
             | 
| 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]( | 
| 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]( | 
| 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]( | 
| 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 | 
| 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 | 
            -
             | 
| 244 | 
            -
             | 
| 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  | 
| 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 | 
            -
             | 
| 302 | 
            -
             | 
| 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,6 @@ class ModerateBlogPost < CMDx::Task | |
| 336 364 | 
             
            end
         | 
| 337 365 | 
             
            ```
         | 
| 338 366 |  | 
| 339 | 
            -
             | 
| 340 | 
            -
            > Use **present tense verbs + noun** for task names, eg: `ModerateBlogPost`, `ScheduleAppointment`, `ValidateDocument`
         | 
| 341 | 
            -
             | 
| 342 | 
            -
            ---
         | 
| 367 | 
            +
            !!! tip
         | 
| 343 368 |  | 
| 344 | 
            -
             | 
| 345 | 
            -
            - **Next:** [Basics - Setup](basics/setup.md)
         | 
| 369 | 
            +
                Use **present tense verbs + noun** for task names, eg: `ModerateBlogPost`, `ScheduleAppointment`, `ValidateDocument`
         | 
    
        data/docs/index.md
    ADDED
    
    | @@ -0,0 +1,120 @@ | |
| 1 | 
            +
            # CMDx
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Build business logic that's powerful, predictable, and maintainable.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            [](https://rubygems.org/gems/cmdx)
         | 
| 6 | 
            +
            [](https://github.com/drexed/cmdx/actions/workflows/ci.yml)
         | 
| 7 | 
            +
            [](https://github.com/drexed/cmdx/blob/main/LICENSE.txt)
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            Say goodbye to messy service objects. CMDx helps you design business logic with clarity and consistency—build faster, debug easier, and ship with confidence.
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            ## Installation
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            ```sh
         | 
| 14 | 
            +
            gem install cmdx
         | 
| 15 | 
            +
            # - or -
         | 
| 16 | 
            +
            bundle add cmdx
         | 
| 17 | 
            +
            ```
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            ## Quick Example
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            Build powerful business logic in four simple steps:
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            ### 1. Compose
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            === "Full Featured Task"
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                ```ruby
         | 
| 28 | 
            +
                class AnalyzeMetrics < CMDx::Task
         | 
| 29 | 
            +
                  register :middleware, CMDx::Middlewares::Correlate, id: -> { Current.request_id }
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  on_success :track_analysis_completion!
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  required :dataset_id, type: :integer, numeric: { min: 1 }
         | 
| 34 | 
            +
                  optional :analysis_type, default: "standard"
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  def work
         | 
| 37 | 
            +
                    if dataset.nil?
         | 
| 38 | 
            +
                      fail!("Dataset not found", code: 404)
         | 
| 39 | 
            +
                    elsif dataset.unprocessed?
         | 
| 40 | 
            +
                      skip!("Dataset not ready for analysis")
         | 
| 41 | 
            +
                    else
         | 
| 42 | 
            +
                      context.result = PValueAnalyzer.execute(dataset:, analysis_type:)
         | 
| 43 | 
            +
                      context.analyzed_at = Time.now
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                      SendAnalyzedEmail.execute(user_id: Current.account.manager_id)
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  private
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  def dataset
         | 
| 52 | 
            +
                    @dataset ||= Dataset.find_by(id: dataset_id)
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  def track_analysis_completion!
         | 
| 56 | 
            +
                    dataset.update!(analysis_result_id: context.result.id)
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
                ```
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            === "Minimum Viable Task"
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                ```ruby
         | 
| 64 | 
            +
                class SendAnalyzedEmail < CMDx::Task
         | 
| 65 | 
            +
                  def work
         | 
| 66 | 
            +
                    user = User.find(context.user_id)
         | 
| 67 | 
            +
                    MetricsMailer.analyzed(user).deliver_now
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
                ```
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            ### 2. Execute
         | 
| 73 | 
            +
             | 
| 74 | 
            +
            ```ruby
         | 
| 75 | 
            +
            result = AnalyzeMetrics.execute(
         | 
| 76 | 
            +
              dataset_id: 123,
         | 
| 77 | 
            +
              "analysis_type" => "advanced"
         | 
| 78 | 
            +
            )
         | 
| 79 | 
            +
            ```
         | 
| 80 | 
            +
             | 
| 81 | 
            +
            ### 3. React
         | 
| 82 | 
            +
             | 
| 83 | 
            +
            ```ruby
         | 
| 84 | 
            +
            if result.success?
         | 
| 85 | 
            +
              puts "Metrics analyzed at #{result.context.analyzed_at}"
         | 
| 86 | 
            +
            elsif result.skipped?
         | 
| 87 | 
            +
              puts "Skipping analyzation due to: #{result.reason}"
         | 
| 88 | 
            +
            elsif result.failed?
         | 
| 89 | 
            +
              puts "Analyzation failed due to: #{result.reason} with code #{result.metadata[:code]}"
         | 
| 90 | 
            +
            end
         | 
| 91 | 
            +
            ```
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            ### 4. Observe
         | 
| 94 | 
            +
             | 
| 95 | 
            +
            ```log
         | 
| 96 | 
            +
            I, [2022-07-17T18:42:37.000000 #3784] INFO -- CMDx:
         | 
| 97 | 
            +
            index=1 chain_id="018c2b95-23j4-2kj3-32kj-3n4jk3n4jknf" type="Task" class="SendAnalyzedEmail" state="complete" status="success" metadata={runtime: 347}
         | 
| 98 | 
            +
             | 
| 99 | 
            +
            I, [2022-07-17T18:43:15.000000 #3784] INFO -- CMDx:
         | 
| 100 | 
            +
            index=0 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="AnalyzeMetrics" state="complete" status="success" metadata={runtime: 187}
         | 
| 101 | 
            +
            ```
         | 
| 102 | 
            +
             | 
| 103 | 
            +
            Ready to dive in? Check out the [Getting Started](getting_started.md) guide to learn more.
         | 
| 104 | 
            +
             | 
| 105 | 
            +
            ## Ecosystem
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            - [cmdx-rspec](https://github.com/drexed/cmdx-rspec) - RSpec test matchers
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            For backwards compatibility of certain functionality:
         | 
| 110 | 
            +
             | 
| 111 | 
            +
            - [cmdx-i18n](https://github.com/drexed/cmdx-i18n) - 85+ translations, `v1.5.0` - `v1.6.2`
         | 
| 112 | 
            +
            - [cmdx-parallel](https://github.com/drexed/cmdx-parallel) - Parallel workflow tasks, `v1.6.1` - `v1.6.2`
         | 
| 113 | 
            +
             | 
| 114 | 
            +
            ## Contributing
         | 
| 115 | 
            +
             | 
| 116 | 
            +
            Bug reports and pull requests are welcome at https://github.com/drexed/cmdx. We're committed to fostering a welcoming, collaborative community. Please follow our [code of conduct](CODE_OF_CONDUCT.md).
         | 
| 117 | 
            +
             | 
| 118 | 
            +
            ## License
         | 
| 119 | 
            +
             | 
| 120 | 
            +
            The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
         | 
| @@ -1,17 +1,10 @@ | |
| 1 1 | 
             
            # Internationalization (i18n)
         | 
| 2 2 |  | 
| 3 | 
            -
            CMDx  | 
| 3 | 
            +
            CMDx supports 90+ languages out of the box for all error messages, validations, coercions, and faults. Error messages automatically adapt to the current `I18n.locale`, making it easy to build applications for global audiences.
         | 
| 4 4 |  | 
| 5 | 
            -
            ##  | 
| 5 | 
            +
            ## Usage
         | 
| 6 6 |  | 
| 7 | 
            -
             | 
| 8 | 
            -
            - [Configuration](#configuration)
         | 
| 9 | 
            -
              - [Local Copies](#local-copies)
         | 
| 10 | 
            -
              - [Available Locales](#available-locales)
         | 
| 11 | 
            -
             | 
| 12 | 
            -
            ## Localization
         | 
| 13 | 
            -
             | 
| 14 | 
            -
            CMDx automatically localizes all error messages based on the `I18n.locale` setting.
         | 
| 7 | 
            +
            All error messages are automatically localized based on your current locale:
         | 
| 15 8 |  | 
| 16 9 | 
             
            ```ruby
         | 
| 17 10 | 
             
            class ProcessQuote < CMDx::Task
         | 
| @@ -30,11 +23,11 @@ end | |
| 30 23 |  | 
| 31 24 | 
             
            ## Configuration
         | 
| 32 25 |  | 
| 33 | 
            -
             | 
| 26 | 
            +
            CMDx uses the `I18n` gem for localization. In Rails, locales load automatically.
         | 
| 34 27 |  | 
| 35 | 
            -
            ###  | 
| 28 | 
            +
            ### Copy Locale Files
         | 
| 36 29 |  | 
| 37 | 
            -
             | 
| 30 | 
            +
            Copy locale files to your Rails application's `config/locales` directory:
         | 
| 38 31 |  | 
| 39 32 | 
             
            ```bash
         | 
| 40 33 | 
             
            rails generate cmdx:locale [LOCALE]
         | 
| @@ -131,8 +124,3 @@ rails generate cmdx:locale fr | |
| 131 124 | 
             
            - zh-HK - Chinese (Hong Kong)
         | 
| 132 125 | 
             
            - zh-TW - Chinese (Traditional)
         | 
| 133 126 | 
             
            - zh-YUE - Chinese (Yue)
         | 
| 134 | 
            -
             | 
| 135 | 
            -
            ---
         | 
| 136 | 
            -
             | 
| 137 | 
            -
            - **Prev:** [Logging](logging.md)
         | 
| 138 | 
            -
            - **Next:** [Deprecation](deprecation.md)
         | 
| @@ -1,21 +1,16 @@ | |
| 1 1 | 
             
            # Interruptions - Exceptions
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
            ## Table of Contents
         | 
| 6 | 
            -
             | 
| 7 | 
            -
            - [Exception Handling](#exception-handling)
         | 
| 8 | 
            -
              - [Non-bang execution](#non-bang-execution)
         | 
| 9 | 
            -
              - [Bang execution](#bang-execution)
         | 
| 3 | 
            +
            Exception handling differs between `execute` and `execute!`. Choose the method that matches your error handling strategy.
         | 
| 10 4 |  | 
| 11 5 | 
             
            ## Exception Handling
         | 
| 12 6 |  | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 7 | 
            +
            !!! warning "Important"
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                Prefer `skip!` and `fail!` over raising exceptions—they signal intent more clearly.
         | 
| 15 10 |  | 
| 16 11 | 
             
            ### Non-bang execution
         | 
| 17 12 |  | 
| 18 | 
            -
             | 
| 13 | 
            +
            Captures all exceptions and returns them as failed results:
         | 
| 19 14 |  | 
| 20 15 | 
             
            ```ruby
         | 
| 21 16 | 
             
            class CompressDocument < CMDx::Task
         | 
| @@ -33,9 +28,13 @@ result.reason   #=> "[ActiveRecord::NotFoundError] record not found" | |
| 33 28 | 
             
            result.cause    #=> <ActiveRecord::NotFoundError>
         | 
| 34 29 | 
             
            ```
         | 
| 35 30 |  | 
| 31 | 
            +
            !!! note
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                Use `exception_handler` with `execute` to send exceptions to APM tools before they become failed results.
         | 
| 34 | 
            +
             | 
| 36 35 | 
             
            ### Bang execution
         | 
| 37 36 |  | 
| 38 | 
            -
             | 
| 37 | 
            +
            Lets exceptions propagate naturally for standard Ruby error handling:
         | 
| 39 38 |  | 
| 40 39 | 
             
            ```ruby
         | 
| 41 40 | 
             
            class CompressDocument < CMDx::Task
         | 
| @@ -51,8 +50,3 @@ rescue ActiveRecord::NotFoundError => e | |
| 51 50 | 
             
              puts "Handle exception: #{e.message}"
         | 
| 52 51 | 
             
            end
         | 
| 53 52 | 
             
            ```
         | 
| 54 | 
            -
             | 
| 55 | 
            -
            ---
         | 
| 56 | 
            -
             | 
| 57 | 
            -
            - **Prev:** [Interruptions - Faults](faults.md)
         | 
| 58 | 
            -
            - **Next:** [Outcomes - Result](../outcomes/result.md)
         | 
| @@ -1,19 +1,6 @@ | |
| 1 1 | 
             
            # Interruptions - Faults
         | 
| 2 2 |  | 
| 3 | 
            -
            Faults are  | 
| 4 | 
            -
             | 
| 5 | 
            -
            ## Table of Contents
         | 
| 6 | 
            -
             | 
| 7 | 
            -
            - [Fault Types](#fault-types)
         | 
| 8 | 
            -
            - [Fault Handling](#fault-handling)
         | 
| 9 | 
            -
            - [Data Access](#data-access)
         | 
| 10 | 
            -
            - [Advanced Matching](#advanced-matching)
         | 
| 11 | 
            -
              - [Task-Specific Matching](#task-specific-matching)
         | 
| 12 | 
            -
              - [Custom Logic Matching](#custom-logic-matching)
         | 
| 13 | 
            -
            - [Fault Propagation](#fault-propagation)
         | 
| 14 | 
            -
              - [Basic Propagation](#basic-propagation)
         | 
| 15 | 
            -
              - [Additional Metadata](#additional-metadata)
         | 
| 16 | 
            -
            - [Chain Analysis](#chain-analysis)
         | 
| 3 | 
            +
            Faults are exceptions raised by `execute!` when tasks halt. They carry rich context about execution state, enabling sophisticated error handling patterns.
         | 
| 17 4 |  | 
| 18 5 | 
             
            ## Fault Types
         | 
| 19 6 |  | 
| @@ -23,8 +10,9 @@ Faults are exception mechanisms that halt task execution via `skip!` and `fail!` | |
| 23 10 | 
             
            | `CMDx::SkipFault` | `skip!` method | Optional processing, early returns |
         | 
| 24 11 | 
             
            | `CMDx::FailFault` | `fail!` method | Validation errors, processing failures |
         | 
| 25 12 |  | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 13 | 
            +
            !!! warning "Important"
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                All faults inherit from `CMDx::Fault` and expose result, task, context, and chain data.
         | 
| 28 16 |  | 
| 29 17 | 
             
            ## Fault Handling
         | 
| 30 18 |  | 
| @@ -45,7 +33,7 @@ end | |
| 45 33 |  | 
| 46 34 | 
             
            ## Data Access
         | 
| 47 35 |  | 
| 48 | 
            -
             | 
| 36 | 
            +
            Access rich execution data from fault exceptions:
         | 
| 49 37 |  | 
| 50 38 | 
             
            ```ruby
         | 
| 51 39 | 
             
            begin
         | 
| @@ -74,7 +62,7 @@ end | |
| 74 62 |  | 
| 75 63 | 
             
            ### Task-Specific Matching
         | 
| 76 64 |  | 
| 77 | 
            -
             | 
| 65 | 
            +
            Handle faults only from specific tasks using `for?`:
         | 
| 78 66 |  | 
| 79 67 | 
             
            ```ruby
         | 
| 80 68 | 
             
            begin
         | 
| @@ -104,7 +92,7 @@ end | |
| 104 92 |  | 
| 105 93 | 
             
            ## Fault Propagation
         | 
| 106 94 |  | 
| 107 | 
            -
             | 
| 95 | 
            +
            Propagate failures with `throw!` to preserve context and maintain the error chain:
         | 
| 108 96 |  | 
| 109 97 | 
             
            ### Basic Propagation
         | 
| 110 98 |  | 
| @@ -151,7 +139,7 @@ end | |
| 151 139 |  | 
| 152 140 | 
             
            ## Chain Analysis
         | 
| 153 141 |  | 
| 154 | 
            -
             | 
| 142 | 
            +
            Trace fault origins and propagation through the execution chain:
         | 
| 155 143 |  | 
| 156 144 | 
             
            ```ruby
         | 
| 157 145 | 
             
            result = DocumentWorkflow.execute(invalid_data)
         | 
| @@ -179,8 +167,3 @@ if result.failed? | |
| 179 167 | 
             
              end
         | 
| 180 168 | 
             
            end
         | 
| 181 169 | 
             
            ```
         | 
| 182 | 
            -
             | 
| 183 | 
            -
            ---
         | 
| 184 | 
            -
             | 
| 185 | 
            -
            - **Prev:** [Interruptions - Halt](halt.md)
         | 
| 186 | 
            -
            - **Next:** [Interruptions - Exceptions](exceptions.md)
         | 
    
        data/docs/interruptions/halt.md
    CHANGED
    
    | @@ -1,24 +1,14 @@ | |
| 1 1 | 
             
            # Interruptions - Halt
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
            ## Table of Contents
         | 
| 6 | 
            -
             | 
| 7 | 
            -
            - [Skipping](#skipping)
         | 
| 8 | 
            -
            - [Failing](#failing)
         | 
| 9 | 
            -
            - [Metadata Enrichment](#metadata-enrichment)
         | 
| 10 | 
            -
            - [State Transitions](#state-transitions)
         | 
| 11 | 
            -
            - [Execution Behavior](#execution-behavior)
         | 
| 12 | 
            -
              - [Non-bang execution](#non-bang-execution)
         | 
| 13 | 
            -
              - [Bang execution](#bang-execution)
         | 
| 14 | 
            -
            - [Best Practices](#best-practices)
         | 
| 3 | 
            +
            Stop task execution intentionally using `skip!` or `fail!`. Both methods signal clear intent about why execution stopped.
         | 
| 15 4 |  | 
| 16 5 | 
             
            ## Skipping
         | 
| 17 6 |  | 
| 18 | 
            -
            `skip!`  | 
| 7 | 
            +
            Use `skip!` when the task doesn't need to run. It's a no-op, not an error.
         | 
| 19 8 |  | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 9 | 
            +
            !!! warning "Important"
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                Skipped tasks are considered "good" outcomes—they succeeded by doing nothing.
         | 
| 22 12 |  | 
| 23 13 | 
             
            ```ruby
         | 
| 24 14 | 
             
            class ProcessInventory < CMDx::Task
         | 
| @@ -45,7 +35,7 @@ result = ProcessInventory.execute(inventory_id: 456) | |
| 45 35 | 
             
            result.status #=> "skipped"
         | 
| 46 36 |  | 
| 47 37 | 
             
            # Without a reason
         | 
| 48 | 
            -
            result.reason #=> " | 
| 38 | 
            +
            result.reason #=> "Unspecified"
         | 
| 49 39 |  | 
| 50 40 | 
             
            # With a reason
         | 
| 51 41 | 
             
            result.reason #=> "Warehouse closed"
         | 
| @@ -53,7 +43,7 @@ result.reason #=> "Warehouse closed" | |
| 53 43 |  | 
| 54 44 | 
             
            ## Failing
         | 
| 55 45 |  | 
| 56 | 
            -
            `fail!`  | 
| 46 | 
            +
            Use `fail!` when the task can't complete successfully. It signals controlled, intentional failure:
         | 
| 57 47 |  | 
| 58 48 | 
             
            ```ruby
         | 
| 59 49 | 
             
            class ProcessRefund < CMDx::Task
         | 
| @@ -80,7 +70,7 @@ result = ProcessRefund.execute(refund_id: 789) | |
| 80 70 | 
             
            result.status #=> "failed"
         | 
| 81 71 |  | 
| 82 72 | 
             
            # Without a reason
         | 
| 83 | 
            -
            result.reason #=> " | 
| 73 | 
            +
            result.reason #=> "Unspecified"
         | 
| 84 74 |  | 
| 85 75 | 
             
            # With a reason
         | 
| 86 76 | 
             
            result.reason #=> "Refund period has expired"
         | 
| @@ -88,7 +78,7 @@ result.reason #=> "Refund period has expired" | |
| 88 78 |  | 
| 89 79 | 
             
            ## Metadata Enrichment
         | 
| 90 80 |  | 
| 91 | 
            -
             | 
| 81 | 
            +
            Enrich halt calls with metadata for better debugging and error handling:
         | 
| 92 82 |  | 
| 93 83 | 
             
            ```ruby
         | 
| 94 84 | 
             
            class ProcessRenewal < CMDx::Task
         | 
| @@ -188,7 +178,7 @@ end | |
| 188 178 |  | 
| 189 179 | 
             
            ## Best Practices
         | 
| 190 180 |  | 
| 191 | 
            -
            Always  | 
| 181 | 
            +
            Always provide a reason for better debugging and clearer exception messages:
         | 
| 192 182 |  | 
| 193 183 | 
             
            ```ruby
         | 
| 194 184 | 
             
            # Good: Clear, specific reason
         | 
| @@ -200,11 +190,27 @@ skip!("Paused") | |
| 200 190 | 
             
            fail!("Unsupported")
         | 
| 201 191 |  | 
| 202 192 | 
             
            # Bad: Default, cannot determine reason
         | 
| 203 | 
            -
            skip! #=> " | 
| 204 | 
            -
            fail! #=> " | 
| 193 | 
            +
            skip! #=> "Unspecified"
         | 
| 194 | 
            +
            fail! #=> "Unspecified"
         | 
| 205 195 | 
             
            ```
         | 
| 206 196 |  | 
| 207 | 
            -
             | 
| 197 | 
            +
            ## Manual Errors
         | 
| 198 | 
            +
             | 
| 199 | 
            +
            For rare cases, manually add errors before halting:
         | 
| 208 200 |  | 
| 209 | 
            -
             | 
| 210 | 
            -
             | 
| 201 | 
            +
            !!! warning "Important"
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                Manual errors don't stop execution—you still need to call `fail!` or `skip!`.
         | 
| 204 | 
            +
             | 
| 205 | 
            +
            ```ruby
         | 
| 206 | 
            +
            class ProcessRenewal < CMDx::Task
         | 
| 207 | 
            +
              def work
         | 
| 208 | 
            +
                if document.nonrenewable?
         | 
| 209 | 
            +
                  errors.add(:document, "not renewable")
         | 
| 210 | 
            +
                  fail!("document could not be renewed")
         | 
| 211 | 
            +
                else
         | 
| 212 | 
            +
                  document.renew!
         | 
| 213 | 
            +
                end
         | 
| 214 | 
            +
              end
         | 
| 215 | 
            +
            end
         | 
| 216 | 
            +
            ```
         |