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.
- 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/yardoc.md +1 -0
- data/.irbrc +14 -2
- data/CHANGELOG.md +64 -45
- data/LLM.md +159 -53
- data/README.md +26 -83
- 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 +12 -24
- data/docs/attributes/defaults.md +3 -16
- data/docs/attributes/definitions.md +16 -30
- data/docs/attributes/naming.md +3 -13
- data/docs/attributes/transformations.md +63 -0
- data/docs/attributes/validations.md +14 -33
- 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 +109 -76
- data/docs/index.md +132 -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 +12 -27
- 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 +88 -6
- data/lib/cmdx/attribute_registry.rb +20 -0
- data/lib/cmdx/attribute_value.rb +56 -10
- data/lib/cmdx/callback_registry.rb +31 -2
- data/lib/cmdx/chain.rb +34 -1
- data/lib/cmdx/coercion_registry.rb +18 -0
- data/lib/cmdx/coercions/array.rb +2 -0
- data/lib/cmdx/coercions/big_decimal.rb +3 -0
- data/lib/cmdx/coercions/boolean.rb +5 -0
- data/lib/cmdx/coercions/complex.rb +2 -0
- data/lib/cmdx/coercions/date.rb +4 -0
- data/lib/cmdx/coercions/date_time.rb +5 -0
- data/lib/cmdx/coercions/float.rb +2 -0
- data/lib/cmdx/coercions/hash.rb +4 -0
- data/lib/cmdx/coercions/integer.rb +2 -0
- data/lib/cmdx/coercions/rational.rb +2 -0
- data/lib/cmdx/coercions/string.rb +2 -0
- data/lib/cmdx/coercions/symbol.rb +2 -0
- data/lib/cmdx/coercions/time.rb +5 -0
- data/lib/cmdx/configuration.rb +119 -3
- data/lib/cmdx/context.rb +36 -0
- data/lib/cmdx/deprecator.rb +6 -3
- data/lib/cmdx/errors.rb +22 -0
- data/lib/cmdx/executor.rb +136 -7
- data/lib/cmdx/faults.rb +14 -0
- data/lib/cmdx/identifier.rb +2 -0
- data/lib/cmdx/locale.rb +3 -0
- data/lib/cmdx/log_formatters/json.rb +2 -0
- data/lib/cmdx/log_formatters/key_value.rb +2 -0
- data/lib/cmdx/log_formatters/line.rb +2 -0
- data/lib/cmdx/log_formatters/logstash.rb +2 -0
- data/lib/cmdx/log_formatters/raw.rb +2 -0
- data/lib/cmdx/middleware_registry.rb +20 -0
- data/lib/cmdx/middlewares/correlate.rb +11 -0
- data/lib/cmdx/middlewares/runtime.rb +4 -0
- data/lib/cmdx/middlewares/timeout.rb +4 -0
- data/lib/cmdx/pipeline.rb +24 -5
- data/lib/cmdx/railtie.rb +13 -0
- data/lib/cmdx/result.rb +133 -2
- data/lib/cmdx/task.rb +103 -8
- data/lib/cmdx/utils/call.rb +2 -0
- data/lib/cmdx/utils/condition.rb +3 -0
- data/lib/cmdx/utils/format.rb +5 -0
- data/lib/cmdx/validator_registry.rb +18 -0
- data/lib/cmdx/validators/exclusion.rb +2 -0
- data/lib/cmdx/validators/format.rb +2 -0
- data/lib/cmdx/validators/inclusion.rb +2 -0
- data/lib/cmdx/validators/length.rb +14 -0
- data/lib/cmdx/validators/numeric.rb +14 -0
- data/lib/cmdx/validators/presence.rb +2 -0
- data/lib/cmdx/version.rb +4 -1
- data/lib/cmdx/workflow.rb +10 -0
- data/lib/cmdx.rb +9 -0
- data/lib/generators/cmdx/locale_generator.rb +0 -1
- data/lib/generators/cmdx/templates/install.rb +9 -0
- 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 +14 -3
- data/lib/cmdx/freezer.rb +0 -51
- data/src/cmdx-logo.png +0 -0
    
        data/docs/index.md
    ADDED
    
    | @@ -0,0 +1,132 @@ | |
