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
    
        data/lib/cmdx/context.rb
    CHANGED
    
    | @@ -11,6 +11,14 @@ module CMDx | |
| 11 11 |  | 
| 12 12 | 
             
                extend Forwardable
         | 
| 13 13 |  | 
| 14 | 
            +
                # Returns the internal hash storing context data.
         | 
| 15 | 
            +
                #
         | 
| 16 | 
            +
                # @return [Hash{Symbol => Object}] The internal hash table
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                # @example
         | 
| 19 | 
            +
                #   context.table # => { name: "John", age: 30 }
         | 
| 20 | 
            +
                #
         | 
| 21 | 
            +
                # @rbs @table: Hash[Symbol, untyped]
         | 
| 14 22 | 
             
                attr_reader :table
         | 
| 15 23 | 
             
                alias to_h table
         | 
| 16 24 |  | 
| @@ -28,6 +36,8 @@ module CMDx | |
| 28 36 | 
             
                # @example
         | 
| 29 37 | 
             
                #   context = Context.new(name: "John", age: 30)
         | 
| 30 38 | 
             
                #   context[:name] # => "John"
         | 
| 39 | 
            +
                #
         | 
| 40 | 
            +
                # @rbs (untyped args) -> void
         | 
| 31 41 | 
             
                def initialize(args = {})
         | 
| 32 42 | 
             
                  @table =
         | 
| 33 43 | 
             
                    if args.respond_to?(:to_hash)
         | 
| @@ -50,6 +60,8 @@ module CMDx | |
| 50 60 | 
             
                #   existing = Context.new(name: "John")
         | 
| 51 61 | 
             
                #   built = Context.build(existing) # reuses existing context
         | 
| 52 62 | 
             
                #   built.object_id == existing.object_id # => true
         | 
| 63 | 
            +
                #
         | 
| 64 | 
            +
                # @rbs (untyped context) -> Context
         | 
| 53 65 | 
             
                def self.build(context = {})
         | 
| 54 66 | 
             
                  if context.is_a?(self) && !context.frozen?
         | 
| 55 67 | 
             
                    context
         | 
| @@ -70,6 +82,8 @@ module CMDx | |
| 70 82 | 
             
                #   context = Context.new(name: "John")
         | 
| 71 83 | 
             
                #   context[:name] # => "John"
         | 
| 72 84 | 
             
                #   context["name"] # => "John" (automatically converted to symbol)
         | 
| 85 | 
            +
                #
         | 
| 86 | 
            +
                # @rbs ((String | Symbol) key) -> untyped
         | 
| 73 87 | 
             
                def [](key)
         | 
| 74 88 | 
             
                  table[key.to_sym]
         | 
| 75 89 | 
             
                end
         | 
| @@ -85,6 +99,8 @@ module CMDx | |
| 85 99 | 
             
                #   context = Context.new
         | 
| 86 100 | 
             
                #   context.store(:name, "John")
         | 
| 87 101 | 
             
                #   context[:name] # => "John"
         | 
| 102 | 
            +
                #
         | 
| 103 | 
            +
                # @rbs ((String | Symbol) key, untyped value) -> untyped
         | 
| 88 104 | 
             
                def store(key, value)
         | 
| 89 105 | 
             
                  table[key.to_sym] = value
         | 
| 90 106 | 
             
                end
         | 
| @@ -104,6 +120,8 @@ module CMDx | |
| 104 120 | 
             
                #   context.fetch(:name) # => "John"
         | 
| 105 121 | 
             
                #   context.fetch(:age, 25) # => 25
         | 
| 106 122 | 
             
                #   context.fetch(:city) { |key| "Unknown #{key}" } # => "Unknown city"
         | 
| 123 | 
            +
                #
         | 
| 124 | 
            +
                # @rbs ((String | Symbol) key, *untyped) ?{ ((String | Symbol)) -> untyped } -> untyped
         | 
| 107 125 | 
             
                def fetch(key, ...)
         | 
| 108 126 | 
             
                  table.fetch(key.to_sym, ...)
         | 
| 109 127 | 
             
                end
         | 
| @@ -122,6 +140,8 @@ module CMDx | |
| 122 140 | 
             
                #   context.fetch_or_store(:name, "Default") # => "John" (existing value)
         | 
| 123 141 | 
             
                #   context.fetch_or_store(:age, 25) # => 25 (stored and returned)
         | 
| 124 142 | 
             
                #   context.fetch_or_store(:city) { |key| "Unknown #{key}" } # => "Unknown city" (stored and returned)
         | 
| 143 | 
            +
                #
         | 
| 144 | 
            +
                # @rbs ((String | Symbol) key, ?untyped value) ?{ () -> untyped } -> untyped
         | 
| 125 145 | 
             
                def fetch_or_store(key, value = nil)
         | 
| 126 146 | 
             
                  table.fetch(key.to_sym) do
         | 
| 127 147 | 
             
                    table[key.to_sym] = block_given? ? yield : value
         | 
| @@ -139,6 +159,8 @@ module CMDx | |
| 139 159 | 
             
                #   context = Context.new(name: "John")
         | 
| 140 160 | 
             
                #   context.merge!(age: 30, city: "NYC")
         | 
| 141 161 | 
             
                #   context.to_h # => {name: "John", age: 30, city: "NYC"}
         | 
| 162 | 
            +
                #
         | 
| 163 | 
            +
                # @rbs (?untyped args) -> self
         | 
| 142 164 | 
             
                def merge!(args = {})
         | 
| 143 165 | 
             
                  args.to_h.each { |key, value| self[key.to_sym] = value }
         | 
| 144 166 | 
             
                  self
         | 
| @@ -156,6 +178,8 @@ module CMDx | |
| 156 178 | 
             
                #   context = Context.new(name: "John", age: 30)
         | 
| 157 179 | 
             
                #   context.delete!(:age) # => 30
         | 
