cmdx 1.9.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/.cursor/prompts/yardoc.md +1 -0
- data/CHANGELOG.md +6 -0
- data/LLM.md +9 -0
- data/README.md +6 -1
- data/docs/getting_started.md +9 -0
- data/docs/index.md +13 -1
- data/lib/cmdx/attribute.rb +82 -1
- data/lib/cmdx/attribute_registry.rb +20 -0
- data/lib/cmdx/attribute_value.rb +25 -0
- data/lib/cmdx/callback_registry.rb +19 -0
- 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 +2 -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 +111 -3
- data/lib/cmdx/context.rb +36 -0
- data/lib/cmdx/deprecator.rb +3 -0
- data/lib/cmdx/errors.rb +22 -0
- data/lib/cmdx/executor.rb +43 -0
- 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 +20 -1
- data/lib/cmdx/railtie.rb +4 -0
- data/lib/cmdx/result.rb +123 -1
- data/lib/cmdx/task.rb +91 -1
- 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 +8 -0
- data/lib/generators/cmdx/locale_generator.rb +0 -1
- metadata +1 -1
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 9220688eed061c4c48562665fe9c2c780a2ecc08642eeaedb095d6bfc312e643
         | 
| 4 | 
            +
              data.tar.gz: f470f2ccebce524942277865f2967979244dde07ba1a43095c7f95209127c635
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 155e2d09a4ca6147a7df578b0f2952ab81e781d5c492d821a4b71b08b41ef510cfccade001cc2e593f08ea1876ba6e5b658595a2bddaf6919f243c8df07d0569
         | 
| 7 | 
            +
              data.tar.gz: fd0dba94495d6d139dc13507169143ee83f13dfac521d6ef607800c6d6e4cfd13e47cc03e2f27e047b185a0b9ebfc0a9d080143f7c6c2ec61aec139f38ea17cd
         | 
    
        data/.cursor/prompts/yardoc.md
    CHANGED
    
    | @@ -12,3 +12,4 @@ Add yardoc to the active tab using the following guidelines: | |
| 12 12 | 
             
            - Method level docs should include `@example`, `param`, `@options`, `@return`, and any `@raise`
         | 
| 13 13 | 
             
            - Hash `@params` should expand with possible `@option`
         | 
| 14 14 | 
             
            - Module and method level docs should NOT include `@since`
         | 
| 15 | 
            +
            - Add RBS inline comments after YARDoc block
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), | |
| 6 6 |  | 
| 7 7 | 
             
            ## [UNRELEASED]
         | 
| 8 8 |  | 
| 9 | 
            +
            ## [1.9.1] - 2025-10-22
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            ### Added
         | 
| 12 | 
            +
            - Added RBS inlines type signatures
         | 
| 13 | 
            +
            - Added YARDocs for `attr_reader` and `attr_accessor` methods
         | 
| 14 | 
            +
             | 
| 9 15 | 
             
            ## [1.9.0] - 2025-10-21
         | 
| 10 16 |  | 
| 11 17 | 
             
            ### Added
         | 
    
        data/LLM.md
    CHANGED
    
    | @@ -372,6 +372,15 @@ end | |
| 372 372 | 
             
            > [!TIP]
         | 
| 373 373 | 
             
            > Use **present tense verbs + noun** for task names, eg: `ModerateBlogPost`, `ScheduleAppointment`, `ValidateDocument`
         | 
| 374 374 |  | 
| 375 | 
            +
            ## Type safety
         | 
| 376 | 
            +
             | 
| 377 | 
            +
            CMDx includes built-in RBS (Ruby Type Signature) inline annotations throughout the codebase, providing type information for static analysis and editor support.
         | 
| 378 | 
            +
             | 
| 379 | 
            +
            - **Type checking** — Catch type errors before runtime using tools like Steep or TypeProf
         | 
| 380 | 
            +
            - **Better IDE support** — Enhanced autocomplete, navigation, and inline documentation
         | 
| 381 | 
            +
            - **Self-documenting code** — Clear method signatures and return types
         | 
| 382 | 
            +
            - **Refactoring confidence** — Type-aware refactoring reduces bugs
         | 
| 383 | 
            +
             | 
| 375 384 | 
             
            ---
         | 
| 376 385 |  | 
| 377 386 | 
             
            url: https://github.com/drexed/cmdx/blob/main/docs/basics/setup.md
         | 
    
        data/README.md
    CHANGED
    
    | @@ -17,6 +17,9 @@ | |
| 17 17 |  | 
| 18 18 | 
             
            Say goodbye to messy service objects. CMDx helps you design business logic with clarity and consistency—build faster, debug easier, and ship with confidence.
         | 
| 19 19 |  | 
| 20 | 
            +
            > [!NOTE]
         | 
| 21 | 
            +
            > Documentation reflects the latest code on `main`. For version-specific documentation, please refer to the `docs/` directory within that version's tag.
         | 
| 22 | 
            +
             | 
| 20 23 | 
             
            ## Requirements
         | 
| 21 24 |  | 
| 22 25 | 
             
            - Ruby: MRI 3.1+ or JRuby 9.4+.
         | 
