cmdx 1.9.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.cursor/prompts/llms.md +3 -13
- data/.cursor/prompts/yardoc.md +1 -0
- data/CHANGELOG.md +16 -0
- data/LLM.md +436 -374
- data/README.md +7 -2
- data/docs/basics/setup.md +17 -0
- data/docs/callbacks.md +1 -1
- data/docs/getting_started.md +22 -2
- data/docs/index.md +13 -1
- data/docs/retries.md +121 -0
- data/docs/tips_and_tricks.md +2 -1
- data/examples/stoplight_circuit_breaker.md +36 -0
- 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 +126 -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 +71 -11
- 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
- data/mkdocs.yml +3 -1
- metadata +3 -1
| @@ -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)
         | 
| @@ -10,6 +10,7 @@ module CMDx | |
| 10 10 |  | 
| 11 11 | 
             
                  extend self
         | 
| 12 12 |  | 
| 13 | 
            +
                  # @rbs DEFAULT_PRECISION: Integer
         | 
| 13 14 | 
             
                  DEFAULT_PRECISION = 14
         | 
| 14 15 |  | 
| 15 16 | 
             
                  # Converts a value to a BigDecimal
         | 
| @@ -28,6 +29,8 @@ module CMDx | |
| 28 29 | 
             
                  # @example Convert other numeric types
         | 
| 29 30 | 
             
                  #   BigDecimal.call(42)                         # => #<BigDecimal:7f8b8c0d8e0f '0.42E2',9(18)>
         | 
| 30 31 | 
             
                  #   BigDecimal.call(3.14159)                    # => #<BigDecimal:7f8b8c0d8e0f '0.314159E1',9(18)>
         | 
| 32 | 
            +
                  #
         | 
| 33 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> BigDecimal
         | 
| 31 34 | 
             
                  def call(value, options = {})
         | 
| 32 35 | 
             
                    BigDecimal(value, options[:precision] || DEFAULT_PRECISION)
         | 
| 33 36 | 
             
                  rescue ArgumentError, TypeError
         | 
| @@ -10,7 +10,10 @@ module CMDx | |
| 10 10 |  | 
| 11 11 | 
             
                  extend self
         | 
| 12 12 |  | 
| 13 | 
            +
                  # @rbs FALSEY: Regexp
         | 
| 13 14 | 
             
                  FALSEY = /^(false|f|no|n|0)$/i
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  # @rbs TRUTHY: Regexp
         | 
| 14 17 | 
             
                  TRUTHY = /^(true|t|yes|y|1)$/i
         | 
| 15 18 |  | 
| 16 19 | 
             
                  # Converts a value to a Boolean
         | 
| @@ -34,6 +37,8 @@ module CMDx | |
| 34 37 | 
             
                  # @example Handle case-insensitive input
         | 
| 35 38 | 
             
                  #   Boolean.call("TRUE")   # => true
         | 
| 36 39 | 
             
                  #   Boolean.call("False")  # => false
         | 
| 40 | 
            +
                  #
         | 
| 41 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> bool
         | 
| 37 42 | 
             
                  def call(value, options = {})
         | 
| 38 43 | 
             
                    case value.to_s.downcase
         | 
| 39 44 | 
             
                    when FALSEY then false
         | 
| @@ -26,6 +26,8 @@ module CMDx | |
| 26 26 | 
             
                  #   Complex.call(5)                          # => (5+0i)
         | 
| 27 27 | 
             
                  #   Complex.call(3.14)                       # => (3.14+0i)
         | 
| 28 28 | 
             
                  #   Complex.call(Complex(1, 2))              # => (1+2i)
         | 
| 29 | 
            +
                  #
         | 
| 30 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Complex
         | 
| 29 31 | 
             
                  def call(value, options = {})
         | 
| 30 32 | 
             
                    Complex(value)
         | 