| 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 | 
            +
            ---
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            Say goodbye to messy service objects. CMDx helps you design business logic with clarity and consistency—build faster, debug easier, and ship with confidence.
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            !!! note
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                Documentation reflects the latest code on `main`. For version-specific documentation, please refer to the `docs/` directory within that version's tag.
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            ## Requirements
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            - Ruby: MRI 3.1+ or JRuby 9.4+.
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            CMDx works with any Ruby framework. Rails support is built-in, but it's framework-agnostic at its core.
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            ## Installation
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            ```sh
         | 
| 26 | 
            +
            gem install cmdx
         | 
| 27 | 
            +
            # - or -
         | 
| 28 | 
            +
            bundle add cmdx
         | 
| 29 | 
            +
            ```
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            ## Quick Example
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            Build powerful business logic in four simple steps:
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            ### 1. Compose
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            === "Full Featured Task"
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                ```ruby
         | 
| 40 | 
            +
                class AnalyzeMetrics < CMDx::Task
         | 
| 41 | 
            +
                  register :middleware, CMDx::Middlewares::Correlate, id: -> { Current.request_id }
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  on_success :track_analysis_completion!
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  required :dataset_id, type: :integer, numeric: { min: 1 }
         | 
| 46 | 
            +
                  optional :analysis_type, default: "standard"
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def work
         | 
| 49 | 
            +
                    if dataset.nil?
         | 
| 50 | 
            +
                      fail!("Dataset not found", code: 404)
         | 
| 51 | 
            +
                    elsif dataset.unprocessed?
         | 
| 52 | 
            +
                      skip!("Dataset not ready for analysis")
         | 
| 53 | 
            +
                    else
         | 
| 54 | 
            +
                      context.result = PValueAnalyzer.execute(dataset:, analysis_type:)
         | 
| 55 | 
            +
                      context.analyzed_at = Time.now
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                      SendAnalyzedEmail.execute(user_id: Current.account.manager_id)
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  private
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  def dataset
         | 
| 64 | 
            +
                    @dataset ||= Dataset.find_by(id: dataset_id)
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  def track_analysis_completion!
         | 
| 68 | 
            +
                    dataset.update!(analysis_result_id: context.result.id)
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
                ```
         | 
| 72 | 
            +
             | 
| 73 | 
            +
            === "Minimum Viable Task"
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                ```ruby
         | 
| 76 | 
            +
                class SendAnalyzedEmail < CMDx::Task
         | 
| 77 | 
            +
                  def work
         | 
| 78 | 
            +
                    user = User.find(context.user_id)
         | 
| 79 | 
            +
                    MetricsMailer.analyzed(user).deliver_now
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
                ```
         | 
| 83 | 
            +
             | 
| 84 | 
            +
            ### 2. Execute
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            ```ruby
         | 
| 87 | 
            +
            result = AnalyzeMetrics.execute(
         | 
| 88 | 
            +
              dataset_id: 123,
         | 
| 89 | 
            +
              "analysis_type" => "advanced"
         | 
| 90 | 
            +
            )
         | 
| 91 | 
            +
            ```
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            ### 3. React
         | 
| 94 | 
            +
             | 
| 95 | 
            +
            ```ruby
         | 
| 96 | 
            +
            if result.success?
         | 
| 97 | 
            +
              puts "Metrics analyzed at #{result.context.analyzed_at}"
         | 
| 98 | 
            +
            elsif result.skipped?
         | 
| 99 | 
            +
              puts "Skipping analyzation due to: #{result.reason}"
         | 
| 100 | 
            +
            elsif result.failed?
         | 
| 101 | 
            +
              puts "Analyzation failed due to: #{result.reason} with code #{result.metadata[:code]}"
         | 
| 102 | 
            +
            end
         | 
| 103 | 
            +
            ```
         | 
| 104 | 
            +
             | 