| @@ -105,6 +108,8 @@ I, [2022-07-17T18:43:15.000000 #3784] INFO -- CMDx: | |
| 105 108 | 
             
            index=0 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="AnalyzeMetrics" state="complete" status="success" metadata={runtime: 187}
         | 
| 106 109 | 
             
            ```
         | 
| 107 110 |  | 
| 111 | 
            +
            Ready to dive in? Check out the [Getting Started](https://drexed.github.io/cmdx/getting_started/) guide to learn more.
         | 
| 112 | 
            +
             | 
| 108 113 | 
             
            ## Ecosystem
         | 
| 109 114 |  | 
| 110 115 | 
             
            - [cmdx-rspec](https://github.com/drexed/cmdx-rspec) - RSpec test matchers
         | 
| @@ -116,7 +121,7 @@ For backwards compatibility of certain functionality: | |
| 116 121 |  | 
| 117 122 | 
             
            ## Contributing
         | 
| 118 123 |  | 
| 119 | 
            -
            Bug reports and pull requests are welcome at https://github.com/drexed/cmdx | 
| 124 | 
            +
            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).
         | 
| 120 125 |  | 
| 121 126 | 
             
            ## License
         | 
| 122 127 |  | 
    
        data/docs/getting_started.md
    CHANGED
    
    | @@ -367,3 +367,12 @@ end | |
| 367 367 | 
             
            !!! tip
         | 
| 368 368 |  | 
| 369 369 | 
             
                Use **present tense verbs + noun** for task names, eg: `ModerateBlogPost`, `ScheduleAppointment`, `ValidateDocument`
         | 
| 370 | 
            +
             | 
| 371 | 
            +
            ## Type safety
         | 
| 372 | 
            +
             | 
| 373 | 
            +
            CMDx includes built-in RBS (Ruby Type Signature) inline annotations throughout the codebase, providing type information for static analysis and editor support.
         | 
| 374 | 
            +
             | 
| 375 | 
            +
            - **Type checking** — Catch type errors before runtime using tools like Steep or TypeProf
         | 
| 376 | 
            +
            - **Better IDE support** — Enhanced autocomplete, navigation, and inline documentation
         | 
| 377 | 
            +
            - **Self-documenting code** — Clear method signatures and return types
         | 
| 378 | 
            +
            - **Refactoring confidence** — Type-aware refactoring reduces bugs
         | 
    
        data/docs/index.md
    CHANGED
    
    | @@ -6,8 +6,20 @@ Build business logic that's powerful, predictable, and maintainable. | |
| 6 6 | 
             
            [](https://github.com/drexed/cmdx/actions/workflows/ci.yml)
         | 
| 7 7 | 
             
            [](https://github.com/drexed/cmdx/blob/main/LICENSE.txt)
         | 
| 8 8 |  | 
| 9 | 
            +
            ---
         | 
| 10 | 
            +
             | 
| 9 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.
         | 
| 10 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 | 
            +
             | 
| 11 23 | 
             
            ## Installation
         | 
| 12 24 |  | 
| 13 25 | 
             
            ```sh
         | 
| @@ -113,7 +125,7 @@ For backwards compatibility of certain functionality: | |
| 113 125 |  | 
| 114 126 | 
             
            ## Contributing
         | 
| 115 127 |  | 
| 116 | 
            -
            Bug reports and pull requests are welcome at https://github.com/drexed/cmdx | 
| 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).
         | 
| 117 129 |  | 
| 118 130 | 
             
            ## License
         | 
| 119 131 |  | 
    
        data/lib/cmdx/attribute.rb
    CHANGED
    
    | @@ -6,14 +6,71 @@ module CMDx | |
| 6 6 | 
             
              # They can be nested to create complex hierarchical data structures.
         | 
| 7 7 | 
             
              class Attribute
         | 
| 8 8 |  | 
| 9 | 
            +
                # @rbs AFFIX: Proc
         | 
| 9 10 | 
             
                AFFIX = proc do |value, &block|
         | 
| 10 11 | 
             
                  value == true ? block.call : value
         | 
| 11 12 | 
             
                end.freeze
         | 
| 12 13 | 
             
                private_constant :AFFIX
         | 
| 13 14 |  | 
| 15 | 
            +
                # Returns the task instance associated with this attribute.
         | 
| 16 | 
            +
                #
         | 
| 17 | 
            +
                # @return [CMDx::Task] The task instance
         | 
| 18 | 
            +
                #
         | 
| 19 | 
            +
                # @example
         | 
| 20 | 
            +
                #   attribute.task.context[:user_id] # => 42
         | 
| 21 | 
            +
                #
         | 
| 22 | 
            +
                # @rbs @task: Task
         | 
| 14 23 | 
             
                attr_accessor :task
         | 
| 15 24 |  | 
| 16 | 
            -
                 | 
| 25 | 
            +
                # Returns the name of this attribute.
         | 
| 26 | 
            +
                #
         | 
| 27 | 
            +
                # @return [Symbol] The attribute name
         | 
| 28 | 
            +
                #
         | 
| 29 | 
            +
                # @example
         | 
| 30 | 
            +
                #   attribute.name # => :user_id
         | 
| 31 | 
            +
                #
         | 
| 32 | 
            +
                # @rbs @name: Symbol
         | 
| 33 | 
            +
                attr_reader :name
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                # Returns the configuration options for this attribute.
         | 
| 36 | 
            +
                #
         | 
| 37 | 
            +
                # @return [Hash{Symbol => Object}] Configuration options hash
         | 
| 38 | 
            +
                #
         | 
| 39 | 
            +
                # @example
         | 
| 40 | 
            +
                #   attribute.options # => { required: true, default: 0 }
         | 
| 41 | 
            +
                #
         | 
| 42 | 
            +
                # @rbs @options: Hash[Symbol, untyped]
         | 
| 43 | 
            +
                attr_reader :options
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                # Returns the child attributes for nested structures.
         | 
| 46 | 
            +
                #
         | 
| 47 | 
            +
                # @return [Array<Attribute>] Array of child attributes
         | 
| 48 | 
            +
                #
         | 
| 49 | 
            +
                # @example
         | 
| 50 | 
            +
                #   attribute.children # => [#<Attribute @name=:street>, #<Attribute @name=:city>]
         | 