| 31 33 | 
             
                  rescue ArgumentError, TypeError
         | 
    
        data/lib/cmdx/coercions/date.rb
    CHANGED
    
    | @@ -12,6 +12,8 @@ module CMDx | |
| 12 12 | 
             
                  extend self
         | 
| 13 13 |  | 
| 14 14 | 
             
                  # Types that are already date-like and don't need conversion
         | 
| 15 | 
            +
                  #
         | 
| 16 | 
            +
                  # @rbs ANALOG_TYPES: Array[String]
         | 
| 15 17 | 
             
                  ANALOG_TYPES = %w[Date DateTime Time].freeze
         | 
| 16 18 |  | 
| 17 19 | 
             
                  # Converts a value to a Date object
         | 
| @@ -33,6 +35,8 @@ module CMDx | |
| 33 35 | 
             
                  # @example Return existing Date objects unchanged
         | 
| 34 36 | 
             
                  #   Date.call(Date.new(2023, 12, 25)) # => #<Date: 2023-12-25>
         | 
| 35 37 | 
             
                  #   Date.call(DateTime.new(2023, 12, 25)) # => #<Date: 2023-12-25>
         | 
| 38 | 
            +
                  #
         | 
| 39 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Date
         | 
| 36 40 | 
             
                  def call(value, options = {})
         | 
| 37 41 | 
             
                    return value if ANALOG_TYPES.include?(value.class.name)
         | 
| 38 42 | 
             
                    return ::Date.strptime(value, options[:strptime]) if options[:strptime]
         | 
| @@ -11,6 +11,9 @@ module CMDx | |
| 11 11 |  | 
| 12 12 | 
             
                  extend self
         | 
| 13 13 |  | 
| 14 | 
            +
                  # Types that are already date-time-like and don't need conversion
         | 
| 15 | 
            +
                  #
         | 
| 16 | 
            +
                  # @rbs ANALOG_TYPES: Array[String]
         | 
| 14 17 | 
             
                  ANALOG_TYPES = %w[Date DateTime Time].freeze
         | 
| 15 18 |  | 
| 16 19 | 
             
                  # Converts a value to a DateTime
         | 
| @@ -32,6 +35,8 @@ module CMDx | |
| 32 35 | 
             
                  # @example Convert existing date objects
         | 
| 33 36 | 
             
                  #   DateTime.call(Date.new(2023, 12, 25))     # => #<DateTime: 2023-12-25T00:00:00+00:00>
         | 
| 34 37 | 
             
                  #   DateTime.call(Time.new(2023, 12, 25))     # => #<DateTime: 2023-12-25T00:00:00+00:00>
         | 
| 38 | 
            +
                  #
         | 
| 39 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> DateTime
         | 
| 35 40 | 
             
                  def call(value, options = {})
         | 
| 36 41 | 
             
                    return value if ANALOG_TYPES.include?(value.class.name)
         | 
| 37 42 | 
             
                    return ::DateTime.strptime(value, options[:strptime]) if options[:strptime]
         | 
    
        data/lib/cmdx/coercions/float.rb
    CHANGED
    
    | @@ -30,6 +30,8 @@ module CMDx | |
| 30 30 | 
             
                  #   Float.call(BigDecimal("123.456")) # => 123.456
         | 
| 31 31 | 
             
                  #   Float.call(Rational(3, 4))       # => 0.75
         | 
| 32 32 | 
             
                  #   Float.call(Complex(5.0, 0))      # => 5.0
         | 
| 33 | 
            +
                  #
         | 
| 34 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Float
         | 
| 33 35 | 
             
                  def call(value, options = {})
         | 
| 34 36 | 
             
                    Float(value)
         | 
| 35 37 | 
             
                  rescue ArgumentError, RangeError, TypeError
         | 
    
        data/lib/cmdx/coercions/hash.rb
    CHANGED
    
    | @@ -30,6 +30,8 @@ module CMDx | |