| 158 180 | 
             
                #   context.delete!(:city) { |key| "Key #{key} not found" } # => "Key city not found"
         | 
| 181 | 
            +
                #
         | 
| 182 | 
            +
                # @rbs ((String | Symbol) key) ?{ ((String | Symbol)) -> untyped } -> untyped
         | 
| 159 183 | 
             
                def delete!(key, &)
         | 
| 160 184 | 
             
                  table.delete(key.to_sym, &)
         | 
| 161 185 | 
             
                end
         | 
| @@ -170,6 +194,8 @@ module CMDx | |
| 170 194 | 
             
                #   context1 = Context.new(name: "John")
         | 
| 171 195 | 
             
                #   context2 = Context.new(name: "John")
         | 
| 172 196 | 
             
                #   context1 == context2 # => true
         | 
| 197 | 
            +
                #
         | 
| 198 | 
            +
                # @rbs (untyped other) -> bool
         | 
| 173 199 | 
             
                def eql?(other)
         | 
| 174 200 | 
             
                  other.is_a?(self.class) && (to_h == other.to_h)
         | 
| 175 201 | 
             
                end
         | 
| @@ -185,6 +211,8 @@ module CMDx | |
| 185 211 | 
             
                #   context = Context.new(name: "John")
         | 
| 186 212 | 
             
                #   context.key?(:name) # => true
         | 
| 187 213 | 
             
                #   context.key?(:age) # => false
         | 
| 214 | 
            +
                #
         | 
| 215 | 
            +
                # @rbs ((String | Symbol) key) -> bool
         | 
| 188 216 | 
             
                def key?(key)
         | 
| 189 217 | 
             
                  table.key?(key.to_sym)
         | 
| 190 218 | 
             
                end
         | 
| @@ -200,6 +228,8 @@ module CMDx | |
| 200 228 | 
             
                #   context = Context.new(user: {profile: {name: "John"}})
         | 
| 201 229 | 
             
                #   context.dig(:user, :profile, :name) # => "John"
         | 
| 202 230 | 
             
                #   context.dig(:user, :profile, :age) # => nil
         | 
| 231 | 
            +
                #
         | 
| 232 | 
            +
                # @rbs ((String | Symbol) key, *(String | Symbol) keys) -> untyped
         | 
| 203 233 | 
             
                def dig(key, *keys)
         | 
| 204 234 | 
             
                  table.dig(key.to_sym, *keys)
         | 
| 205 235 | 
             
                end
         | 
| @@ -211,6 +241,8 @@ module CMDx | |
| 211 241 | 
             
                # @example
         | 
| 212 242 | 
             
                #   context = Context.new(name: "John", age: 30)
         | 
| 213 243 | 
             
                #   context.to_s # => "name: John, age: 30"
         | 
| 244 | 
            +
                #
         | 
| 245 | 
            +
                # @rbs () -> String
         | 
| 214 246 | 
             
                def to_s
         | 
| 215 247 | 
             
                  Utils::Format.to_str(to_h)
         | 
| 216 248 | 
             
                end
         | 
| @@ -227,6 +259,8 @@ module CMDx | |
| 227 259 | 
             
                # @yield [Object] optional block
         | 
| 228 260 | 
             
                #
         | 
| 229 261 | 
             
                # @return [Object] the result of the method call
         | 
| 262 | 
            +
                #
         | 
| 263 | 
            +
                # @rbs (Symbol method_name, *untyped args, **untyped _kwargs) ?{ () -> untyped } -> untyped
         | 
| 230 264 | 
             
                def method_missing(method_name, *args, **_kwargs, &)
         | 
| 231 265 | 
             
                  fetch(method_name) do
         | 
| 232 266 | 
             
                    store(method_name[0..-2], args.first) if method_name.end_with?("=")
         | 
| @@ -244,6 +278,8 @@ module CMDx | |
| 244 278 | 
             
                #   context = Context.new(name: "John")
         | 
| 245 279 | 
             
                #   context.respond_to?(:name) # => true
         | 
| 246 280 | 
             
                #   context.respond_to?(:age) # => false
         | 
| 281 | 
            +
                #
         | 
| 282 | 
            +
                # @rbs (Symbol method_name, ?bool include_private) -> bool
         | 
| 247 283 | 
             
                def respond_to_missing?(method_name, include_private = false)
         | 
| 248 284 | 
             
                  key?(method_name) || super
         | 
| 249 285 | 
             
                end
         | 
    
        data/lib/cmdx/deprecator.rb
    CHANGED
    
    | @@ -10,6 +10,7 @@ module CMDx | |
| 10 10 |  | 
| 11 11 | 
             
                extend self
         | 
| 12 12 |  | 
| 13 | 
            +
                # @rbs EVAL: Proc
         | 
| 13 14 | 
             
                EVAL = proc do |target, callable|
         | 
| 14 15 | 
             
                  case callable
         | 
| 15 16 | 
             
                  when /raise|log|warn/ then callable
         | 
| @@ -45,6 +46,8 @@ module CMDx | |
| 45 46 | 
             
                #   end
         | 
| 46 47 | 
             
                #
         | 
| 47 48 | 
             
                #   MyTask.new # => [MyTask] DEPRECATED: migrate to a replacement or discontinue use
         | 
| 49 | 
            +
                #
         | 
| 50 | 
            +
                # @rbs (Task task) -> void
         | 
| 48 51 | 
             
                def restrict(task)
         | 
| 49 52 | 
             
                  type = EVAL.call(task, task.class.settings[:deprecate])
         | 
| 50 53 |  | 
    
        data/lib/cmdx/errors.rb
    CHANGED
    
    | @@ -8,11 +8,21 @@ module CMDx | |
| 8 8 |  | 
| 9 9 | 
             
                extend Forwardable
         | 
| 10 10 |  | 
| 11 | 
            +
                # Returns the internal hash of error messages by attribute.
         | 
| 12 | 
            +
                #
         | 