| 51 | 
            +
                #
         | 
| 52 | 
            +
                # @rbs @children: Array[Attribute]
         | 
| 53 | 
            +
                attr_reader :children
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                # Returns the parent attribute if this is a nested attribute.
         | 
| 56 | 
            +
                #
         | 
| 57 | 
            +
                # @return [Attribute, nil] The parent attribute, or nil if root-level
         | 
| 58 | 
            +
                #
         | 
| 59 | 
            +
                # @example
         | 
| 60 | 
            +
                #   attribute.parent # => #<Attribute @name=:address>
         | 
| 61 | 
            +
                #
         | 
| 62 | 
            +
                # @rbs @parent: (Attribute | nil)
         | 
| 63 | 
            +
                attr_reader :parent
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                # Returns the expected type(s) for this attribute's value.
         | 
| 66 | 
            +
                #
         | 
| 67 | 
            +
                # @return [Array<Class>] Array of expected type classes
         | 
| 68 | 
            +
                #
         | 
| 69 | 
            +
                # @example
         | 
| 70 | 
            +
                #   attribute.types # => [Integer, String]
         | 
| 71 | 
            +
                #
         | 
| 72 | 
            +
                # @rbs @types: Array[Class]
         | 
| 73 | 
            +
                attr_reader :types
         | 
| 17 74 |  | 
| 18 75 | 
             
                # Creates a new attribute with the specified name and configuration.
         | 
| 19 76 | 
             
                #
         | 
| @@ -35,6 +92,8 @@ module CMDx | |
| 35 92 | 
             
                #     required :name, types: String
         | 
| 36 93 | 
             
                #     optional :email, types: String
         | 
| 37 94 | 
             
                #   end
         | 
| 95 | 
            +
                #
         | 
| 96 | 
            +
                # @rbs ((Symbol | String) name, ?Hash[Symbol, untyped] options) ?{ () -> void } -> void
         | 
| 38 97 | 
             
                def initialize(name, options = {}, &)
         | 
| 39 98 | 
             
                  @parent = options.delete(:parent)
         | 
| 40 99 | 
             
                  @required = options.delete(:required) || false
         | 
| @@ -62,6 +121,8 @@ module CMDx | |
| 62 121 | 
             
                  #
         | 
| 63 122 | 
             
                  # @example
         | 
| 64 123 | 
             
                  #   Attribute.build(:first_name, :last_name, required: true, types: String)
         | 
| 124 | 
            +
                  #
         | 
| 125 | 
            +
                  # @rbs (*untyped names, **untyped options) ?{ () -> void } -> Array[Attribute]
         | 
| 65 126 | 
             
                  def build(*names, **options, &)
         | 
| 66 127 | 
             
                    if names.none?
         | 
| 67 128 | 
             
                      raise ArgumentError, "no attributes given"
         | 
| @@ -83,6 +144,8 @@ module CMDx | |
| 83 144 | 
             
                  #
         | 
| 84 145 | 
             
                  # @example
         | 
| 85 146 | 
             
                  #   Attribute.optional(:description, :tags, types: String)
         | 
| 147 | 
            +
                  #
         | 
| 148 | 
            +
                  # @rbs (*untyped names, **untyped options) ?{ () -> void } -> Array[Attribute]
         | 
| 86 149 | 
             
                  def optional(*names, **options, &)
         | 
| 87 150 | 
             
                    build(*names, **options.merge(required: false), &)
         | 
| 88 151 | 
             
                  end
         | 
| @@ -98,6 +161,8 @@ module CMDx | |
| 98 161 | 
             
                  #
         | 
| 99 162 | 
             
                  # @example
         | 
| 100 163 | 
             
                  #   Attribute.required(:id, :name, types: [Integer, String])
         | 
| 164 | 
            +
                  #
         | 
| 165 | 
            +
                  # @rbs (*untyped names, **untyped options) ?{ () -> void } -> Array[Attribute]
         | 
| 101 166 | 
             
                  def required(*names, **options, &)
         | 
| 102 167 | 
             
                    build(*names, **options.merge(required: true), &)
         | 
| 103 168 | 
             
                  end
         | 
| @@ -110,6 +175,8 @@ module CMDx | |
| 110 175 | 
             
                #
         | 
| 111 176 | 
             
                # @example
         | 
| 112 177 | 
             
                #   attribute.required? # => true
         | 
| 178 | 
            +
                #
         | 
| 179 | 
            +
                # @rbs () -> bool
         | 
| 113 180 | 
             
                def required?
         | 
| 114 181 | 
             
                  !!@required
         | 
| 115 182 | 
             
                end
         | 
| @@ -120,6 +187,8 @@ module CMDx | |
| 120 187 | 
             
                #
         | 
| 121 188 | 
             
                # @example
         | 
| 122 189 | 
             
                #   attribute.source # => :context
         | 
| 190 | 
            +
                #
         | 
| 191 | 
            +
                # @rbs () -> untyped
         | 
| 123 192 | 
             
                def source
         | 
| 124 193 | 
             
                  @source ||= parent&.method_name || begin
         | 
| 125 194 | 
             
                    value = options[:source]
         | 
| @@ -140,6 +209,8 @@ module CMDx | |
| 140 209 | 
             
                #
         | 
| 141 210 | 
             
                # @example
         | 
| 142 211 | 
             
                #   attribute.method_name # => :user_name
         | 
| 212 | 
            +
                #
         | 
| 213 | 
            +
                # @rbs () -> Symbol
         | 
| 143 214 | 
             
                def method_name
         | 
| 144 215 | 
             
                  @method_name ||= options[:as] || begin
         | 
| 145 216 | 
             
                    prefix = AFFIX.call(options[:prefix]) { "#{source}_" }
         | 