| 30 30 | 
             
                  #   Hash.call([:a, 1, :b, 2]) # => {a: 1, b: 2}
         | 
| 31 31 | 
             
                  # @example Coerce from JSON string
         | 
| 32 32 | 
             
                  #   Hash.call('{"key": "value"}') # => {"key" => "value"}
         | 
| 33 | 
            +
                  #
         | 
| 34 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Hash[untyped, untyped]
         | 
| 33 35 | 
             
                  def call(value, options = {})
         | 
| 34 36 | 
             
                    if value.nil?
         | 
| 35 37 | 
             
                      {}
         | 
| @@ -34,6 +34,8 @@ module CMDx | |
| 34 34 | 
             
                  #   Integer.call(nil)       # => 0
         | 
| 35 35 | 
             
                  #   Integer.call(false)     # => 0
         | 
| 36 36 | 
             
                  #   Integer.call(true)      # => 1
         | 
| 37 | 
            +
                  #
         | 
| 38 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Integer
         | 
| 37 39 | 
             
                  def call(value, options = {})
         | 
| 38 40 | 
             
                    Integer(value)
         | 
| 39 41 | 
             
                  rescue ArgumentError, FloatDomainError, RangeError, TypeError
         | 
| @@ -33,6 +33,8 @@ module CMDx | |
| 33 33 | 
             
                  #   Rational.call("")        # => (0/1)
         | 
| 34 34 | 
             
                  #   Rational.call(nil)       # => (0/1)
         | 
| 35 35 | 
             
                  #   Rational.call(0)         # => (0/1)
         | 
| 36 | 
            +
                  #
         | 
| 37 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Rational
         | 
| 36 38 | 
             
                  def call(value, options = {})
         | 
| 37 39 | 
             
                    Rational(value)
         | 
| 38 40 | 
             
                  rescue ArgumentError, FloatDomainError, RangeError, TypeError, ZeroDivisionError
         | 
| @@ -25,6 +25,8 @@ module CMDx | |
| 25 25 | 
             
                  #   Symbol.call("user_id")         # => :user_id
         | 
| 26 26 | 
             
                  #   Symbol.call("")                # => :""
         | 
| 27 27 | 
             
                  #   Symbol.call(:existing)         # => :existing
         | 
| 28 | 
            +
                  #
         | 
| 29 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Symbol
         | 
| 28 30 | 
             
                  def call(value, options = {})
         | 
| 29 31 | 
             
                    value.to_sym
         | 
| 30 32 | 
             
                  rescue NoMethodError
         | 
    
        data/lib/cmdx/coercions/time.rb
    CHANGED
    
    | @@ -11,6 +11,9 @@ module CMDx | |
| 11 11 |  | 
| 12 12 | 
             
                  extend self
         | 
| 13 13 |  | 
| 14 | 
            +
                  # Types that are already time-like and don't need conversion
         | 
| 15 | 
            +
                  #
         | 
| 16 | 
            +
                  # @rbs ANALOG_TYPES: Array[String]
         | 
| 14 17 | 
             
                  ANALOG_TYPES = %w[DateTime Time].freeze
         | 
| 15 18 |  | 
| 16 19 | 
             
                  # Converts a value to a Time object
         | 
| @@ -34,6 +37,8 @@ module CMDx | |
| 34 37 | 
             
                  # @example Convert strings with custom format
         | 
| 35 38 | 
             
                  #   Time.call("25/12/2023", strptime: "%d/%m/%Y")  # => Time object
         | 
| 36 39 | 
             
                  #   Time.call("12-25-2023", strptime: "%m-%d-%Y")  # => Time object
         | 
| 40 | 
            +
                  #
         | 
| 41 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Time
         | 
| 37 42 | 
             
                  def call(value, options = {})
         | 
| 38 43 | 
             
                    return value if ANALOG_TYPES.include?(value.class.name)
         | 