| 13 | 
            +
                # @return [Hash{Symbol => Set<String>}] Hash mapping attribute names to error message sets
         | 
| 14 | 
            +
                #
         | 
| 15 | 
            +
                # @example
         | 
| 16 | 
            +
                #   errors.messages # => { email: #<Set: ["must be valid", "is required"]> }
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                # @rbs @messages: Hash[Symbol, Set[String]]
         | 
| 11 19 | 
             
                attr_reader :messages
         | 
| 12 20 |  | 
| 13 21 | 
             
                def_delegators :messages, :empty?
         | 
| 14 22 |  | 
| 15 23 | 
             
                # Initialize a new error collection.
         | 
| 24 | 
            +
                #
         | 
| 25 | 
            +
                # @rbs () -> void
         | 
| 16 26 | 
             
                def initialize
         | 
| 17 27 | 
             
                  @messages = {}
         | 
| 18 28 | 
             
                end
         | 
| @@ -26,6 +36,8 @@ module CMDx | |
| 26 36 | 
             
                #   errors = CMDx::Errors.new
         | 
| 27 37 | 
             
                #   errors.add(:email, "must be valid format")
         | 
| 28 38 | 
             
                #   errors.add(:email, "cannot be blank")
         | 
| 39 | 
            +
                #
         | 
| 40 | 
            +
                # @rbs (Symbol attribute, String message) -> void
         | 
| 29 41 | 
             
                def add(attribute, message)
         | 
| 30 42 | 
             
                  return if message.empty?
         | 
| 31 43 |  | 
| @@ -42,6 +54,8 @@ module CMDx | |
| 42 54 | 
             
                # @example
         | 
| 43 55 | 
             
                #   errors.for?(:email) # => true
         | 
| 44 56 | 
             
                #   errors.for?(:name)  # => false
         | 
| 57 | 
            +
                #
         | 
| 58 | 
            +
                # @rbs (Symbol attribute) -> bool
         | 
| 45 59 | 
             
                def for?(attribute)
         | 
| 46 60 | 
             
                  return false unless messages.key?(attribute)
         | 
| 47 61 |  | 
| @@ -54,6 +68,8 @@ module CMDx | |
| 54 68 | 
             
                #
         | 
| 55 69 | 
             
                # @example
         | 
| 56 70 | 
             
                #   errors.full_messages # => { email: ["email must be valid format", "email cannot be blank"] }
         | 
| 71 | 
            +
                #
         | 
| 72 | 
            +
                # @rbs () -> Hash[Symbol, Array[String]]
         | 
| 57 73 | 
             
                def full_messages
         | 
| 58 74 | 
             
                  messages.each_with_object({}) do |(attribute, messages), hash|
         | 
| 59 75 | 
             
                    hash[attribute] = messages.map { |message| "#{attribute} #{message}" }
         | 
| @@ -66,6 +82,8 @@ module CMDx | |
| 66 82 | 
             
                #
         | 
| 67 83 | 
             
                # @example
         | 
| 68 84 | 
             
                #   errors.to_h # => { email: ["must be valid format", "cannot be blank"] }
         | 
| 85 | 
            +
                #
         | 
| 86 | 
            +
                # @rbs () -> Hash[Symbol, Array[String]]
         | 
| 69 87 | 
             
                def to_h
         | 
| 70 88 | 
             
                  messages.transform_values(&:to_a)
         | 
| 71 89 | 
             
                end
         | 
| @@ -78,6 +96,8 @@ module CMDx | |
| 78 96 | 
             
                # @example
         | 
| 79 97 | 
             
                #   errors.to_hash # => { email: ["must be valid format", "cannot be blank"] }
         | 
| 80 98 | 
             
                #   errors.to_hash(true) # => { email: ["email must be valid format", "email cannot be blank"] }
         | 
| 99 | 
            +
                #
         | 
| 100 | 
            +
                # @rbs (?bool full) -> Hash[Symbol, Array[String]]
         | 
| 81 101 | 
             
                def to_hash(full = false)
         | 
| 82 102 | 
             
                  full ? full_messages : to_h
         | 
| 83 103 | 
             
                end
         | 
| @@ -88,6 +108,8 @@ module CMDx | |
| 88 108 | 
             
                #
         | 
| 89 109 | 
             
                # @example
         | 
| 90 110 | 
             
                #   errors.to_s # => "email must be valid format. email cannot be blank"
         | 
| 111 | 
            +
                #
         | 
| 112 | 
            +
                # @rbs () -> String
         | 
| 91 113 | 
             
                def to_s
         | 
| 92 114 | 
             
                  full_messages.values.flatten.join(". ")
         | 
| 93 115 | 
             
                end
         | 
    
        data/lib/cmdx/executor.rb
    CHANGED
    
    | @@ -8,6 +8,14 @@ module CMDx | |
| 8 8 | 
             
              # and proper error handling for different types of failures.
         | 
| 9 9 | 
             
              class Executor
         | 
| 10 10 |  | 
| 11 | 
            +
                # Returns the task being executed.
         | 
| 12 | 
            +
                #
         | 
| 13 | 
            +
                # @return [Task] The task instance
         | 
| 14 | 
            +
                #
         | 
| 15 | 
            +
                # @example
         | 
| 16 | 
            +
                #   executor.task.id # => "abc123"
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                # @rbs @task: Task
         | 
| 11 19 | 
             
                attr_reader :task
         | 
| 12 20 |  | 
| 13 21 | 
             
                # @param task [CMDx::Task] The task to execute
         | 
| @@ -16,6 +24,8 @@ module CMDx | |
| 16 24 | 
             
                #
         | 
| 17 25 | 
             
                # @example
         | 
| 18 26 | 
             
                #   executor = CMDx::Executor.new(my_task)
         | 
| 27 | 
            +
                #
         | 
| 28 | 
            +
                # @rbs (Task task) -> void
         | 
| 19 29 | 
             
                def initialize(task)
         | 
| 20 30 | 
             
                  @task = task
         | 