| @@ -150,6 +221,8 @@ module CMDx | |
| 150 221 | 
             
                end
         | 
| 151 222 |  | 
| 152 223 | 
             
                # Defines and verifies the entire attribute tree including nested children.
         | 
| 224 | 
            +
                #
         | 
| 225 | 
            +
                # @rbs () -> void
         | 
| 153 226 | 
             
                def define_and_verify_tree
         | 
| 154 227 | 
             
                  define_and_verify
         | 
| 155 228 |  | 
| @@ -172,6 +245,8 @@ module CMDx | |
| 172 245 | 
             
                #
         | 
| 173 246 | 
             
                # @example
         | 
| 174 247 | 
             
                #   attributes :street, :city, :zip, types: String
         | 
| 248 | 
            +
                #
         | 
| 249 | 
            +
                # @rbs (*untyped names, **untyped options) ?{ () -> void } -> Array[Attribute]
         | 
| 175 250 | 
             
                def attributes(*names, **options, &)
         | 
| 176 251 | 
             
                  attrs = self.class.build(*names, **options.merge(parent: self), &)
         | 
| 177 252 | 
             
                  children.concat(attrs)
         | 
| @@ -189,6 +264,8 @@ module CMDx | |
| 189 264 | 
             
                #
         | 
| 190 265 | 
             
                # @example
         | 
| 191 266 | 
             
                #   optional :middle_name, :nickname, types: String
         | 
| 267 | 
            +
                #
         | 
| 268 | 
            +
                # @rbs (*untyped names, **untyped options) ?{ () -> void } -> Array[Attribute]
         | 
| 192 269 | 
             
                def optional(*names, **options, &)
         | 
| 193 270 | 
             
                  attributes(*names, **options.merge(required: false), &)
         | 
| 194 271 | 
             
                end
         | 
| @@ -204,6 +281,8 @@ module CMDx | |
| 204 281 | 
             
                #
         | 
| 205 282 | 
             
                # @example
         | 
| 206 283 | 
             
                #   required :first_name, :last_name, types: String
         | 
| 284 | 
            +
                #
         | 
| 285 | 
            +
                # @rbs (*untyped names, **untyped options) ?{ () -> void } -> Array[Attribute]
         | 
| 207 286 | 
             
                def required(*names, **options, &)
         | 
| 208 287 | 
             
                  attributes(*names, **options.merge(required: true), &)
         | 
| 209 288 | 
             
                end
         | 
| @@ -211,6 +290,8 @@ module CMDx | |
| 211 290 | 
             
                # Defines the attribute method on the task and validates the configuration.
         | 
| 212 291 | 
             
                #
         | 
| 213 292 | 
             
                # @raise [RuntimeError] When the method name is already defined on the task
         | 
| 293 | 
            +
                #
         | 
| 294 | 
            +
                # @rbs () -> void
         | 
| 214 295 | 
             
                def define_and_verify
         | 
| 215 296 | 
             
                  if task.respond_to?(method_name, true)
         | 
| 216 297 | 
             
                    raise <<~MESSAGE
         | 
| @@ -6,6 +6,14 @@ module CMDx | |
| 6 6 | 
             
              # in a hierarchical structure, supporting nested attribute definitions.
         | 
| 7 7 | 
             
              class AttributeRegistry
         | 
| 8 8 |  | 
| 9 | 
            +
                # Returns the collection of registered attributes.
         | 
| 10 | 
            +
                #
         | 
| 11 | 
            +
                # @return [Array<Attribute>] Array of registered attributes
         | 
| 12 | 
            +
                #
         | 
| 13 | 
            +
                # @example
         | 
| 14 | 
            +
                #   registry.registry # => [#<Attribute @name=:name>, #<Attribute @name=:email>]
         | 
| 15 | 
            +
                #
         | 
| 16 | 
            +
                # @rbs @registry: Array[Attribute]
         | 
| 9 17 | 
             
                attr_reader :registry
         | 
| 10 18 | 
             
                alias to_a registry
         | 
| 11 19 |  | 
| @@ -18,6 +26,8 @@ module CMDx | |
| 18 26 | 
             
                # @example
         | 
| 19 27 | 
             
                #   registry = AttributeRegistry.new
         | 
| 20 28 | 
             
                #   registry = AttributeRegistry.new([attr1, attr2])
         | 
| 29 | 
            +
                #
         | 
| 30 | 
            +
                # @rbs (?Array[Attribute] registry) -> void
         | 
| 21 31 | 
             
                def initialize(registry = [])
         | 
| 22 32 | 
             
                  @registry = registry
         | 
| 23 33 | 
             
                end
         | 
| @@ -28,6 +38,8 @@ module CMDx | |
| 28 38 | 
             
                #
         | 
| 29 39 | 
             
                # @example
         | 
| 30 40 | 
             
                #   new_registry = registry.dup
         | 
| 41 | 
            +
                #
         | 
| 42 | 
            +
                # @rbs () -> AttributeRegistry
         | 
| 31 43 | 
             
                def dup
         | 
| 32 44 | 
             
                  self.class.new(registry.dup)
         | 
| 33 45 | 
             
                end
         | 
| @@ -41,6 +53,8 @@ module CMDx | |
| 41 53 | 
             
                # @example
         | 
| 42 54 | 
             
                #   registry.register(attribute)
         | 
| 43 55 | 
             
                #   registry.register([attr1, attr2])
         | 
| 56 | 
            +
                #
         | 
| 57 | 
            +
                # @rbs (Attribute | Array[Attribute] attributes) -> self
         | 
| 44 58 | 
             
                def register(attributes)
         | 
| 45 59 | 
             
                  @registry.concat(Array(attributes))
         | 