| 39 44 | 
             
                    return value.to_time if value.respond_to?(:to_time)
         | 
    
        data/lib/cmdx/configuration.rb
    CHANGED
    
    | @@ -6,11 +6,122 @@ module CMDx | |
| 6 6 | 
             
              # callbacks, coercions, validators, breakpoints, backtraces, and logging.
         | 
| 7 7 | 
             
              class Configuration
         | 
| 8 8 |  | 
| 9 | 
            +
                # @rbs DEFAULT_BREAKPOINTS: Array[String]
         | 
| 9 10 | 
             
                DEFAULT_BREAKPOINTS = %w[failed].freeze
         | 
| 10 11 |  | 
| 11 | 
            -
                 | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 12 | 
            +
                # @rbs DEFAULT_ROLLPOINTS: Array[String]
         | 
| 13 | 
            +
                DEFAULT_ROLLPOINTS = %w[failed].freeze
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                # Returns the middleware registry for task execution.
         | 
| 16 | 
            +
                #
         | 
| 17 | 
            +
                # @return [MiddlewareRegistry] The middleware registry
         | 
| 18 | 
            +
                #
         | 
| 19 | 
            +
                # @example
         | 
| 20 | 
            +
                #   config.middlewares.register(CustomMiddleware)
         | 
| 21 | 
            +
                #
         | 
| 22 | 
            +
                # @rbs @middlewares: MiddlewareRegistry
         | 
| 23 | 
            +
                attr_accessor :middlewares
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                # Returns the callback registry for task lifecycle hooks.
         | 
| 26 | 
            +
                #
         | 
| 27 | 
            +
                # @return [CallbackRegistry] The callback registry
         | 
| 28 | 
            +
                #
         | 
| 29 | 
            +
                # @example
         | 
| 30 | 
            +
                #   config.callbacks.register(:before_execution, :log_start)
         | 
| 31 | 
            +
                #
         | 
| 32 | 
            +
                # @rbs @callbacks: CallbackRegistry
         | 
| 33 | 
            +
                attr_accessor :callbacks
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                # Returns the coercion registry for type conversions.
         | 
| 36 | 
            +
                #
         | 
| 37 | 
            +
                # @return [CoercionRegistry] The coercion registry
         | 
| 38 | 
            +
                #
         | 
| 39 | 
            +
                # @example
         | 
| 40 | 
            +
                #   config.coercions.register(:custom, CustomCoercion)
         | 
| 41 | 
            +
                #
         | 
| 42 | 
            +
                # @rbs @coercions: CoercionRegistry
         | 
| 43 | 
            +
                attr_accessor :coercions
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                # Returns the validator registry for attribute validation.
         | 
| 46 | 
            +
                #
         | 
| 47 | 
            +
                # @return [ValidatorRegistry] The validator registry
         | 
| 48 | 
            +
                #
         | 
| 49 | 
            +
                # @example
         | 
| 50 | 
            +
                #   config.validators.register(:email, EmailValidator)
         | 
| 51 | 
            +
                #
         | 
| 52 | 
            +
                # @rbs @validators: ValidatorRegistry
         | 
| 53 | 
            +
                attr_accessor :validators
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                # Returns the breakpoint statuses for task execution interruption.
         | 
| 56 | 
            +
                #
         | 
| 57 | 
            +
                # @return [Array<String>] Array of status names that trigger breakpoints
         | 
| 58 | 
            +
                #
         | 
| 59 | 
            +
                # @example
         | 
| 60 | 
            +
                #   config.task_breakpoints = ["failed", "skipped"]
         | 
| 61 | 
            +
                #
         | 
| 62 | 
            +
                # @rbs @task_breakpoints: Array[String]
         | 
| 63 | 
            +
                attr_accessor :task_breakpoints
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                # Returns the breakpoint statuses for workflow execution interruption.
         | 
| 66 | 
            +
                #
         | 
| 67 | 
            +
                # @return [Array<String>] Array of status names that trigger breakpoints
         | 