| 21 31 | 
             
                end
         | 
| @@ -32,6 +42,8 @@ module CMDx | |
| 32 42 | 
             
                # @example
         | 
| 33 43 | 
             
                #   CMDx::Executor.execute(my_task)
         | 
| 34 44 | 
             
                #   CMDx::Executor.execute(my_task, raise: true)
         | 
| 45 | 
            +
                #
         | 
| 46 | 
            +
                # @rbs (Task task, raise: bool) -> Result
         | 
| 35 47 | 
             
                def self.execute(task, raise: false)
         | 
| 36 48 | 
             
                  instance = new(task)
         | 
| 37 49 | 
             
                  raise ? instance.execute! : instance.execute
         | 
| @@ -44,6 +56,8 @@ module CMDx | |
| 44 56 | 
             
                # @example
         | 
| 45 57 | 
             
                #   executor = CMDx::Executor.new(my_task)
         | 
| 46 58 | 
             
                #   result = executor.execute
         | 
| 59 | 
            +
                #
         | 
| 60 | 
            +
                # @rbs () -> Result
         | 
| 47 61 | 
             
                def execute
         | 
| 48 62 | 
             
                  task.class.settings[:middlewares].call!(task) do
         | 
| 49 63 | 
             
                    pre_execution! unless @pre_execution
         | 
| @@ -73,6 +87,8 @@ module CMDx | |
| 73 87 | 
             
                # @example
         | 
| 74 88 | 
             
                #   executor = CMDx::Executor.new(my_task)
         | 
| 75 89 | 
             
                #   result = executor.execute!
         | 
| 90 | 
            +
                #
         | 
| 91 | 
            +
                # @rbs () -> Result
         | 
| 76 92 | 
             
                def execute!
         | 
| 77 93 | 
             
                  task.class.settings[:middlewares].call!(task) do
         | 
| 78 94 | 
             
                    pre_execution! unless @pre_execution
         | 
| @@ -102,13 +118,12 @@ module CMDx | |
| 102 118 | 
             
                #
         | 
| 103 119 | 
             
                # @return [Boolean] Whether execution should halt
         | 
| 104 120 | 
             
                #
         | 
| 105 | 
            -
                # @ | 
| 106 | 
            -
                #   halt_execution?(fault_exception)
         | 
| 121 | 
            +
                # @rbs (Exception exception) -> bool
         | 
| 107 122 | 
             
                def halt_execution?(exception)
         | 
| 108 | 
            -
                   | 
| 109 | 
            -
                   | 
| 123 | 
            +
                  statuses = task.class.settings[:breakpoints] || task.class.settings[:task_breakpoints]
         | 
| 124 | 
            +
                  statuses = Array(statuses).map(&:to_s).uniq
         | 
| 110 125 |  | 
| 111 | 
            -
                   | 
| 126 | 
            +
                  statuses.include?(exception.result.status)
         | 
| 112 127 | 
             
                end
         | 
| 113 128 |  | 
| 114 129 | 
             
                # Determines if execution should be retried based on retry configuration.
         | 
| @@ -117,8 +132,7 @@ module CMDx | |
| 117 132 | 
             
                #
         | 
| 118 133 | 
             
                # @return [Boolean] Whether execution should be retried
         | 
| 119 134 | 
             
                #
         | 
| 120 | 
            -
                # @ | 
| 121 | 
            -
                #   retry_execution?(standard_error)
         | 
| 135 | 
            +
                # @rbs (Exception exception) -> bool
         | 
| 122 136 | 
             
                def retry_execution?(exception)
         | 
| 123 137 | 
             
                  available_retries = (task.class.settings[:retries] || 0).to_i
         | 
| 124 138 | 
             
                  return false unless available_retries.positive?
         | 
| @@ -137,7 +151,18 @@ module CMDx | |
| 137 151 | 
             
                    task.to_h.merge!(reason:, remaining_retries:)
         | 
| 138 152 | 
             
                  end
         | 
| 139 153 |  | 
| 140 | 
            -
                  jitter = task.class.settings[:retry_jitter] | 
| 154 | 
            +
                  jitter = task.class.settings[:retry_jitter]
         | 
| 155 | 
            +
                  jitter =
         | 
| 156 | 
            +
                    if jitter.is_a?(Symbol)
         | 
| 157 | 
            +
                      task.send(jitter, current_retries)
         | 
| 158 | 
            +
                    elsif jitter.is_a?(Proc)
         | 
| 159 | 
            +
                      task.instance_exec(current_retries, &jitter)
         | 
| 160 | 
            +
                    elsif jitter.respond_to?(:call)
         | 
| 161 | 
            +
                      jitter.call(task, current_retries)
         | 
| 162 | 
            +
                    else
         | 
| 163 | 
            +
                      jitter.to_f * current_retries
         | 
| 164 | 
            +
                    end
         | 
| 165 | 
            +
             | 
| 141 166 | 
             
                  sleep(jitter) if jitter.positive?
         | 
| 142 167 |  | 
| 143 168 | 
             
                  true
         | 
| @@ -149,8 +174,7 @@ module CMDx | |
| 149 174 | 
             
                #
         | 
| 150 175 | 
             
                # @raise [Exception] The provided exception
         | 
| 151 176 | 
             
                #
         | 
| 152 | 
            -
                # @ | 
| 153 | 
            -
                #   raise_exception(standard_error)
         | 
| 177 | 
            +
                # @rbs (Exception exception) -> void
         | 
| 154 178 | 
             
                def raise_exception(exception)
         | 
| 155 179 | 
             
                  Chain.clear
         | 
| 156 180 |  | 
| @@ -165,6 +189,8 @@ module CMDx | |
| 165 189 | 
             
                #
         | 
| 166 190 | 
             
                # @example
         | 
| 167 191 | 
             
                #   invoke_callbacks(:before_execution)
         | 
| 192 | 
            +
                #
         | 