| 46 60 | 
             
                  self
         | 
| @@ -56,6 +70,8 @@ module CMDx | |
| 56 70 | 
             
                # @example
         | 
| 57 71 | 
             
                #   registry.deregister(:name)
         | 
| 58 72 | 
             
                #   registry.deregister(['name1', 'name2'])
         | 
| 73 | 
            +
                #
         | 
| 74 | 
            +
                # @rbs ((Symbol | String | Array[Symbol | String]) names) -> self
         | 
| 59 75 | 
             
                def deregister(names)
         | 
| 60 76 | 
             
                  Array(names).each do |name|
         | 
| 61 77 | 
             
                    @registry.reject! { |attribute| matches_attribute_tree?(attribute, name.to_sym) }
         | 
| @@ -69,6 +85,8 @@ module CMDx | |
| 69 85 | 
             
                # and validate the attribute hierarchy.
         | 
| 70 86 | 
             
                #
         | 
| 71 87 | 
             
                # @param task [Task] The task to associate with all attributes
         | 
| 88 | 
            +
                #
         | 
| 89 | 
            +
                # @rbs (Task task) -> void
         | 
| 72 90 | 
             
                def define_and_verify(task)
         | 
| 73 91 | 
             
                  registry.each do |attribute|
         | 
| 74 92 | 
             
                    attribute.task = task
         | 
| @@ -84,6 +102,8 @@ module CMDx | |
| 84 102 | 
             
                # @param name [Symbol] The name to match against
         | 
| 85 103 | 
             
                #
         | 
| 86 104 | 
             
                # @return [Boolean] True if the attribute or any child matches the name
         | 
| 105 | 
            +
                #
         | 
| 106 | 
            +
                # @rbs (Attribute attribute, Symbol name) -> bool
         | 
| 87 107 | 
             
                def matches_attribute_tree?(attribute, name)
         | 
| 88 108 | 
             
                  return true if attribute.method_name == name
         | 
| 89 109 |  | 
    
        data/lib/cmdx/attribute_value.rb
    CHANGED
    
    | @@ -8,6 +8,14 @@ module CMDx | |
| 8 8 |  | 
| 9 9 | 
             
                extend Forwardable
         | 
| 10 10 |  | 
| 11 | 
            +
                # Returns the attribute managed by this value handler.
         | 
| 12 | 
            +
                #
         | 
| 13 | 
            +
                # @return [Attribute] The attribute instance
         | 
| 14 | 
            +
                #
         | 
| 15 | 
            +
                # @example
         | 
| 16 | 
            +
                #   attr_value.attribute.name # => :user_id
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                # @rbs @attribute: Attribute
         | 
| 11 19 | 
             
                attr_reader :attribute
         | 
| 12 20 |  | 
| 13 21 | 
             
                def_delegators :attribute, :task, :parent, :name, :options, :types, :source, :method_name, :required?
         | 
| @@ -20,6 +28,8 @@ module CMDx | |
| 20 28 | 
             
                # @example
         | 
| 21 29 | 
             
                #   attr = Attribute.new(:user_id, required: true)
         | 
| 22 30 | 
             
                #   attr_value = AttributeValue.new(attr)
         | 
| 31 | 
            +
                #
         | 
| 32 | 
            +
                # @rbs (Attribute attribute) -> void
         | 
| 23 33 | 
             
                def initialize(attribute)
         | 
| 24 34 | 
             
                  @attribute = attribute
         | 
| 25 35 | 
             
                end
         | 
| @@ -30,6 +40,8 @@ module CMDx | |
| 30 40 | 
             
                #
         | 
| 31 41 | 
             
                # @example
         | 
| 32 42 | 
             
                #   attr_value.value # => "john_doe"
         | 
| 43 | 
            +
                #
         | 
| 44 | 
            +
                # @rbs () -> untyped
         | 
| 33 45 | 
             
                def value
         | 
| 34 46 | 
             
                  attributes[method_name]
         | 
| 35 47 | 
             
                end
         | 
| @@ -41,6 +53,8 @@ module CMDx | |
| 41 53 | 
             
                #
         | 
| 42 54 | 
             
                # @example
         | 
| 43 55 | 
             
                #   attr_value.generate # => 42
         | 
| 56 | 
            +
                #
         | 
| 57 | 
            +
                # @rbs () -> untyped
         | 
| 44 58 | 
             
                def generate
         | 
| 45 59 | 
             
                  return value if attributes.key?(method_name)
         | 
| 46 60 |  | 
| @@ -64,6 +78,8 @@ module CMDx | |
| 64 78 | 
             
                # @example
         | 
| 65 79 | 
             
                #   attr_value.validate
         | 
| 66 80 | 
             
                #   # Validates value against :presence, :format, etc.
         | 
| 81 | 
            +
                #
         | 
| 82 | 
            +
                # @rbs () -> void
         | 
| 67 83 | 
             
                def validate
         | 
| 68 84 | 
             
                  registry = task.class.settings[:validators]
         | 
| 69 85 |  | 
| @@ -86,6 +102,7 @@ module CMDx | |
| 86 102 | 
             
                # @example
         | 
| 87 103 | 
             
                #   # Sources from task method, proc, or direct value
         | 
| 88 104 | 
             
                #   source_value # => "raw_value"
         | 
| 105 | 
            +
                # @rbs () -> untyped
         | 
| 89 106 | 
             
                def source_value
         | 
| 90 107 | 
             
                  sourced_value =
         | 
| 91 108 | 
             
                    case source
         | 
| @@ -115,6 +132,8 @@ module CMDx | |
| 115 132 | 
             
                # @example
         | 
| 116 133 | 
             
                #   # Default can be symbol, proc, or direct value
         | 