| 68 | 
            +
                #
         | 
| 69 | 
            +
                # @example
         | 
| 70 | 
            +
                #   config.workflow_breakpoints = ["failed", "skipped"]
         | 
| 71 | 
            +
                #
         | 
| 72 | 
            +
                # @rbs @task_breakpoints: Array[String]
         | 
| 73 | 
            +
                # @rbs @workflow_breakpoints: Array[String]
         | 
| 74 | 
            +
                attr_accessor :workflow_breakpoints
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                # Returns the logger instance for CMDx operations.
         | 
| 77 | 
            +
                #
         | 
| 78 | 
            +
                # @return [Logger] The logger instance
         | 
| 79 | 
            +
                #
         | 
| 80 | 
            +
                # @example
         | 
| 81 | 
            +
                #   config.logger.level = Logger::DEBUG
         | 
| 82 | 
            +
                #
         | 
| 83 | 
            +
                # @rbs @logger: Logger
         | 
| 84 | 
            +
                attr_accessor :logger
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                # Returns whether to log backtraces for failed tasks.
         | 
| 87 | 
            +
                #
         | 
| 88 | 
            +
                # @return [Boolean] true if backtraces should be logged
         | 
| 89 | 
            +
                #
         | 
| 90 | 
            +
                # @example
         | 
| 91 | 
            +
                #   config.backtrace = true
         | 
| 92 | 
            +
                #
         | 
| 93 | 
            +
                # @rbs @backtrace: bool
         | 
| 94 | 
            +
                attr_accessor :backtrace
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                # Returns the proc used to clean backtraces before logging.
         | 
| 97 | 
            +
                #
         | 
| 98 | 
            +
                # @return [Proc, nil] The backtrace cleaner proc, or nil if not set
         | 
| 99 | 
            +
                #
         | 
| 100 | 
            +
                # @example
         | 
| 101 | 
            +
                #   config.backtrace_cleaner = ->(bt) { bt.first(5) }
         | 
| 102 | 
            +
                #
         | 
| 103 | 
            +
                # @rbs @backtrace_cleaner: (Proc | nil)
         | 
| 104 | 
            +
                attr_accessor :backtrace_cleaner
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                # Returns the proc called when exceptions occur during execution.
         | 
| 107 | 
            +
                #
         | 
| 108 | 
            +
                # @return [Proc, nil] The exception handler proc, or nil if not set
         | 
| 109 | 
            +
                #
         | 
| 110 | 
            +
                # @example
         | 
| 111 | 
            +
                #   config.exception_handler = ->(task, error) { Sentry.capture_exception(error) }
         | 
| 112 | 
            +
                #
         | 
| 113 | 
            +
                # @rbs @exception_handler: (Proc | nil)
         | 
| 114 | 
            +
                attr_accessor :exception_handler
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                # Returns the statuses that trigger a task execution rollback.
         | 
| 117 | 
            +
                #
         | 
| 118 | 
            +
                # @return [Array<String>] Array of status names that trigger rollback
         | 
| 119 | 
            +
                #
         | 
| 120 | 
            +
                # @example
         | 
| 121 | 
            +
                #   config.rollback_on = ["failed", "skipped"]
         | 
| 122 | 
            +
                #
         | 
| 123 | 
            +
                # @rbs @rollback_on: Array[String]
         | 
| 124 | 
            +
                attr_accessor :rollback_on
         | 
| 14 125 |  | 
| 15 126 | 
             
                # Initializes a new Configuration instance with default values.
         | 
| 16 127 | 
             
                #
         | 
| @@ -23,6 +134,8 @@ module CMDx | |
| 23 134 | 
             
                #   config = Configuration.new
         | 
| 24 135 | 
             
                #   config.middlewares.class # => MiddlewareRegistry
         | 
| 25 136 | 
             
                #   config.task_breakpoints # => ["failed"]
         | 
| 137 | 
            +
                #
         | 