| 193 | 
            +
                # @rbs (Symbol type) -> void
         | 
| 168 194 | 
             
                def invoke_callbacks(type)
         | 
| 169 195 | 
             
                  task.class.settings[:callbacks].invoke(type, task)
         | 
| 170 196 | 
             
                end
         | 
| @@ -172,11 +198,15 @@ module CMDx | |
| 172 198 | 
             
                private
         | 
| 173 199 |  | 
| 174 200 | 
             
                # Lazy loaded repeator instance to handle retries.
         | 
| 201 | 
            +
                #
         | 
| 202 | 
            +
                # @rbs () -> untyped
         | 
| 175 203 | 
             
                def repeator
         | 
| 176 204 | 
             
                  @repeator ||= Repeator.new(task)
         | 
| 177 205 | 
             
                end
         | 
| 178 206 |  | 
| 179 207 | 
             
                # Performs pre-execution tasks including validation and attribute verification.
         | 
| 208 | 
            +
                #
         | 
| 209 | 
            +
                # @rbs () -> void
         | 
| 180 210 | 
             
                def pre_execution!
         | 
| 181 211 | 
             
                  @pre_execution = true
         | 
| 182 212 |  | 
| @@ -195,6 +225,8 @@ module CMDx | |
| 195 225 | 
             
                end
         | 
| 196 226 |  | 
| 197 227 | 
             
                # Executes the main task logic.
         | 
| 228 | 
            +
                #
         | 
| 229 | 
            +
                # @rbs () -> void
         | 
| 198 230 | 
             
                def execution!
         | 
| 199 231 | 
             
                  invoke_callbacks(:before_execution)
         | 
| 200 232 |  | 
| @@ -203,6 +235,8 @@ module CMDx | |
| 203 235 | 
             
                end
         | 
| 204 236 |  | 
| 205 237 | 
             
                # Performs post-execution tasks including callback invocation.
         | 
| 238 | 
            +
                #
         | 
| 239 | 
            +
                # @rbs () -> void
         | 
| 206 240 | 
             
                def post_execution!
         | 
| 207 241 | 
             
                  invoke_callbacks(:"on_#{task.result.state}")
         | 
| 208 242 | 
             
                  invoke_callbacks(:on_executed) if task.result.executed?
         | 
| @@ -212,21 +246,29 @@ module CMDx | |
| 212 246 | 
             
                  invoke_callbacks(:on_bad) if task.result.bad?
         | 
| 213 247 | 
             
                end
         | 
| 214 248 |  | 
| 215 | 
            -
                # Finalizes execution by freezing the task and  | 
| 249 | 
            +
                # Finalizes execution by freezing the task, logging results, and rolling back work.
         | 
| 250 | 
            +
                #
         | 
| 251 | 
            +
                # @rbs () -> Result
         | 
| 216 252 | 
             
                def finalize_execution!
         | 
| 217 253 | 
             
                  log_execution!
         | 
| 218 254 | 
             
                  log_backtrace! if task.class.settings[:backtrace]
         | 
| 219 255 |  | 
| 220 256 | 
             
                  freeze_execution!
         | 
| 221 257 | 
             
                  clear_chain!
         | 
| 258 | 
            +
             | 
| 259 | 
            +
                  rollback_execution!
         | 
| 222 260 | 
             
                end
         | 
| 223 261 |  | 
| 224 262 | 
             
                # Logs the execution result at the configured log level.
         | 
| 263 | 
            +
                #
         | 
| 264 | 
            +
                # @rbs () -> void
         | 
| 225 265 | 
             
                def log_execution!
         | 
| 226 266 | 
             
                  task.logger.info { task.result.to_h }
         | 
| 227 267 | 
             
                end
         | 
| 228 268 |  | 
| 229 269 | 
             
                # Logs the backtrace of the exception if the task failed.
         | 
| 270 | 
            +
                #
         | 
| 271 | 
            +
                # @rbs () -> void
         | 
| 230 272 | 
             
                def log_backtrace!
         | 
| 231 273 | 
             
                  return unless task.result.failed?
         | 
| 232 274 |  | 
| @@ -244,6 +286,8 @@ module CMDx | |
| 244 286 | 
             
                end
         | 
| 245 287 |  | 
| 246 288 | 
             
                # Freezes the task and its associated objects to prevent modifications.
         | 
| 289 | 
            +
                #
         | 
| 290 | 
            +
                # @rbs () -> void
         | 
| 247 291 | 
             
                def freeze_execution!
         | 
| 248 292 | 
             
                  # Stubbing on frozen objects is not allowed in most test environments.
         | 
| 249 293 | 
             
                  skip_freezing = ENV.fetch("SKIP_CMDX_FREEZING", false)
         | 
| @@ -260,11 +304,27 @@ module CMDx | |
| 260 304 | 
             
                  task.chain.freeze
         | 
| 261 305 | 
             
                end
         | 
| 262 306 |  | 
| 307 | 
            +
                # Clears the chain if the task is the outermost (top-level) task.
         | 
| 308 | 
            +
                #
         | 
| 309 | 
            +
                # @rbs () -> void
         | 
| 263 310 | 
             
                def clear_chain!
         | 
| 264 311 | 
             
                  return unless task.result.index.zero?
         | 
| 265 312 |  | 
| 266 313 | 
             
                  Chain.clear
         | 
| 267 314 | 
             
                end
         | 
| 268 315 |  | 
| 316 | 
            +
                # Rolls back the work of a task.
         | 
| 317 | 
            +
                #
         | 
| 318 | 
            +
                # @rbs () -> void
         | 
| 319 | 
            +
                def rollback_execution!
         | 
| 320 | 
            +
                  return unless task.respond_to?(:rollback)
         | 
| 321 | 
            +
             | 
| 322 | 
            +
                  statuses = task.class.settings[:rollback_on]
         | 
| 323 | 
            +
                  statuses = Array(statuses).map(&:to_s).uniq
         | 