| 117 134 | 
             
                #   -> { rand(100) } # => 23
         | 
| 135 | 
            +
                #
         | 
| 136 | 
            +
                # @rbs () -> untyped
         | 
| 118 137 | 
             
                def default_value
         | 
| 119 138 | 
             
                  default = options[:default]
         | 
| 120 139 |  | 
| @@ -140,6 +159,8 @@ module CMDx | |
| 140 159 | 
             
                # @example
         | 
| 141 160 | 
             
                #   # Derives from hash key, method call, or proc execution
         | 
| 142 161 | 
             
                #   context.user_id # => 42
         | 
| 162 | 
            +
                #
         | 
| 163 | 
            +
                # @rbs (untyped source_value) -> untyped
         | 
| 143 164 | 
             
                def derive_value(source_value)
         | 
| 144 165 | 
             
                  derived_value =
         | 
| 145 166 | 
             
                    case source_value
         | 
| @@ -163,6 +184,8 @@ module CMDx | |
| 163 184 | 
             
                #
         | 
| 164 185 | 
             
                # @example
         | 
| 165 186 | 
             
                #   :downcase # => "hello"
         | 
| 187 | 
            +
                #
         | 
| 188 | 
            +
                # @rbs (untyped derived_value) -> untyped
         | 
| 166 189 | 
             
                def transform_value(derived_value)
         | 
| 167 190 | 
             
                  transform = options[:transform]
         | 
| 168 191 |  | 
| @@ -186,6 +209,8 @@ module CMDx | |
| 186 209 | 
             
                # @example
         | 
| 187 210 | 
             
                #   # Coerces "42" to Integer, "true" to Boolean, etc.
         | 
| 188 211 | 
             
                #   coerce_value("42") # => 42
         | 
| 212 | 
            +
                #
         | 
| 213 | 
            +
                # @rbs (untyped transformed_value) -> untyped
         | 
| 189 214 | 
             
                def coerce_value(transformed_value)
         | 
| 190 215 | 
             
                  return transformed_value if types.empty?
         | 
| 191 216 |  | 
| @@ -7,6 +7,7 @@ module CMDx | |
| 7 7 | 
             
              # Each callback type represents a specific execution phase or outcome.
         | 
| 8 8 | 
             
              class CallbackRegistry
         | 
| 9 9 |  | 
| 10 | 
            +
                # @rbs TYPES: Array[Symbol]
         | 
| 10 11 | 
             
                TYPES = %i[
         | 
| 11 12 | 
             
                  before_validation
         | 
| 12 13 | 
             
                  before_execution
         | 
| @@ -20,10 +21,20 @@ module CMDx | |
| 20 21 | 
             
                  on_bad
         | 
| 21 22 | 
             
                ].freeze
         | 
| 22 23 |  | 
| 24 | 
            +
                # Returns the internal registry of callbacks organized by type.
         | 
| 25 | 
            +
                #
         | 
| 26 | 
            +
                # @return [Hash{Symbol => Set<Array>}] Hash mapping callback types to their registered callables
         | 
| 27 | 
            +
                #
         | 
| 28 | 
            +
                # @example
         | 
| 29 | 
            +
                #   registry.registry # => { before_execution: #<Set: [[[:validate], {}]]> }
         | 
| 30 | 
            +
                #
         | 
| 31 | 
            +
                # @rbs @registry: Hash[Symbol, Set[Array[untyped]]]
         | 
| 23 32 | 
             
                attr_reader :registry
         | 
| 24 33 | 
             
                alias to_h registry
         | 
| 25 34 |  | 
| 26 35 | 
             
                # @param registry [Hash] Initial registry hash, defaults to empty
         | 
| 36 | 
            +
                #
         | 
| 37 | 
            +
                # @rbs (?Hash[Symbol, Set[Array[untyped]]] registry) -> void
         | 
| 27 38 | 
             
                def initialize(registry = {})
         | 
| 28 39 | 
             
                  @registry = registry
         | 
| 29 40 | 
             
                end
         | 
| @@ -31,6 +42,8 @@ module CMDx | |
| 31 42 | 
             
                # Creates a deep copy of the registry with duplicated callable sets
         | 
| 32 43 | 
             
                #
         | 
| 33 44 | 
             
                # @return [CallbackRegistry] A new instance with duplicated registry contents
         | 
| 45 | 
            +
                #
         | 
| 46 | 
            +
                # @rbs () -> CallbackRegistry
         | 
| 34 47 | 
             
                def dup
         | 
| 35 48 | 
             
                  self.class.new(registry.transform_values(&:dup))
         | 
| 36 49 | 
             
                end
         | 
| @@ -54,6 +67,8 @@ module CMDx | |
| 54 67 | 
             
                #   registry.register(:on_success, if: { status: :completed }) do |task|
         | 
| 55 68 | 
             
                #     task.log("Success callback executed")
         | 
| 56 69 | 
             
                #   end
         | 
| 70 | 
            +
                #
         | 
| 71 | 
            +
                # @rbs (Symbol type, *untyped callables, **untyped options) ?{ (Task) -> void } -> self
         | 
| 57 72 | 
             
                def register(type, *callables, **options, &block)
         | 
| 58 73 | 
             
                  callables << block if block_given?
         | 
| 59 74 |  | 
| @@ -73,6 +88,8 @@ module CMDx | |
| 73 88 | 
             
                #
         | 
| 74 89 | 
             
                # @example Remove a specific callback
         | 
| 75 90 | 
             
                #   registry.deregister(:before_execution, :validate_inputs)
         | 
| 91 | 
            +
                #
         | 
| 92 | 
            +
                # @rbs (Symbol type, *untyped callables, **untyped options) ?{ (Task) -> void } -> self
         | 