| 138 | 
            +
                # @rbs () -> void
         | 
| 26 139 | 
             
                def initialize
         | 
| 27 140 | 
             
                  @middlewares = MiddlewareRegistry.new
         | 
| 28 141 | 
             
                  @callbacks = CallbackRegistry.new
         | 
| @@ -31,6 +144,7 @@ module CMDx | |
| 31 144 |  | 
| 32 145 | 
             
                  @task_breakpoints = DEFAULT_BREAKPOINTS
         | 
| 33 146 | 
             
                  @workflow_breakpoints = DEFAULT_BREAKPOINTS
         | 
| 147 | 
            +
                  @rollback_on = DEFAULT_ROLLPOINTS
         | 
| 34 148 |  | 
| 35 149 | 
             
                  @backtrace = false
         | 
| 36 150 | 
             
                  @backtrace_cleaner = nil
         | 
| @@ -52,6 +166,8 @@ module CMDx | |
| 52 166 | 
             
                #   config = Configuration.new
         | 
| 53 167 | 
             
                #   config.to_h
         | 
| 54 168 | 
             
                #   # => { middlewares: #<MiddlewareRegistry>, callbacks: #<CallbackRegistry>, ... }
         | 
| 169 | 
            +
                #
         | 
| 170 | 
            +
                # @rbs () -> Hash[Symbol, untyped]
         | 
| 55 171 | 
             
                def to_h
         | 
| 56 172 | 
             
                  {
         | 
| 57 173 | 
             
                    middlewares: @middlewares,
         | 
| @@ -60,6 +176,7 @@ module CMDx | |
| 60 176 | 
             
                    validators: @validators,
         | 
| 61 177 | 
             
                    task_breakpoints: @task_breakpoints,
         | 
| 62 178 | 
             
                    workflow_breakpoints: @workflow_breakpoints,
         | 
| 179 | 
            +
                    rollback_on: @rollback_on,
         | 
| 63 180 | 
             
                    backtrace: @backtrace,
         | 
| 64 181 | 
             
                    backtrace_cleaner: @backtrace_cleaner,
         | 
| 65 182 | 
             
                    exception_handler: @exception_handler,
         | 
| @@ -78,6 +195,8 @@ module CMDx | |
| 78 195 | 
             
              # @example
         | 
| 79 196 | 
             
              #   config = CMDx.configuration
         | 
| 80 197 | 
             
              #   config.middlewares # => #<MiddlewareRegistry>
         | 
| 198 | 
            +
              #
         | 
| 199 | 
            +
              # @rbs () -> Configuration
         | 
| 81 200 | 
             
              def configuration
         | 
| 82 201 | 
             
                return @configuration if @configuration
         | 
| 83 202 |  | 
| @@ -99,6 +218,8 @@ module CMDx | |
| 99 218 | 
             
              #     config.task_breakpoints = ["failed", "skipped"]
         | 
| 100 219 | 
             
              #     config.logger.level = Logger::DEBUG
         | 
| 101 220 | 
             
              #   end
         | 
| 221 | 
            +
              #
         | 
| 222 | 
            +
              # @rbs () { (Configuration) -> void } -> Configuration
         | 
| 102 223 | 
             
              def configure
         | 
| 103 224 | 
             
                raise ArgumentError, "block required" unless block_given?
         | 
| 104 225 |  | 
| @@ -114,6 +235,8 @@ module CMDx | |
| 114 235 | 
             
              # @example
         | 
| 115 236 | 
             
              #   CMDx.reset_configuration!
         | 
| 116 237 | 
             
              #   # Configuration is now reset to defaults
         | 
| 238 | 
            +
              #
         | 
| 239 | 
            +
              # @rbs () -> Configuration
         | 
| 117 240 | 
             
              def reset_configuration!
         | 
| 118 241 | 
             
                @configuration = Configuration.new
         | 
| 119 242 | 
             
              end
         |