| 324 | 
            +
                  return unless statuses.include?(task.result.status)
         | 
| 325 | 
            +
             | 
| 326 | 
            +
                  task.rollback
         | 
| 327 | 
            +
                end
         | 
| 328 | 
            +
             | 
| 269 329 | 
             
              end
         | 
| 270 330 | 
             
            end
         | 
    
        data/lib/cmdx/faults.rb
    CHANGED
    
    | @@ -11,6 +11,14 @@ module CMDx | |
| 11 11 |  | 
| 12 12 | 
             
                extend Forwardable
         | 
| 13 13 |  | 
| 14 | 
            +
                # Returns the result that caused this fault.
         | 
| 15 | 
            +
                #
         | 
| 16 | 
            +
                # @return [Result] The result instance
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                # @example
         | 
| 19 | 
            +
                #   fault.result.reason # => "Validation failed"
         | 
| 20 | 
            +
                #
         | 
| 21 | 
            +
                # @rbs @result: Result
         | 
| 14 22 | 
             
                attr_reader :result
         | 
| 15 23 |  | 
| 16 24 | 
             
                def_delegators :result, :task, :context, :chain
         | 
| @@ -24,6 +32,8 @@ module CMDx | |
| 24 32 | 
             
                # @example
         | 
| 25 33 | 
             
                #   fault = Fault.new(task_result)
         | 
| 26 34 | 
             
                #   fault.result.reason # => "Task validation failed"
         | 
| 35 | 
            +
                #
         | 
| 36 | 
            +
                # @rbs (Result result) -> void
         | 
| 27 37 | 
             
                def initialize(result)
         | 
| 28 38 | 
             
                  @result = result
         | 
| 29 39 |  | 
| @@ -41,6 +51,8 @@ module CMDx | |
| 41 51 | 
             
                  # @example
         | 
| 42 52 | 
             
                  #   Fault.for?(UserTask, AdminUserTask)
         | 
| 43 53 | 
             
                  #   # => true if fault.task is a UserTask or AdminUserTask
         | 
| 54 | 
            +
                  #
         | 
| 55 | 
            +
                  # @rbs (*Class tasks) -> Class
         | 
| 44 56 | 
             
                  def for?(*tasks)
         | 
| 45 57 | 
             
                    temp_fault = Class.new(self) do
         | 
| 46 58 | 
             
                      def self.===(other)
         | 
| @@ -62,6 +74,8 @@ module CMDx | |
| 62 74 | 
             
                  # @example
         | 
| 63 75 | 
             
                  #   Fault.matches? { |fault| fault.result.metadata[:critical] }
         | 
| 64 76 | 
             
                  #   # => true if fault has critical metadata
         | 
| 77 | 
            +
                  #
         | 
| 78 | 
            +
                  # @rbs () { (Fault) -> bool } -> Class
         | 
| 65 79 | 
             
                  def matches?(&block)
         | 
| 66 80 | 
             
                    raise ArgumentError, "block required" unless block_given?
         | 
| 67 81 |  | 
    
        data/lib/cmdx/identifier.rb
    CHANGED
    
    
    
        data/lib/cmdx/locale.rb
    CHANGED
    
    | @@ -8,6 +8,7 @@ module CMDx | |
| 8 8 |  | 
| 9 9 | 
             
                extend self
         | 
| 10 10 |  | 
| 11 | 
            +
                # @rbs EN: Hash[String, untyped]
         | 
| 11 12 | 
             
                EN = YAML.load_file(CMDx.gem_path.join("lib/locales/en.yml")).freeze
         | 
| 12 13 | 
             
                private_constant :EN
         | 
| 13 14 |  | 
| @@ -34,6 +35,8 @@ module CMDx | |
| 34 35 | 
             
                # @example With fallback
         | 
| 35 36 | 
             
                #   Locale.translate("missing.key", default: "Custom fallback message")
         | 
| 36 37 | 
             
                #   # => "Custom fallback message"
         | 
| 38 | 
            +
                #
         | 
| 39 | 
            +
                # @rbs ((String | Symbol) key, **untyped options) -> String
         | 
| 37 40 | 
             
                def translate(key, **options)
         | 
| 38 41 | 
             
                  options[:default] ||= EN.dig("en", *key.to_s.split("."))
         | 
| 39 42 | 
             
                  return ::I18n.t(key, **options) if defined?(::I18n)
         | 
| @@ -21,6 +21,8 @@ module CMDx | |
| 21 21 | 
             
                  # @example Basic usage
         | 
| 22 22 | 
             
                  #   logger_formatter.call("INFO", Time.now, "MyApp", "User logged in")
         | 
| 23 23 | 
             
                  #   # => '{"severity":"INFO","timestamp":"2024-01-15T10:30:45.123456Z","progname":"MyApp","pid":12345,"message":"User logged in"}\n'
         | 
| 24 | 
            +
                  #
         | 
| 25 | 
            +
                  # @rbs (String severity, Time time, String? progname, String message) -> String
         | 
| 24 26 | 
             
                  def call(severity, time, progname, message)
         | 