| 76 93 | 
             
                def deregister(type, *callables, **options, &block)
         | 
| 77 94 | 
             
                  callables << block if block_given?
         | 
| 78 95 | 
             
                  return self unless registry[type]
         | 
| @@ -91,6 +108,8 @@ module CMDx | |
| 91 108 | 
             
                #
         | 
| 92 109 | 
             
                # @example Invoke all before_execution callbacks
         | 
| 93 110 | 
             
                #   registry.invoke(:before_execution, task)
         | 
| 111 | 
            +
                #
         | 
| 112 | 
            +
                # @rbs (Symbol type, Task task) -> void
         | 
| 94 113 | 
             
                def invoke(type, task)
         | 
| 95 114 | 
             
                  raise TypeError, "unknown callback type #{type.inspect}" unless TYPES.include?(type)
         | 
| 96 115 |  | 
    
        data/lib/cmdx/chain.rb
    CHANGED
    
    | @@ -8,9 +8,28 @@ module CMDx | |
| 8 8 |  | 
| 9 9 | 
             
                extend Forwardable
         | 
| 10 10 |  | 
| 11 | 
            +
                # @rbs THREAD_KEY: Symbol
         | 
| 11 12 | 
             
                THREAD_KEY = :cmdx_chain
         | 
| 12 13 |  | 
| 13 | 
            -
                 | 
| 14 | 
            +
                # Returns the unique identifier for this chain.
         | 
| 15 | 
            +
                #
         | 
| 16 | 
            +
                # @return [String] The chain identifier
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                # @example
         | 
| 19 | 
            +
                #   chain.id # => "abc123xyz"
         | 
| 20 | 
            +
                #
         | 
| 21 | 
            +
                # @rbs @id: String
         | 
| 22 | 
            +
                attr_reader :id
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                # Returns the collection of execution results in this chain.
         | 
| 25 | 
            +
                #
         | 
| 26 | 
            +
                # @return [Array<Result>] Array of task results
         | 
| 27 | 
            +
                #
         | 
| 28 | 
            +
                # @example
         | 
| 29 | 
            +
                #   chain.results # => [#<Result>, #<Result>]
         | 
| 30 | 
            +
                #
         | 
| 31 | 
            +
                # @rbs @results: Array[Result]
         | 
| 32 | 
            +
                attr_reader :results
         | 
| 14 33 |  | 
| 15 34 | 
             
                def_delegators :results, :index, :first, :last, :size
         | 
| 16 35 | 
             
                def_delegators :first, :state, :status, :outcome, :runtime
         | 
| @@ -18,6 +37,8 @@ module CMDx | |
| 18 37 | 
             
                # Creates a new chain with a unique identifier and empty results collection.
         | 
| 19 38 | 
             
                #
         | 
| 20 39 | 
             
                # @return [Chain] A new chain instance
         | 
| 40 | 
            +
                #
         | 
| 41 | 
            +
                # @rbs () -> void
         | 
| 21 42 | 
             
                def initialize
         | 
| 22 43 | 
             
                  @id = Identifier.generate
         | 
| 23 44 | 
             
                  @results = []
         | 
| @@ -34,6 +55,8 @@ module CMDx | |
| 34 55 | 
             
                  #   if chain
         | 
| 35 56 | 
             
                  #     puts "Current chain: #{chain.id}"
         | 
| 36 57 | 
             
                  #   end
         | 
| 58 | 
            +
                  #
         | 
| 59 | 
            +
                  # @rbs () -> Chain?
         | 
| 37 60 | 
             
                  def current
         | 
| 38 61 | 
             
                    Thread.current[THREAD_KEY]
         | 
| 39 62 | 
             
                  end
         | 
| @@ -46,6 +69,8 @@ module CMDx | |
| 46 69 | 
             
                  #
         | 
| 47 70 | 
             
                  # @example
         | 
| 48 71 | 
             
                  #   Chain.current = my_chain
         | 
| 72 | 
            +
                  #
         | 
| 73 | 
            +
                  # @rbs (Chain chain) -> Chain
         | 
| 49 74 | 
             
                  def current=(chain)
         | 
| 50 75 | 
             
                    Thread.current[THREAD_KEY] = chain
         | 
| 51 76 | 
             
                  end
         | 
| @@ -56,6 +81,8 @@ module CMDx | |
| 56 81 | 
             
                  #
         | 
| 57 82 | 
             
                  # @example
         | 
| 58 83 | 
             
                  #   Chain.clear
         | 
| 84 | 
            +
                  #
         | 
| 85 | 
            +
                  # @rbs () -> nil
         | 
| 59 86 | 
             
                  def clear
         | 
| 60 87 | 
             
                    Thread.current[THREAD_KEY] = nil
         | 
| 61 88 | 
             
                  end
         | 
| @@ -73,6 +100,8 @@ module CMDx | |
| 73 100 | 
             
                  #   result = task.execute
         | 
| 74 101 | 
             
                  #   chain = Chain.build(result)
         | 
| 75 102 | 
             
                  #   puts "Chain size: #{chain.size}"
         | 
| 103 | 
            +
                  #
         | 
| 104 | 
            +
                  # @rbs (Result result) -> Chain
         | 
| 76 105 | 
             
                  def build(result)
         | 
| 77 106 | 
             
                    raise TypeError, "must be a CMDx::Result" unless result.is_a?(Result)
         | 
| 78 107 |  | 
| @@ -95,6 +124,8 @@ module CMDx | |
| 95 124 | 
             
                #   chain_hash = chain.to_h
         | 
| 96 125 | 
             
                #   puts chain_hash[:id]
         | 
| 97 126 | 
             
                #   puts chain_hash[:results].size
         | 