| 105 | 
            +
            ### 4. Observe
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            ```log
         | 
| 108 | 
            +
            I, [2022-07-17T18:42:37.000000 #3784] INFO -- CMDx:
         | 
| 109 | 
            +
            index=1 chain_id="018c2b95-23j4-2kj3-32kj-3n4jk3n4jknf" type="Task" class="SendAnalyzedEmail" state="complete" status="success" metadata={runtime: 347}
         | 
| 110 | 
            +
             | 
| 111 | 
            +
            I, [2022-07-17T18:43:15.000000 #3784] INFO -- CMDx:
         | 
| 112 | 
            +
            index=0 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="AnalyzeMetrics" state="complete" status="success" metadata={runtime: 187}
         | 
| 113 | 
            +
            ```
         | 
| 114 | 
            +
             | 
| 115 | 
            +
            Ready to dive in? Check out the [Getting Started](getting_started.md) guide to learn more.
         | 
| 116 | 
            +
             | 
| 117 | 
            +
            ## Ecosystem
         | 
| 118 | 
            +
             | 
| 119 | 
            +
            - [cmdx-rspec](https://github.com/drexed/cmdx-rspec) - RSpec test matchers
         | 
| 120 | 
            +
             | 
| 121 | 
            +
            For backwards compatibility of certain functionality:
         | 
| 122 | 
            +
             | 
| 123 | 
            +
            - [cmdx-i18n](https://github.com/drexed/cmdx-i18n) - 85+ translations, `v1.5.0` - `v1.6.2`
         | 
| 124 | 
            +
            - [cmdx-parallel](https://github.com/drexed/cmdx-parallel) - Parallel workflow tasks, `v1.6.1` - `v1.6.2`
         | 
| 125 | 
            +
             | 
| 126 | 
            +
            ## Contributing
         | 
| 127 | 
            +
             | 
| 128 | 
            +
            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).
         | 
| 129 | 
            +
             | 
| 130 | 
            +
            ## License
         | 
| 131 | 
            +
             | 
| 132 | 
            +
            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,25 +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)
         | 
| 15 | 
            -
            - [Manual Errors](#manual-errors)
         | 
| 3 | 
            +
            Stop task execution intentionally using `skip!` or `fail!`. Both methods signal clear intent about why execution stopped.
         | 
| 16 4 |  | 
| 17 5 | 
             
            ## Skipping
         | 
| 18 6 |  | 
| 19 | 
            -
            `skip!`  | 
| 7 | 
            +
            Use `skip!` when the task doesn't need to run. It's a no-op, not an error.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            !!! warning "Important"
         | 
| 20 10 |  | 
| 21 | 
            -
             | 
| 22 | 
            -
            > Skipping is a no-op, not a failure or error and are considered successful outcomes.
         | 
| 11 | 
            +
                Skipped tasks are considered "good" outcomes—they succeeded by doing nothing.
         | 
| 23 12 |  | 
| 24 13 | 
             
            ```ruby
         | 
| 25 14 | 
             
            class ProcessInventory < CMDx::Task
         | 
| @@ -54,7 +43,7 @@ result.reason #=> "Warehouse closed" | |
| 54 43 |  | 
| 55 44 | 
             
            ## Failing
         | 
| 56 45 |  | 
| 57 | 
            -
            `fail!`  | 
| 46 | 
            +
            Use `fail!` when the task can't complete successfully. It signals controlled, intentional failure:
         | 
| 58 47 |  | 
| 59 48 | 
             
            ```ruby
         | 
| 60 49 | 
             
            class ProcessRefund < CMDx::Task
         | 
| @@ -89,7 +78,7 @@ result.reason #=> "Refund period has expired" | |
| 89 78 |  | 
| 90 79 | 
             
            ## Metadata Enrichment
         | 
| 91 80 |  | 
| 92 | 
            -
             | 
| 81 | 
            +
            Enrich halt calls with metadata for better debugging and error handling:
         | 
| 93 82 |  | 
| 94 83 | 
             
            ```ruby
         | 
| 95 84 | 
             
            class ProcessRenewal < CMDx::Task
         | 
| @@ -189,7 +178,7 @@ end | |
| 189 178 |  | 
| 190 179 | 
             
            ## Best Practices
         | 
| 191 180 |  | 
| 192 | 
            -
            Always  | 
| 181 | 
            +
            Always provide a reason for better debugging and clearer exception messages:
         | 
| 193 182 |  | 
| 194 183 | 
             
            ```ruby
         | 
| 195 184 | 
             
            # Good: Clear, specific reason
         | 
| @@ -207,10 +196,11 @@ fail! #=> "Unspecified" | |
| 207 196 |  | 
| 208 197 | 
             
            ## Manual Errors
         | 
| 209 198 |  | 
| 210 | 
            -
             | 
| 199 | 
            +
            For rare cases, manually add errors before halting:
         | 
| 211 200 |  | 
| 212 | 
            -
             | 
| 213 | 
            -
             | 
| 201 | 
            +
            !!! warning "Important"
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                Manual errors don't stop execution—you still need to call `fail!` or `skip!`.
         | 
| 214 204 |  | 
| 215 205 | 
             
            ```ruby
         | 
| 216 206 | 
             
            class ProcessRenewal < CMDx::Task
         | 
| @@ -224,8 +214,3 @@ class ProcessRenewal < CMDx::Task | |
| 224 214 | 
             
              end
         | 
| 225 215 | 
             
            end
         | 
| 226 216 | 
             
            ```
         | 
| 227 | 
            -
             | 
| 228 | 
            -
            ---
         | 
| 229 | 
            -
             | 
| 230 | 
            -
            - **Prev:** [Basics - Chain](../basics/chain.md)
         | 
| 231 | 
            -
            - **Next:** [Interruptions - Faults](faults.md)
         | 
    
        data/docs/logging.md
    CHANGED
    
    | @@ -1,16 +1,10 @@ | |
| 1 1 | 
             
            # Logging
         | 
| 2 2 |  | 
| 3 | 
            -
            CMDx  | 
| 4 | 
            -
             | 
| 5 | 
            -
            ## Table of Contents
         | 
| 6 | 
            -
             | 
| 7 | 
            -
            - [Formatters](#formatters)
         | 
| 8 | 
            -
            - [Structure](#structure)
         | 
| 9 | 
            -
            - [Usage](#usage)
         | 
| 3 | 
            +
            CMDx automatically logs every task execution with structured data, making debugging and monitoring effortless. Choose from multiple formatters to match your logging infrastructure.
         | 
| 10 4 |  | 
| 11 5 | 
             
            ## Formatters
         | 
| 12 6 |  | 
| 13 | 
            -
             | 
| 7 | 
            +
            Choose the format that works best for your logging system:
         | 
| 14 8 |  | 
| 15 9 | 
             
            | Formatter | Use Case | Output Style |
         | 
| 16 10 | 
             
            |-----------|----------|--------------|
         | 
| @@ -40,12 +34,13 @@ E, [2022-07-17T18:43:15.000000 #3784] ERROR -- BillingWorkflow: | |
| 40 34 | 
             
            index=3 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="BillingWorkflow"  state="interrupted" status="failed" caused_failure={index: 2, class: "CalculateTax", status: "failed"} threw_failure={index: 1, class: "ValidateCustomer", status: "failed"}
         | 
| 41 35 | 
             
            ```
         | 
| 42 36 |  | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 37 | 
            +
            !!! tip
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                Use logging as a low-level event stream to track all tasks in a request. Combine with correlation for powerful distributed tracing.
         | 
| 45 40 |  | 
| 46 41 | 
             
            ## Structure
         | 
| 47 42 |  | 
| 48 | 
            -
             | 
| 43 | 
            +
            Every log entry includes rich metadata. Available fields depend on execution context and outcome.
         | 
| 49 44 |  | 
| 50 45 | 
             
            ### Core Fields
         | 
| 51 46 |  | 
| @@ -86,7 +81,7 @@ All log entries include comprehensive execution metadata. Field availability dep | |
| 86 81 |  | 
| 87 82 | 
             
            ## Usage
         | 
| 88 83 |  | 
| 89 | 
            -
             | 
| 84 | 
            +
            Access the framework logger directly within tasks:
         | 
| 90 85 |  | 
| 91 86 | 
             
            ```ruby
         | 
| 92 87 | 
             
            class ProcessSubscription < CMDx::Task
         | 
| @@ -97,8 +92,3 @@ class ProcessSubscription < CMDx::Task | |
| 97 92 | 
             
              end
         | 
| 98 93 | 
             
            end
         | 
| 99 94 | 
             
            ```
         | 
| 100 | 
            -
             | 
| 101 | 
            -
            ---
         | 
| 102 | 
            -
             | 
| 103 | 
            -
            - **Prev:** [Middlewares](middlewares.md)
         | 
| 104 | 
            -
            - **Next:** [Internationalization (i18n)](internationalization.md)
         | 
    
        data/docs/middlewares.md
    CHANGED
    
    | @@ -1,27 +1,16 @@ | |
| 1 1 | 
             
            # Middlewares
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 3 | 
            +
            Wrap task execution with middleware for cross-cutting concerns like authentication, caching, timeouts, and monitoring. Think Rack middleware, but for your business logic.
         | 
| 4 4 |  | 
| 5 | 
            -
             | 
| 5 | 
            +
            See [Global Configuration](getting_started.md#middlewares) for framework-wide setup.
         | 
| 6 6 |  | 
| 7 | 
            -
            ##  | 
| 7 | 
            +
            ## Execution Order
         | 
| 8 8 |  | 
| 9 | 
            -
             | 
| 10 | 
            -
            - [Declarations](#declarations)
         | 
| 11 | 
            -
              - [Proc or Lambda](#proc-or-lambda)
         | 
| 12 | 
            -
              - [Class or Module](#class-or-module)
         | 
| 13 | 
            -
            - [Removals](#removals)
         | 
| 14 | 
            -
            - [Built-in](#built-in)
         | 
| 15 | 
            -
              - [Timeout](#timeout)
         | 
| 16 | 
            -
              - [Correlate](#correlate)
         | 
| 17 | 
            -
              - [Runtime](#runtime)
         | 
| 9 | 
            +
            Middleware wraps task execution in layers, like an onion:
         | 
| 18 10 |  | 
| 19 | 
            -
             | 
| 11 | 
            +
            !!! note
         | 
| 20 12 |  | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
            > [!NOTE]
         | 
| 24 | 
            -
            > Middleware executes in the order they are registered, with the first registered middleware being the outermost wrapper.
         | 
| 13 | 
            +
                First registered = outermost wrapper. They execute in registration order.
         | 
| 25 14 |  | 
| 26 15 | 
             
            ```ruby
         | 
| 27 16 | 
             
            class ProcessCampaign < CMDx::Task
         | 
| @@ -97,10 +86,11 @@ end | |
| 97 86 |  | 
| 98 87 | 
             
            ## Removals
         | 
| 99 88 |  | 
| 100 | 
            -
             | 
| 89 | 
            +
            Remove class or module-based middleware globally or per-task:
         | 
| 90 | 
            +
             | 
| 91 | 
            +
            !!! warning
         | 
| 101 92 |  | 
| 102 | 
            -
             | 
| 103 | 
            -
            > Only one removal operation is allowed per `deregister` call. Multiple removals require separate calls.
         | 
| 93 | 
            +
                Each `deregister` call removes one middleware. Use multiple calls for batch removals.
         | 
| 104 94 |  | 
| 105 95 | 
             
            ```ruby
         | 
| 106 96 | 
             
            class ProcessCampaign < CMDx::Task
         | 
| @@ -113,7 +103,7 @@ end | |
| 113 103 |  | 
| 114 104 | 
             
            ### Timeout
         | 
| 115 105 |  | 
| 116 | 
            -
             | 
| 106 | 
            +
            Prevent tasks from running too long:
         | 
| 117 107 |  | 
| 118 108 | 
             
            ```ruby
         | 
| 119 109 | 
             
            class ProcessReport < CMDx::Task
         | 
| @@ -149,7 +139,7 @@ result.metadata #=> { limit: 3 } | |
| 149 139 |  | 
| 150 140 | 
             
            ### Correlate
         | 
| 151 141 |  | 
| 152 | 
            -
             | 
| 142 | 
            +
            Add correlation IDs for distributed tracing and request tracking:
         | 
| 153 143 |  | 
| 154 144 | 
             
            ```ruby
         | 
| 155 145 | 
             
            class ProcessExport < CMDx::Task
         | 
| @@ -179,8 +169,7 @@ result.metadata #=> { correlation_id: "550e8400-e29b-41d4-a716-446655440000" } | |
| 179 169 |  | 
| 180 170 | 
             
            ### Runtime
         | 
| 181 171 |  | 
| 182 | 
            -
             | 
| 183 | 
            -
            The calculation uses a monotonic clock and the time is returned in milliseconds.
         | 
| 172 | 
            +
            Track task execution time in milliseconds using a monotonic clock:
         | 
| 184 173 |  | 
| 185 174 | 
             
            ```ruby
         | 
| 186 175 | 
             
            class PerformanceMonitoringCheck
         | 
| @@ -200,8 +189,3 @@ end | |
| 200 189 | 
             
            result = ProcessExport.execute
         | 
| 201 190 | 
             
            result.metadata #=> { runtime: 1247 } (ms)
         | 
| 202 191 | 
             
            ```
         | 
| 203 | 
            -
             | 
| 204 | 
            -
            ---
         | 
| 205 | 
            -
             | 
| 206 | 
            -
            - **Prev:** [Callbacks](callbacks.md)
         | 
| 207 | 
            -
            - **Next:** [Logging](logging.md)
         | 
    
        data/docs/outcomes/result.md
    CHANGED
    
    | @@ -1,27 +1,14 @@ | |
| 1 1 | 
             
            # Outcomes - Result
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
            ## Table of Contents
         | 
| 6 | 
            -
             | 
| 7 | 
            -
            - [Result Attributes](#result-attributes)
         | 
| 8 | 
            -
            - [Lifecycle Information](#lifecycle-information)
         | 
| 9 | 
            -
            - [Outcome Analysis](#outcome-analysis)
         | 
| 10 | 
            -
            - [Chain Analysis](#chain-analysis)
         | 
| 11 | 
            -
            - [Index and Position](#index-and-position)
         | 
| 12 | 
            -
            - [Block Yield](#block-yield)
         | 
| 13 | 
            -
            - [Handlers](#handlers)
         | 
| 14 | 
            -
            - [Pattern Matching](#pattern-matching)
         | 
| 15 | 
            -
              - [Array Pattern](#array-pattern)
         | 
| 16 | 
            -
              - [Hash Pattern](#hash-pattern)
         | 
| 17 | 
            -
              - [Pattern Guards](#pattern-guards)
         | 
| 3 | 
            +
            Results are your window into task execution. They expose everything: outcome, state, timing, context, and metadata.
         | 
| 18 4 |  | 
| 19 5 | 
             
            ## Result Attributes
         | 
| 20 6 |  | 
| 21 | 
            -
             | 
| 7 | 
            +
            Access essential execution information:
         | 
| 22 8 |  | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 9 | 
            +
            !!! warning "Important"
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                Results are immutable after execution completes.
         | 
| 25 12 |  | 
| 26 13 | 
             
            ```ruby
         | 
| 27 14 | 
             
            result = BuildApplication.execute(version: "1.2.3")
         | 
| @@ -43,7 +30,7 @@ result.metadata #=> { error_code: "BUILD_TOOL.NOT_FOUND" } | |
| 43 30 |  | 
| 44 31 | 
             
            ## Lifecycle Information
         | 
| 45 32 |  | 
| 46 | 
            -
             | 
| 33 | 
            +
            Check execution state and status with predicate methods:
         | 
| 47 34 |  | 
| 48 35 | 
             
            ```ruby
         | 
| 49 36 | 
             
            result = BuildApplication.execute(version: "1.2.3")
         | 
| @@ -65,7 +52,7 @@ result.bad?         #=> false (skipped or failed) | |
| 65 52 |  | 
| 66 53 | 
             
            ## Outcome Analysis
         | 
| 67 54 |  | 
| 68 | 
            -
             | 
| 55 | 
            +
            Get a unified outcome string combining state and status:
         | 
| 69 56 |  | 
| 70 57 | 
             
            ```ruby
         | 
| 71 58 | 
             
            result = BuildApplication.execute(version: "1.2.3")
         | 
| @@ -75,7 +62,7 @@ result.outcome #=> "success" (state and status) | |
| 75 62 |  | 
| 76 63 | 
             
            ## Chain Analysis
         | 
| 77 64 |  | 
| 78 | 
            -
             | 
| 65 | 
            +
            Trace fault origins and propagation:
         | 
| 79 66 |  | 
| 80 67 | 
             
            ```ruby
         | 
| 81 68 | 
             
            result = DeploymentWorkflow.execute(app_name: "webapp")
         | 
| @@ -116,7 +103,7 @@ result.chain.results[result.index] == result #=> true | |
| 116 103 |  | 
| 117 104 | 
             
            ## Block Yield
         | 
| 118 105 |  | 
| 119 | 
            -
             | 
| 106 | 
            +
            Execute code with direct result access:
         | 
| 120 107 |  | 
| 121 108 | 
             
            ```ruby
         | 
| 122 109 | 
             
            BuildApplication.execute(version: "1.2.3") do |result|
         | 
| @@ -132,34 +119,35 @@ end | |
| 132 119 |  | 
| 133 120 | 
             
            ## Handlers
         | 
| 134 121 |  | 
| 135 | 
            -
             | 
| 122 | 
            +
            Handle outcomes with functional-style methods. Handlers return the result for chaining:
         | 
| 136 123 |  | 
| 137 124 | 
             
            ```ruby
         | 
| 138 125 | 
             
            result = BuildApplication.execute(version: "1.2.3")
         | 
| 139 126 |  | 
| 140 127 | 
             
            # Status-based handlers
         | 
| 141 128 | 
             
            result
         | 
| 142 | 
            -
              . | 
| 143 | 
            -
              . | 
| 144 | 
            -
              . | 
| 129 | 
            +
              .handle_success { |result| notify_deployment_ready(result) }
         | 
| 130 | 
            +
              .handle_failed { |result| handle_build_failure(result) }
         | 
| 131 | 
            +
              .handle_skipped { |result| log_skip_reason(result) }
         | 
| 145 132 |  | 
| 146 133 | 
             
            # State-based handlers
         | 
| 147 134 | 
             
            result
         | 
| 148 | 
            -
              . | 
| 149 | 
            -
              . | 
| 135 | 
            +
              .handle_complete { |result| update_build_status(result) }
         | 
| 136 | 
            +
              .handle_interrupted { |result| cleanup_partial_artifacts(result) }
         | 
| 150 137 |  | 
| 151 138 | 
             
            # Outcome-based handlers
         | 
| 152 139 | 
             
            result
         | 
| 153 | 
            -
              . | 
| 154 | 
            -
              . | 
| 140 | 
            +
              .handle_good { |result| increment_success_counter(result) }
         | 
| 141 | 
            +
              .handle_bad { |result| alert_operations_team(result) }
         | 
| 155 142 | 
             
            ```
         | 
| 156 143 |  | 
| 157 144 | 
             
            ## Pattern Matching
         | 
| 158 145 |  | 
| 159 | 
            -
             | 
| 146 | 
            +
            Use Ruby 3.0+ pattern matching for elegant outcome handling:
         | 
| 147 | 
            +
             | 
| 148 | 
            +
            !!! warning "Important"
         | 
| 160 149 |  | 
| 161 | 
            -
             | 
| 162 | 
            -
            > Pattern matching requires Ruby 3.0+
         | 
| 150 | 
            +
                Pattern matching works with both array and hash deconstruction.
         | 
| 163 151 |  | 
| 164 152 | 
             
            ### Array Pattern
         | 
| 165 153 |  | 
| @@ -203,8 +191,3 @@ in { runtime: time } if time > performance_threshold | |
| 203 191 | 
             
              investigate_build_performance(result)
         | 
| 204 192 | 
             
            end
         | 
| 205 193 | 
             
            ```
         | 
| 206 | 
            -
             | 
| 207 | 
            -
            ---
         | 
| 208 | 
            -
             | 
| 209 | 
            -
            - **Prev:** [Interruptions - Exceptions](../interruptions/exceptions.md)
         | 
| 210 | 
            -
            - **Next:** [Outcomes - States](states.md)
         |