| 25 27 | 
             
                    hash = {
         | 
| 26 28 | 
             
                      severity:,
         | 
| @@ -21,6 +21,8 @@ module CMDx | |
| 21 21 | 
             
                  # @example Basic usage
         | 
| 22 22 | 
             
                  #   logger_formatter.call("INFO", Time.now, "MyApp", "User logged in")
         | 
| 23 23 | 
             
                  #   # => "severity=INFO timestamp=2024-01-15T10:30:45.123456Z progname=MyApp pid=12345 message=User logged in\n"
         | 
| 24 | 
            +
                  #
         | 
| 25 | 
            +
                  # @rbs (String severity, Time time, String? progname, String message) -> String
         | 
| 24 26 | 
             
                  def call(severity, time, progname, message)
         | 
| 25 27 | 
             
                    hash = {
         | 
| 26 28 | 
             
                      severity:,
         | 
| @@ -21,6 +21,8 @@ module CMDx | |
| 21 21 | 
             
                  # @example Basic usage
         | 
| 22 22 | 
             
                  #   logger_formatter.call("INFO", Time.now, "MyApp", "User logged in")
         | 
| 23 23 | 
             
                  #   # => "I, [2024-01-15T10:30:45.123456Z #12345] INFO -- MyApp: User logged in\n"
         | 
| 24 | 
            +
                  #
         | 
| 25 | 
            +
                  # @rbs (String severity, Time time, String? progname, String message) -> String
         | 
| 24 26 | 
             
                  def call(severity, time, progname, message)
         | 
| 25 27 | 
             
                    "#{severity[0]}, [#{time.utc.iso8601(6)} ##{Process.pid}] #{severity} -- #{progname}: #{message}\n"
         | 
| 26 28 | 
             
                  end
         | 
| @@ -22,6 +22,8 @@ module CMDx | |
| 22 22 | 
             
                  # @example Basic usage
         | 
| 23 23 | 
             
                  #   logger_formatter.call("INFO", Time.now, "MyApp", "User logged in")
         | 
| 24 24 | 
             
                  #   # => '{"severity":"INFO","progname":"MyApp","pid":12345,"message":"User logged in","@version":"1","@timestamp":"2024-01-15T10:30:45.123456Z"}\n'
         | 
| 25 | 
            +
                  #
         | 
| 26 | 
            +
                  # @rbs (String severity, Time time, String? progname, String message) -> String
         | 
| 25 27 | 
             
                  def call(severity, time, progname, message)
         | 
| 26 28 | 
             
                    hash = {
         | 
| 27 29 | 
             
                      severity:,
         | 
| @@ -22,6 +22,8 @@ module CMDx | |
| 22 22 | 
             
                  # @example Basic usage
         | 
| 23 23 | 
             
                  #   logger_formatter.call("INFO", Time.now, "MyApp", "User logged in")
         | 
| 24 24 | 
             
                  #   # => "User logged in\n"
         | 
| 25 | 
            +
                  #
         | 
| 26 | 
            +
                  # @rbs (String severity, Time time, String? progname, String message) -> String
         | 
| 25 27 | 
             
                  def call(severity, time, progname, message)
         | 
| 26 28 | 
             
                    "#{message}\n"
         | 
| 27 29 | 
             
                  end
         | 
| @@ -9,6 +9,14 @@ module CMDx | |
| 9 9 | 
             
              # they were registered.
         | 
| 10 10 | 
             
              class MiddlewareRegistry
         | 
| 11 11 |  | 
| 12 | 
            +
                # Returns the ordered collection of middleware entries.
         | 
| 13 | 
            +
                #
         | 
| 14 | 
            +
                # @return [Array<Array>] Array of middleware-options pairs
         | 
| 15 | 
            +
                #
         | 
| 16 | 
            +
                # @example
         | 
| 17 | 
            +
                #   registry.registry # => [[LoggingMiddleware, {level: :debug}], [AuthMiddleware, {}]]
         | 
| 18 | 
            +
                #
         | 
| 19 | 
            +
                # @rbs @registry: Array[Array[untyped]]
         | 
| 12 20 | 
             
                attr_reader :registry
         | 
| 13 21 | 
             
                alias to_a registry
         | 
| 14 22 |  | 
| @@ -19,6 +27,8 @@ module CMDx | |
| 19 27 | 
             
                # @example
         | 
| 20 28 | 
             
                #   registry = MiddlewareRegistry.new
         | 
| 21 29 | 
             
                #   registry = MiddlewareRegistry.new([[MyMiddleware, {option: 'value'}]])
         | 
| 30 | 
            +
                #
         | 
| 31 | 
            +
                # @rbs (?Array[Array[untyped]] registry) -> void
         | 
| 22 32 | 
             
                def initialize(registry = [])
         | 
| 23 33 | 
             
                  @registry = registry
         | 
| 24 34 | 
             
                end
         | 
| @@ -29,6 +39,8 @@ module CMDx | |
| 29 39 | 
             
                #
         | 
| 30 40 | 
             
                # @example
         | 
| 31 41 | 
             
                #   new_registry = registry.dup
         | 
| 42 | 
            +
                #
         | 
| 43 | 
            +
                # @rbs () -> MiddlewareRegistry
         | 
| 32 44 | 
             
                def dup
         | 
| 33 45 | 
             
                  self.class.new(registry.map(&:dup))
         | 
| 34 46 | 
             
                end
         | 
| @@ -46,6 +58,8 @@ module CMDx | |
| 46 58 | 
             
                # @example
         | 
| 47 59 | 
             
                #   registry.register(LoggingMiddleware, at: 0, log_level: :debug)
         | 
| 48 60 | 
             
                #   registry.register(AuthMiddleware, at: -1, timeout: 30)
         | 
| 61 | 
            +
                #
         | 
| 62 | 
            +
                # @rbs (untyped middleware, ?at: Integer, **untyped options) -> self
         | 
| 49 63 | 
             
                def register(middleware, at: -1, **options)
         | 
| 50 64 | 
             
                  registry.insert(at, [middleware, options])
         | 
| 51 65 | 
             
                  self
         | 
| @@ -59,6 +73,8 @@ module CMDx | |
| 59 73 | 
             
                #
         | 
| 60 74 | 
             
                # @example
         | 
| 61 75 | 
             
                #   registry.deregister(LoggingMiddleware)
         | 
| 76 | 
            +
                #
         | 
| 77 | 
            +
                # @rbs (untyped middleware) -> self
         | 
| 62 78 | 
             
                def deregister(middleware)
         | 
| 63 79 | 
             
                  registry.reject! { |mw, _opts| mw == middleware }
         | 
| 64 80 | 
             
                  self
         | 
| @@ -79,6 +95,8 @@ module CMDx | |
| 79 95 | 
             
                #   result = registry.call!(my_task) do |processed_task|
         | 
| 80 96 | 
             
                #     processed_task.execute
         | 
| 81 97 | 
             
                #   end
         | 
| 98 | 
            +
                #
         | 
| 99 | 
            +
                # @rbs (untyped task) { (untyped) -> untyped } -> untyped
         | 
| 82 100 | 
             
                def call!(task, &)
         | 
| 83 101 | 
             
                  raise ArgumentError, "block required" unless block_given?
         | 
| 84 102 |  | 
| @@ -96,6 +114,8 @@ module CMDx | |
| 96 114 | 
             
                # @yieldparam task [Object] The processed task object
         | 
| 97 115 | 
             
                #
         | 
| 98 116 | 
             
                # @return [Object] Result of the block execution or next middleware call
         | 
| 117 | 
            +
                #
         | 
| 118 | 
            +
                # @rbs (Integer index, untyped task) { (untyped) -> untyped } -> untyped
         | 
| 99 119 | 
             
                def recursively_call_middleware(index, task, &block)
         | 
| 100 120 | 
             
                  return yield(task) if index >= registry.size
         | 
| 101 121 |  | 
| @@ -12,6 +12,7 @@ module CMDx | |
| 12 12 |  | 
| 13 13 | 
             
                  extend self
         | 
| 14 14 |  | 
| 15 | 
            +
                  # @rbs THREAD_KEY: Symbol
         | 
| 15 16 | 
             
                  THREAD_KEY = :cmdx_correlate
         | 
| 16 17 |  | 
| 17 18 | 
             
                  # Retrieves the current correlation ID from thread-local storage.
         | 
| @@ -20,6 +21,8 @@ module CMDx | |
| 20 21 | 
             
                  #
         | 
| 21 22 | 
             
                  # @example Get current correlation ID
         | 
| 22 23 | 
             
                  #   Correlate.id # => "550e8400-e29b-41d4-a716-446655440000"
         | 
| 24 | 
            +
                  #
         | 
| 25 | 
            +
                  # @rbs () -> String?
         | 
| 23 26 | 
             
                  def id
         | 
| 24 27 | 
             
                    Thread.current[THREAD_KEY]
         | 
| 25 28 | 
             
                  end
         | 
| @@ -31,6 +34,8 @@ module CMDx | |
| 31 34 | 
             
                  #
         | 
| 32 35 | 
             
                  # @example Set correlation ID
         | 
| 33 36 | 
             
                  #   Correlate.id = "abc-123-def"
         | 
| 37 | 
            +
                  #
         | 
| 38 | 
            +
                  # @rbs (String id) -> String
         | 
| 34 39 | 
             
                  def id=(id)
         | 
| 35 40 | 
             
                    Thread.current[THREAD_KEY] = id
         | 
| 36 41 | 
             
                  end
         | 
| @@ -41,6 +46,8 @@ module CMDx | |
| 41 46 | 
             
                  #
         | 
| 42 47 | 
             
                  # @example Clear correlation ID
         | 
| 43 48 | 
             
                  #   Correlate.clear
         | 
| 49 | 
            +
                  #
         | 
| 50 | 
            +
                  # @rbs () -> nil
         | 
| 44 51 | 
             
                  def clear
         | 
| 45 52 | 
             
                    Thread.current[THREAD_KEY] = nil
         | 
| 46 53 | 
             
                  end
         | 
| @@ -58,6 +65,8 @@ module CMDx | |
| 58 65 | 
             
                  #     perform_operation
         | 
| 59 66 | 
             
                  #   end
         | 
| 60 67 | 
             
                  #   # Previous ID is restored
         | 
| 68 | 
            +
                  #
         | 
| 69 | 
            +
                  # @rbs (String new_id) { () -> untyped } -> untyped
         | 
| 61 70 | 
             
                  def use(new_id)
         | 
| 62 71 | 
             
                    old_id = id
         | 
| 63 72 | 
             
                    self.id = new_id
         | 
| @@ -92,6 +101,8 @@ module CMDx | |
| 92 101 | 
             
                  #   Correlate.call(task, id: -> { "dynamic-#{Time.now.to_i}" }, &block)
         | 
| 93 102 | 
             
                  # @example Conditional correlation
         | 
| 94 103 | 
             
                  #   Correlate.call(task, if: :enable_correlation, &block)
         | 
| 104 | 
            +
                  #
         | 
| 105 | 
            +
                  # @rbs (Task task, **untyped options) { () -> untyped } -> untyped
         | 
| 95 106 | 
             
                  def call(task, **options, &)
         | 
| 96 107 | 
             
                    return yield unless Utils::Condition.evaluate(task, options)
         | 
| 97 108 |  | 
| @@ -32,6 +32,8 @@ module CMDx | |
| 32 32 | 
             
                  #   Runtime.call(task, if: :enable_profiling, &block)
         | 
| 33 33 | 
             
                  # @example Disable runtime measurement
         | 
| 34 34 | 
             
                  #   Runtime.call(task, unless: :skip_profiling, &block)
         | 
| 35 | 
            +
                  #
         | 
| 36 | 
            +
                  # @rbs (Task task, **untyped options) { () -> untyped } -> untyped
         | 
| 35 37 | 
             
                  def call(task, **options)
         | 
| 36 38 | 
             
                    return yield unless Utils::Condition.evaluate(task, options)
         | 
| 37 39 |  | 
| @@ -49,6 +51,8 @@ module CMDx | |
| 49 51 | 
             
                  # timing measurements that are not affected by system clock changes.
         | 
| 50 52 | 
             
                  #
         | 
| 51 53 | 
             
                  # @return [Integer] Current monotonic time in milliseconds
         | 
| 54 | 
            +
                  #
         | 
| 55 | 
            +
                  # @rbs () -> Integer
         | 
| 52 56 | 
             
                  def monotonic_time
         | 
| 53 57 | 
             
                    Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
         | 
| 54 58 | 
             
                  end
         |