| 127 | 
            +
                #
         | 
| 128 | 
            +
                # @rbs () -> Hash[Symbol, untyped]
         | 
| 98 129 | 
             
                def to_h
         | 
| 99 130 | 
             
                  {
         | 
| 100 131 | 
             
                    id: id,
         | 
| @@ -108,6 +139,8 @@ module CMDx | |
| 108 139 | 
             
                #
         | 
| 109 140 | 
             
                # @example
         | 
| 110 141 | 
             
                #   puts chain.to_s
         | 
| 142 | 
            +
                #
         | 
| 143 | 
            +
                # @rbs () -> String
         | 
| 111 144 | 
             
                def to_s
         | 
| 112 145 | 
             
                  Utils::Format.to_str(to_h)
         | 
| 113 146 | 
             
                end
         | 
| @@ -7,6 +7,14 @@ module CMDx | |
| 7 7 | 
             
              # for various data types including arrays, numbers, dates, and other primitives.
         | 
| 8 8 | 
             
              class CoercionRegistry
         | 
| 9 9 |  | 
| 10 | 
            +
                # Returns the internal registry mapping coercion types to handler classes.
         | 
| 11 | 
            +
                #
         | 
| 12 | 
            +
                # @return [Hash{Symbol => Class}] Hash of coercion type names to coercion classes
         | 
| 13 | 
            +
                #
         | 
| 14 | 
            +
                # @example
         | 
| 15 | 
            +
                #   registry.registry # => { integer: Coercions::Integer, boolean: Coercions::Boolean }
         | 
| 16 | 
            +
                #
         | 
| 17 | 
            +
                # @rbs @registry: Hash[Symbol, Class]
         | 
| 10 18 | 
             
                attr_reader :registry
         | 
| 11 19 | 
             
                alias to_h registry
         | 
| 12 20 |  | 
| @@ -17,6 +25,8 @@ module CMDx | |
| 17 25 | 
             
                # @example
         | 
| 18 26 | 
             
                #   registry = CoercionRegistry.new
         | 
| 19 27 | 
             
                #   registry = CoercionRegistry.new(custom: CustomCoercion)
         | 
| 28 | 
            +
                #
         | 
| 29 | 
            +
                # @rbs (?Hash[Symbol, Class]? registry) -> void
         | 
| 20 30 | 
             
                def initialize(registry = nil)
         | 
| 21 31 | 
             
                  @registry = registry || {
         | 
| 22 32 | 
             
                    array: Coercions::Array,
         | 
| @@ -40,6 +50,8 @@ module CMDx | |
| 40 50 | 
             
                #
         | 
| 41 51 | 
             
                # @example
         | 
| 42 52 | 
             
                #   new_registry = registry.dup
         | 
| 53 | 
            +
                #
         | 
| 54 | 
            +
                # @rbs () -> CoercionRegistry
         | 
| 43 55 | 
             
                def dup
         | 
| 44 56 | 
             
                  self.class.new(registry.dup)
         | 
| 45 57 | 
             
                end
         | 
| @@ -54,6 +66,8 @@ module CMDx | |
| 54 66 | 
             
                # @example
         | 
| 55 67 | 
             
                #   registry.register(:custom_type, CustomCoercion)
         | 
| 56 68 | 
             
                #   registry.register("another_type", AnotherCoercion)
         | 
| 69 | 
            +
                #
         | 
| 70 | 
            +
                # @rbs ((Symbol | String) name, Class coercion) -> self
         | 
| 57 71 | 
             
                def register(name, coercion)
         | 
| 58 72 | 
             
                  registry[name.to_sym] = coercion
         | 
| 59 73 | 
             
                  self
         | 
| @@ -68,6 +82,8 @@ module CMDx | |
| 68 82 | 
             
                # @example
         | 
| 69 83 | 
             
                #   registry.deregister(:custom_type)
         | 
| 70 84 | 
             
                #   registry.deregister("another_type")
         | 
| 85 | 
            +
                #
         | 
| 86 | 
            +
                # @rbs ((Symbol | String) name) -> self
         | 
| 71 87 | 
             
                def deregister(name)
         | 
| 72 88 | 
             
                  registry.delete(name.to_sym)
         | 
| 73 89 | 
             
                  self
         | 
| @@ -87,6 +103,8 @@ module CMDx | |
| 87 103 | 
             
                # @example
         | 
| 88 104 | 
             
                #   result = registry.coerce(:integer, task, "42")
         | 
| 89 105 | 
             
                #   result = registry.coerce(:boolean, task, "true", strict: true)
         | 
| 106 | 
            +
                #
         | 
| 107 | 
            +
                # @rbs (Symbol type, untyped task, untyped value, ?Hash[Symbol, untyped] options) -> untyped
         | 
| 90 108 | 
             
                def coerce(type, task, value, options = {})
         | 
| 91 109 | 
             
                  raise TypeError, "unknown coercion type #{type.inspect}" unless registry.key?(type)
         | 
| 92 110 |  | 
    
        data/lib/cmdx/coercions/array.rb
    CHANGED
    
    | @@ -26,6 +26,8 @@ module CMDx | |
| 26 26 | 
             
                  #   Array.call("hello")     # => ["hello"]
         | 
| 27 27 | 
             
                  #   Array.call(42)          # => [42]
         | 
| 28 28 | 
             
                  #   Array.call(nil)         # => []
         | 
| 29 | 
            +
                  #
         | 
| 30 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Array[untyped]
         | 
| 29 31 | 
             
                  def call(value, options = {})
         | 
| 30 32 | 
             
                    if value.is_a?(::String) && value.start_with?("[")
         | 
| 31 33 | 
             
                      JSON.parse(value)
         |