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
| @@ -21,6 +21,8 @@ module CMDx | |
| 21 21 | 
             
                  extend self
         | 
| 22 22 |  | 
| 23 23 | 
             
                  # Default timeout limit in seconds when none is specified.
         | 
| 24 | 
            +
                  #
         | 
| 25 | 
            +
                  # @rbs DEFAULT_LIMIT: Integer
         | 
| 24 26 | 
             
                  DEFAULT_LIMIT = 3
         | 
| 25 27 |  | 
| 26 28 | 
             
                  # Middleware entry point that enforces execution time limits.
         | 
| @@ -51,6 +53,8 @@ module CMDx | |
| 51 53 | 
             
                  #   Timeout.call(task, seconds: -> { calculate_timeout }, &block)
         | 
| 52 54 | 
             
                  # @example Conditional timeout control
         | 
| 53 55 | 
             
                  #   Timeout.call(task, if: :enable_timeout, &block)
         | 
| 56 | 
            +
                  #
         | 
| 57 | 
            +
                  # @rbs (Task task, **untyped options) { () -> untyped } -> untyped
         | 
| 54 58 | 
             
                  def call(task, **options, &)
         | 
| 55 59 | 
             
                    return yield unless Utils::Condition.evaluate(task, options)
         | 
| 56 60 |  | 
    
        data/lib/cmdx/pipeline.rb
    CHANGED
    
    | @@ -6,7 +6,14 @@ module CMDx | |
| 6 6 | 
             
              # and handling breakpoints that can interrupt execution at specific task statuses.
         | 
| 7 7 | 
             
              class Pipeline
         | 
| 8 8 |  | 
| 9 | 
            -
                #  | 
| 9 | 
            +
                # Returns the workflow being executed by this pipeline.
         | 
| 10 | 
            +
                #
         | 
| 11 | 
            +
                # @return [Workflow] The workflow instance
         | 
| 12 | 
            +
                #
         | 
| 13 | 
            +
                # @example
         | 
| 14 | 
            +
                #   pipeline.workflow.context[:status] # => "processing"
         | 
| 15 | 
            +
                #
         | 
| 16 | 
            +
                # @rbs @workflow: Workflow
         | 
| 10 17 | 
             
                attr_reader :workflow
         | 
| 11 18 |  | 
| 12 19 | 
             
                # @param workflow [Workflow] The workflow to execute
         | 
| @@ -15,6 +22,8 @@ module CMDx | |
| 15 22 | 
             
                #
         | 
| 16 23 | 
             
                # @example
         | 
| 17 24 | 
             
                #   pipeline = Pipeline.new(my_workflow)
         | 
| 25 | 
            +
                #
         | 
| 26 | 
            +
                # @rbs (Workflow workflow) -> void
         | 
| 18 27 | 
             
                def initialize(workflow)
         | 
| 19 28 | 
             
                  @workflow = workflow
         | 
| 20 29 | 
             
                end
         | 
| @@ -27,6 +36,8 @@ module CMDx | |
| 27 36 | 
             
                #
         | 
| 28 37 | 
             
                # @example
         | 
| 29 38 | 
             
                #   Pipeline.execute(my_workflow)
         | 
| 39 | 
            +
                #
         | 
| 40 | 
            +
                # @rbs (Workflow workflow) -> void
         | 
| 30 41 | 
             
                def self.execute(workflow)
         | 
| 31 42 | 
             
                  new(workflow).execute
         | 
| 32 43 | 
             
                end
         | 
| @@ -40,6 +51,8 @@ module CMDx | |
| 40 51 | 
             
                # @example
         | 
| 41 52 | 
             
                #   pipeline = Pipeline.new(my_workflow)
         | 
| 42 53 | 
             
                #   pipeline.execute
         | 
| 54 | 
            +
                #
         | 
| 55 | 
            +
                # @rbs () -> void
         | 
| 43 56 | 
             
                def execute
         | 
| 44 57 | 
             
                  workflow.class.pipeline.each do |group|
         | 
| 45 58 | 
             
                    next unless Utils::Condition.evaluate(workflow, group.options)
         | 
| @@ -65,6 +78,8 @@ module CMDx | |
| 65 78 | 
             
                #
         | 
| 66 79 | 
             
                # @example
         | 
| 67 80 | 
             
                #   execute_group_tasks(group, ["failed", "skipped"])
         | 
| 81 | 
            +
                #
         | 
| 82 | 
            +
                # @rbs (untyped group, Array[String] breakpoints) -> void
         | 
| 68 83 | 
             
                def execute_group_tasks(group, breakpoints)
         | 
| 69 84 | 
             
                  case strategy = group.options[:strategy]
         | 
| 70 85 | 
             
                  when NilClass, /sequential/ then execute_tasks_in_sequence(group, breakpoints)
         | 
| @@ -85,6 +100,8 @@ module CMDx | |
| 85 100 | 
             
                #
         | 
| 86 101 | 
             
                # @example
         | 
| 87 102 | 
             
                #   execute_tasks_in_sequence(group, ["failed", "skipped"])
         | 
| 103 | 
            +
                #
         | 
| 104 | 
            +
                # @rbs (untyped group, Array[String] breakpoints) -> void
         | 
| 88 105 | 
             
                def execute_tasks_in_sequence(group, breakpoints)
         | 
| 89 106 | 
             
                  group.tasks.each do |task|
         | 
| 90 107 | 
             
                    task_result = task.execute(workflow.context)
         | 
| @@ -107,6 +124,8 @@ module CMDx | |
| 107 124 | 
             
                #
         | 
| 108 125 | 
             
                # @example
         | 
| 109 126 | 
             
                #   execute_tasks_in_parallel(group, ["failed"])
         | 
| 127 | 
            +
                #
         | 
| 128 | 
            +
                # @rbs (untyped group, Array[String] breakpoints) -> void
         | 
| 110 129 | 
             
                def execute_tasks_in_parallel(group, breakpoints)
         | 
| 111 130 | 
             
                  raise "install the `parallel` gem to use this feature" unless defined?(Parallel)
         | 
| 112 131 |  | 
    
        data/lib/cmdx/railtie.rb
    CHANGED
    
    | @@ -21,6 +21,8 @@ module CMDx | |
| 21 21 | 
             
                #   # This initializer runs automatically when Rails starts
         | 
| 22 22 | 
             
                #   # It will load locales like en.yml, es.yml, fr.yml if they exist
         | 
| 23 23 | 
             
                #   # in the CMDx gem's locales directory
         | 
| 24 | 
            +
                #
         | 
| 25 | 
            +
                # @rbs (untyped app) -> void
         | 
| 24 26 | 
             
                initializer("cmdx.configure_locales") do |app|
         | 
| 25 27 | 
             
                  Array(app.config.i18n.available_locales).each do |locale|
         | 
| 26 28 | 
             
                    path = CMDx.gem_path.join("lib/locales/#{locale}.yml")
         | 
| @@ -35,6 +37,8 @@ module CMDx | |
| 35 37 | 
             
                # Configures the backtrace cleaner for CMDx in a Rails environment.
         | 
| 36 38 | 
             
                #
         | 
| 37 39 | 
             
                # Sets the backtrace cleaner to the Rails backtrace cleaner.
         | 
| 40 | 
            +
                #
         | 
| 41 | 
            +
                # @rbs () -> void
         | 
| 38 42 | 
             
                initializer("cmdx.backtrace_cleaner") do
         | 
| 39 43 | 
             
                  CMDx.configuration.backtrace_cleaner = lambda do |backtrace|
         | 
| 40 44 | 
             
                    Rails.backtrace_cleaner.clean(backtrace)
         | 
    
        data/lib/cmdx/result.rb
    CHANGED
    
    | @@ -11,18 +11,22 @@ module CMDx | |
| 11 11 |  | 
| 12 12 | 
             
                extend Forwardable
         | 
| 13 13 |  | 
| 14 | 
            +
                # @rbs STATES: Array[String]
         | 
| 14 15 | 
             
                STATES = [
         | 
| 15 16 | 
             
                  INITIALIZED = "initialized",  # Initial state before execution
         | 
| 16 17 | 
             
                  EXECUTING = "executing",      # Currently executing task logic
         | 
| 17 18 | 
             
                  COMPLETE = "complete",        # Successfully completed execution
         | 
| 18 19 | 
             
                  INTERRUPTED = "interrupted"   # Execution was halted due to failure
         | 
| 19 20 | 
             
                ].freeze
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                # @rbs STATUSES: Array[String]
         | 
| 20 23 | 
             
                STATUSES = [
         | 
| 21 24 | 
             
                  SUCCESS = "success",  # Task completed successfully
         | 
| 22 25 | 
             
                  SKIPPED = "skipped",  # Task was skipped intentionally
         | 
| 23 26 | 
             
                  FAILED = "failed"     # Task failed due to error or validation
         | 
| 24 27 | 
             
                ].freeze
         | 
| 25 28 |  | 
| 29 | 
            +
                # @rbs STRIP_FAILURE: Proc
         | 
| 26 30 | 
             
                STRIP_FAILURE = proc do |hash, result, key|
         | 
| 27 31 | 
             
                  unless result.send(:"#{key}?")
         | 
| 28 32 | 
             
                    # Strip caused/threw failures since its the same info as the log line
         | 
| @@ -31,7 +35,65 @@ module CMDx | |
| 31 35 | 
             
                end.freeze
         | 
| 32 36 | 
             
                private_constant :STRIP_FAILURE
         | 
| 33 37 |  | 
| 34 | 
            -
                 | 
| 38 | 
            +
                # Returns the task instance associated with this result.
         | 
| 39 | 
            +
                #
         | 
| 40 | 
            +
                # @return [CMDx::Task] The task instance
         | 
| 41 | 
            +
                #
         | 
| 42 | 
            +
                # @example
         | 
| 43 | 
            +
                #   result.task.id # => "users/create"
         | 
| 44 | 
            +
                #
         | 
| 45 | 
            +
                # @rbs @task: Task
         | 
| 46 | 
            +
                attr_reader :task
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                # Returns the current execution state of the result.
         | 
| 49 | 
            +
                #
         | 
| 50 | 
            +
                # @return [String] One of: "initialized", "executing", "complete", "interrupted"
         | 
| 51 | 
            +
                #
         | 
| 52 | 
            +
                # @example
         | 
| 53 | 
            +
                #   result.state # => "complete"
         | 
| 54 | 
            +
                #
         | 
| 55 | 
            +
                # @rbs @state: String
         | 
| 56 | 
            +
                attr_reader :state
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                # Returns the execution status of the result.
         | 
| 59 | 
            +
                #
         | 
| 60 | 
            +
                # @return [String] One of: "success", "skipped", "failed"
         | 
| 61 | 
            +
                #
         | 
| 62 | 
            +
                # @example
         | 
| 63 | 
            +
                #   result.status # => "success"
         | 
| 64 | 
            +
                #
         | 
| 65 | 
            +
                # @rbs @status: String
         | 
| 66 | 
            +
                attr_reader :status
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                # Returns additional metadata about the result.
         | 
| 69 | 
            +
                #
         | 
| 70 | 
            +
                # @return [Hash{Symbol => Object}] Metadata hash
         | 
| 71 | 
            +
                #
         | 
| 72 | 
            +
                # @example
         | 
| 73 | 
            +
                #   result.metadata # => { duration: 1.5, retries: 2 }
         | 
| 74 | 
            +
                #
         | 
| 75 | 
            +
                # @rbs @metadata: Hash[Symbol, untyped]
         | 
| 76 | 
            +
                attr_reader :metadata
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                # Returns the reason for interruption (skip or failure).
         | 
| 79 | 
            +
                #
         | 
| 80 | 
            +
                # @return [String, nil] The reason message, or nil if not interrupted
         | 
| 81 | 
            +
                #
         | 
| 82 | 
            +
                # @example
         | 
| 83 | 
            +
                #   result.reason # => "Validation failed"
         | 
| 84 | 
            +
                #
         | 
| 85 | 
            +
                # @rbs @reason: (String | nil)
         | 
| 86 | 
            +
                attr_reader :reason
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                # Returns the exception that caused the interruption.
         | 
| 89 | 
            +
                #
         | 
| 90 | 
            +
                # @return [Exception, nil] The causing exception, or nil if not interrupted
         | 
| 91 | 
            +
                #
         | 
| 92 | 
            +
                # @example
         | 
| 93 | 
            +
                #   result.cause # => #<StandardError: Connection timeout>
         | 
| 94 | 
            +
                #
         | 
| 95 | 
            +
                # @rbs @cause: (Exception | nil)
         | 
| 96 | 
            +
                attr_reader :cause
         | 
| 35 97 |  | 
| 36 98 | 
             
                def_delegators :task, :context, :chain, :errors
         | 
| 37 99 | 
             
                alias ctx context
         | 
| @@ -45,6 +107,8 @@ module CMDx | |
| 45 107 | 
             
                # @example
         | 
| 46 108 | 
             
                #   result = CMDx::Result.new(my_task)
         | 
| 47 109 | 
             
                #   result.state # => "initialized"
         | 
| 110 | 
            +
                #
         | 
| 111 | 
            +
                # @rbs (Task) -> void
         | 
| 48 112 | 
             
                def initialize(task)
         | 
| 49 113 | 
             
                  raise TypeError, "must be a CMDx::Task" unless task.is_a?(CMDx::Task)
         | 
| 50 114 |  | 
| @@ -62,6 +126,8 @@ module CMDx | |
| 62 126 | 
             
                  # @example
         | 
| 63 127 | 
             
                  #   result.initialized? # => true
         | 
| 64 128 | 
             
                  #   result.executing?   # => false
         | 
| 129 | 
            +
                  #
         | 
| 130 | 
            +
                  # @rbs () -> bool
         | 
| 65 131 | 
             
                  define_method(:"#{s}?") { state == s }
         | 
| 66 132 |  | 
| 67 133 | 
             
                  # @param block [Proc] Block to execute conditionally
         | 
| @@ -75,6 +141,8 @@ module CMDx | |
| 75 141 | 
             
                  # @example
         | 
| 76 142 | 
             
                  #   result.handle_initialized { |r| puts "Starting execution" }
         | 
| 77 143 | 
             
                  #   result.handle_complete { |r| puts "Task completed" }
         | 
| 144 | 
            +
                  #
         | 
| 145 | 
            +
                  # @rbs () { (Result) -> void } -> self
         | 
| 78 146 | 
             
                  define_method(:"handle_#{s}") do |&block|
         | 
| 79 147 | 
             
                    raise ArgumentError, "block required" unless block
         | 
| 80 148 |  | 
| @@ -87,6 +155,8 @@ module CMDx | |
| 87 155 | 
             
                #
         | 
| 88 156 | 
             
                # @example
         | 
| 89 157 | 
             
                #   result.executed! # Transitions to complete or interrupted
         | 
| 158 | 
            +
                #
         | 
| 159 | 
            +
                # @rbs () -> self
         | 
| 90 160 | 
             
                def executed!
         | 
| 91 161 | 
             
                  success? ? complete! : interrupt!
         | 
| 92 162 | 
             
                end
         | 
| @@ -95,6 +165,8 @@ module CMDx | |
| 95 165 | 
             
                #
         | 
| 96 166 | 
             
                # @example
         | 
| 97 167 | 
             
                #   result.executed? # => true if complete? || interrupted?
         | 
| 168 | 
            +
                #
         | 
| 169 | 
            +
                # @rbs () -> bool
         | 
| 98 170 | 
             
                def executed?
         | 
| 99 171 | 
             
                  complete? || interrupted?
         | 
| 100 172 | 
             
                end
         | 
| @@ -109,6 +181,8 @@ module CMDx | |
| 109 181 | 
             
                #
         | 
| 110 182 | 
             
                # @example
         | 
| 111 183 | 
             
                #   result.handle_executed { |r| puts "Task finished: #{r.outcome}" }
         | 
| 184 | 
            +
                #
         | 
| 185 | 
            +
                # @rbs () { (Result) -> void } -> self
         | 
| 112 186 | 
             
                def handle_executed(&)
         | 
| 113 187 | 
             
                  raise ArgumentError, "block required" unless block_given?
         | 
| 114 188 |  | 
| @@ -120,6 +194,8 @@ module CMDx | |
| 120 194 | 
             
                #
         | 
| 121 195 | 
             
                # @example
         | 
| 122 196 | 
             
                #   result.executing! # Transitions from initialized to executing
         | 
| 197 | 
            +
                #
         | 
| 198 | 
            +
                # @rbs () -> void
         | 
| 123 199 | 
             
                def executing!
         | 
| 124 200 | 
             
                  return if executing?
         | 
| 125 201 |  | 
| @@ -132,6 +208,8 @@ module CMDx | |
| 132 208 | 
             
                #
         | 
| 133 209 | 
             
                # @example
         | 
| 134 210 | 
             
                #   result.complete! # Transitions from executing to complete
         | 
| 211 | 
            +
                #
         | 
| 212 | 
            +
                # @rbs () -> void
         | 
| 135 213 | 
             
                def complete!
         | 
| 136 214 | 
             
                  return if complete?
         | 
| 137 215 |  | 
| @@ -144,6 +222,8 @@ module CMDx | |
| 144 222 | 
             
                #
         | 
| 145 223 | 
             
                # @example
         | 
| 146 224 | 
             
                #   result.interrupt! # Transitions from executing to interrupted
         | 
| 225 | 
            +
                #
         | 
| 226 | 
            +
                # @rbs () -> void
         | 
| 147 227 | 
             
                def interrupt!
         | 
| 148 228 | 
             
                  return if interrupted?
         | 
| 149 229 |  | 
| @@ -158,6 +238,8 @@ module CMDx | |
| 158 238 | 
             
                  # @example
         | 
| 159 239 | 
             
                  #   result.success? # => true
         | 
| 160 240 | 
             
                  #   result.failed?  # => false
         | 
| 241 | 
            +
                  #
         | 
| 242 | 
            +
                  # @rbs () -> bool
         | 
| 161 243 | 
             
                  define_method(:"#{s}?") { status == s }
         | 
| 162 244 |  | 
| 163 245 | 
             
                  # @param block [Proc] Block to execute conditionally
         | 
| @@ -171,6 +253,8 @@ module CMDx | |
| 171 253 | 
             
                  # @example
         | 
| 172 254 | 
             
                  #   result.handle_success { |r| puts "Task succeeded" }
         | 
| 173 255 | 
             
                  #   result.handle_failed { |r| puts "Task failed: #{r.reason}" }
         | 
| 256 | 
            +
                  #
         | 
| 257 | 
            +
                  # @rbs () { (Result) -> void } -> self
         | 
| 174 258 | 
             
                  define_method(:"handle_#{s}") do |&block|
         | 
| 175 259 | 
             
                    raise ArgumentError, "block required" unless block
         | 
| 176 260 |  | 
| @@ -183,6 +267,8 @@ module CMDx | |
| 183 267 | 
             
                #
         | 
| 184 268 | 
             
                # @example
         | 
| 185 269 | 
             
                #   result.good? # => true if !failed?
         | 
| 270 | 
            +
                #
         | 
| 271 | 
            +
                # @rbs () -> bool
         | 
| 186 272 | 
             
                def good?
         | 
| 187 273 | 
             
                  !failed?
         | 
| 188 274 | 
             
                end
         | 
| @@ -198,6 +284,8 @@ module CMDx | |
| 198 284 | 
             
                #
         | 
| 199 285 | 
             
                # @example
         | 
| 200 286 | 
             
                #   result.handle_good { |r| puts "Task completed successfully" }
         | 
| 287 | 
            +
                #
         | 
| 288 | 
            +
                # @rbs () { (Result) -> void } -> self
         | 
| 201 289 | 
             
                def handle_good(&)
         | 
| 202 290 | 
             
                  raise ArgumentError, "block required" unless block_given?
         | 
| 203 291 |  | 
| @@ -209,6 +297,8 @@ module CMDx | |
| 209 297 | 
             
                #
         | 
| 210 298 | 
             
                # @example
         | 
| 211 299 | 
             
                #   result.bad? # => true if !success?
         | 
| 300 | 
            +
                #
         | 
| 301 | 
            +
                # @rbs () -> bool
         | 
| 212 302 | 
             
                def bad?
         | 
| 213 303 | 
             
                  !success?
         | 
| 214 304 | 
             
                end
         | 
| @@ -223,6 +313,8 @@ module CMDx | |
| 223 313 | 
             
                #
         | 
| 224 314 | 
             
                # @example
         | 
| 225 315 | 
             
                #   result.handle_bad { |r| puts "Task had issues: #{r.reason}" }
         | 
| 316 | 
            +
                #
         | 
| 317 | 
            +
                # @rbs () { (Result) -> void } -> self
         | 
| 226 318 | 
             
                def handle_bad(&)
         | 
| 227 319 | 
             
                  raise ArgumentError, "block required" unless block_given?
         | 
| 228 320 |  | 
| @@ -240,6 +332,8 @@ module CMDx | |
| 240 332 | 
             
                # @example
         | 
| 241 333 | 
             
                #   result.skip!("Dependencies not met", cause: dependency_error)
         | 
| 242 334 | 
             
                #   result.skip!("Already processed", halt: false)
         | 
| 335 | 
            +
                #
         | 
| 336 | 
            +
                # @rbs (?String? reason, halt: bool, cause: Exception?, **untyped metadata) -> void
         | 
| 243 337 | 
             
                def skip!(reason = nil, halt: true, cause: nil, **metadata)
         | 
| 244 338 | 
             
                  return if skipped?
         | 
| 245 339 |  | 
| @@ -264,6 +358,8 @@ module CMDx | |
| 264 358 | 
             
                # @example
         | 
| 265 359 | 
             
                #   result.fail!("Validation failed", cause: validation_error)
         | 
| 266 360 | 
             
                #   result.fail!("Network timeout", halt: false, timeout: 30)
         | 
| 361 | 
            +
                #
         | 
| 362 | 
            +
                # @rbs (?String? reason, halt: bool, cause: Exception?, **untyped metadata) -> void
         | 
| 267 363 | 
             
                def fail!(reason = nil, halt: true, cause: nil, **metadata)
         | 
| 268 364 | 
             
                  return if failed?
         | 
| 269 365 |  | 
| @@ -283,6 +379,8 @@ module CMDx | |
| 283 379 | 
             
                #
         | 
| 284 380 | 
             
                # @example
         | 
| 285 381 | 
             
                #   result.halt! # Raises appropriate fault based on status
         | 
| 382 | 
            +
                #
         | 
| 383 | 
            +
                # @rbs () -> void
         | 
| 286 384 | 
             
                def halt!
         | 
| 287 385 | 
             
                  return if success?
         | 
| 288 386 |  | 
| @@ -315,6 +413,8 @@ module CMDx | |
| 315 413 | 
             
                # @example
         | 
| 316 414 | 
             
                #   other_result = OtherTask.execute
         | 
| 317 415 | 
             
                #   result.throw!(other_result, cause: upstream_error)
         | 
| 416 | 
            +
                #
         | 
| 417 | 
            +
                # @rbs (Result result, halt: bool, cause: Exception?, **untyped metadata) -> void
         | 
| 318 418 | 
             
                def throw!(result, halt: true, cause: nil, **metadata)
         | 
| 319 419 | 
             
                  raise TypeError, "must be a CMDx::Result" unless result.is_a?(Result)
         | 
| 320 420 |  | 
| @@ -332,6 +432,8 @@ module CMDx | |
| 332 432 | 
             
                # @example
         | 
| 333 433 | 
             
                #   cause = result.caused_failure
         | 
| 334 434 | 
             
                #   puts "Caused by: #{cause.task.id}" if cause
         | 
| 435 | 
            +
                #
         | 
| 436 | 
            +
                # @rbs () -> Result?
         | 
| 335 437 | 
             
                def caused_failure
         | 
| 336 438 | 
             
                  return unless failed?
         | 
| 337 439 |  | 
| @@ -344,6 +446,8 @@ module CMDx | |
| 344 446 | 
             
                #   if result.caused_failure?
         | 
| 345 447 | 
             
                #     puts "This task caused the failure"
         | 
| 346 448 | 
             
                #   end
         | 
| 449 | 
            +
                #
         | 
| 450 | 
            +
                # @rbs () -> bool
         | 
| 347 451 | 
             
                def caused_failure?
         | 
| 348 452 | 
             
                  return false unless failed?
         | 
| 349 453 |  | 
| @@ -355,6 +459,8 @@ module CMDx | |
| 355 459 | 
             
                # @example
         | 
| 356 460 | 
             
                #   thrown = result.threw_failure
         | 
| 357 461 | 
             
                #   puts "Thrown by: #{thrown.task.id}" if thrown
         | 
| 462 | 
            +
                #
         | 
| 463 | 
            +
                # @rbs () -> Result?
         | 
| 358 464 | 
             
                def threw_failure
         | 
| 359 465 | 
             
                  return unless failed?
         | 
| 360 466 |  | 
| @@ -369,6 +475,8 @@ module CMDx | |
| 369 475 | 
             
                #   if result.threw_failure?
         | 
| 370 476 | 
             
                #     puts "This task threw the failure"
         | 
| 371 477 | 
             
                #   end
         | 
| 478 | 
            +
                #
         | 
| 479 | 
            +
                # @rbs () -> bool
         | 
| 372 480 | 
             
                def threw_failure?
         | 
| 373 481 | 
             
                  return false unless failed?
         | 
| 374 482 |  | 
| @@ -381,6 +489,8 @@ module CMDx | |
| 381 489 | 
             
                #   if result.thrown_failure?
         | 
| 382 490 | 
             
                #     puts "This failure was thrown from another task"
         | 
| 383 491 | 
             
                #   end
         | 
| 492 | 
            +
                #
         | 
| 493 | 
            +
                # @rbs () -> bool
         | 
| 384 494 | 
             
                def thrown_failure?
         | 
| 385 495 | 
             
                  failed? && !caused_failure?
         | 
| 386 496 | 
             
                end
         | 
| @@ -390,6 +500,8 @@ module CMDx | |
| 390 500 | 
             
                # @example
         | 
| 391 501 | 
             
                #   position = result.index
         | 
| 392 502 | 
             
                #   puts "Task #{position + 1} of #{chain.results.count}"
         | 
| 503 | 
            +
                #
         | 
| 504 | 
            +
                # @rbs () -> Integer
         | 
| 393 505 | 
             
                def index
         | 
| 394 506 | 
             
                  chain.index(self)
         | 
| 395 507 | 
             
                end
         | 
| @@ -398,6 +510,8 @@ module CMDx | |
| 398 510 | 
             
                #
         | 
| 399 511 | 
             
                # @example
         | 
| 400 512 | 
             
                #   result.outcome # => "success" or "interrupted"
         | 
| 513 | 
            +
                #
         | 
| 514 | 
            +
                # @rbs () -> String
         | 
| 401 515 | 
             
                def outcome
         | 
| 402 516 | 
             
                  initialized? || thrown_failure? ? state : status
         | 
| 403 517 | 
             
                end
         | 
| @@ -407,6 +521,8 @@ module CMDx | |
| 407 521 | 
             
                # @example
         | 
| 408 522 | 
             
                #   result.to_h
         | 
| 409 523 | 
             
                #   # => {state: "complete", status: "success", outcome: "success", metadata: {}}
         | 
| 524 | 
            +
                #
         | 
| 525 | 
            +
                # @rbs () -> Hash[Symbol, untyped]
         | 
| 410 526 | 
             
                def to_h
         | 
| 411 527 | 
             
                  task.to_h.merge!(
         | 
| 412 528 | 
             
                    state:,
         | 
| @@ -430,6 +546,8 @@ module CMDx | |
| 430 546 | 
             
                #
         | 
| 431 547 | 
             
                # @example
         | 
| 432 548 | 
             
                #   result.to_s # => "task_id=my_task state=complete status=success"
         | 
| 549 | 
            +
                #
         | 
| 550 | 
            +
                # @rbs () -> String
         | 
| 433 551 | 
             
                def to_s
         | 
| 434 552 | 
             
                  Utils::Format.to_str(to_h) do |key, value|
         | 
| 435 553 | 
             
                    case key
         | 
| @@ -446,6 +564,8 @@ module CMDx | |
| 446 564 | 
             
                # @example
         | 
| 447 565 | 
             
                #   state, status = result.deconstruct
         | 
| 448 566 | 
             
                #   puts "State: #{state}, Status: #{status}"
         | 
| 567 | 
            +
                #
         | 
| 568 | 
            +
                # @rbs (*untyped) -> Array[untyped]
         | 
| 449 569 | 
             
                def deconstruct(*)
         | 
| 450 570 | 
             
                  [state, status, reason, cause, metadata]
         | 
| 451 571 | 
             
                end
         | 
| @@ -461,6 +581,8 @@ module CMDx | |
| 461 581 | 
             
                #   in {bad: true}
         | 
| 462 582 | 
             
                #     puts "Task had issues"
         | 
| 463 583 | 
             
                #   end
         | 
| 584 | 
            +
                #
         | 
| 585 | 
            +
                # @rbs (*untyped) -> Hash[Symbol, untyped]
         | 
| 464 586 | 
             
                def deconstruct_keys(*)
         | 
| 465 587 | 
             
                  {
         | 
| 466 588 | 
             
                    state: state,
         | 
    
        data/lib/cmdx/task.rb
    CHANGED
    
    | @@ -8,10 +8,68 @@ module CMDx | |
| 8 8 |  | 
| 9 9 | 
             
                extend Forwardable
         | 
| 10 10 |  | 
| 11 | 
            -
                 | 
| 11 | 
            +
                # Returns the hash of processed attribute values for this task.
         | 
| 12 | 
            +
                #
         | 
| 13 | 
            +
                # @return [Hash{Symbol => Object}] Hash of attribute names to their values
         | 
| 14 | 
            +
                #
         | 
| 15 | 
            +
                # @example
         | 
| 16 | 
            +
                #   task.attributes # => { user_id: 42, user_name: "John" }
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                # @rbs @attributes: Hash[Symbol, untyped]
         | 
| 19 | 
            +
                attr_reader :attributes
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                # Returns the collection of validation and execution errors.
         | 
| 22 | 
            +
                #
         | 
| 23 | 
            +
                # @return [Errors] The errors collection
         | 
| 24 | 
            +
                #
         | 
| 25 | 
            +
                # @example
         | 
| 26 | 
            +
                #   task.errors.to_h # => { email: ["must be valid"] }
         | 
| 27 | 
            +
                #
         | 
| 28 | 
            +
                # @rbs @errors: Errors
         | 
| 29 | 
            +
                attr_reader :errors
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                # Returns the unique identifier for this task instance.
         | 
| 32 | 
            +
                #
         | 
| 33 | 
            +
                # @return [String] The task identifier
         | 
| 34 | 
            +
                #
         | 
| 35 | 
            +
                # @example
         | 
| 36 | 
            +
                #   task.id # => "abc123xyz"
         | 
| 37 | 
            +
                #
         | 
| 38 | 
            +
                # @rbs @id: String
         | 
| 39 | 
            +
                attr_reader :id
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                # Returns the execution context for this task.
         | 
| 42 | 
            +
                #
         | 
| 43 | 
            +
                # @return [Context] The context instance
         | 
| 44 | 
            +
                #
         | 
| 45 | 
            +
                # @example
         | 
| 46 | 
            +
                #   task.context[:user_id] # => 42
         | 
| 47 | 
            +
                #
         | 
| 48 | 
            +
                # @rbs @context: Context
         | 
| 49 | 
            +
                attr_reader :context
         | 
| 12 50 | 
             
                alias ctx context
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                # Returns the execution result for this task.
         | 
| 53 | 
            +
                #
         | 
| 54 | 
            +
                # @return [Result] The result instance
         | 
| 55 | 
            +
                #
         | 
| 56 | 
            +
                # @example
         | 
| 57 | 
            +
                #   task.result.status # => "success"
         | 
| 58 | 
            +
                #
         | 
| 59 | 
            +
                # @rbs @result: Result
         | 
| 60 | 
            +
                attr_reader :result
         | 
| 13 61 | 
             
                alias res result
         | 
| 14 62 |  | 
| 63 | 
            +
                # Returns the execution chain containing all task results.
         | 
| 64 | 
            +
                #
         | 
| 65 | 
            +
                # @return [Chain] The chain instance
         | 
| 66 | 
            +
                #
         | 
| 67 | 
            +
                # @example
         | 
| 68 | 
            +
                #   task.chain.results.size # => 3
         | 
| 69 | 
            +
                #
         | 
| 70 | 
            +
                # @rbs @chain: Chain
         | 
| 71 | 
            +
                attr_reader :chain
         | 
| 72 | 
            +
             | 
| 15 73 | 
             
                def_delegators :result, :skip!, :fail!, :throw!
         | 
| 16 74 |  | 
| 17 75 | 
             
                # @param context [Hash, Context] The initial context for the task
         | 
| @@ -25,6 +83,8 @@ module CMDx | |
| 25 83 | 
             
                # @example
         | 
| 26 84 | 
             
                #   task = MyTask.new(name: "example", priority: :high)
         | 
| 27 85 | 
             
                #   task = MyTask.new(Context.build(name: "example"))
         | 
| 86 | 
            +
                #
         | 
| 87 | 
            +
                # @rbs (untyped context) -> void
         | 
| 28 88 | 
             
                def initialize(context = {})
         | 
| 29 89 | 
             
                  Deprecator.restrict(self)
         | 
| 30 90 |  | 
| @@ -47,6 +107,8 @@ module CMDx | |
| 47 107 | 
             
                  #   class MyTask < Task
         | 
| 48 108 | 
             
                  #     settings deprecate: true, tags: [:experimental]
         | 
| 49 109 | 
             
                  #   end
         | 
| 110 | 
            +
                  #
         | 
| 111 | 
            +
                  # @rbs (**untyped options) -> Hash[Symbol, untyped]
         | 
| 50 112 | 
             
                  def settings(**options)
         | 
| 51 113 | 
             
                    @settings ||= begin
         | 
| 52 114 | 
             
                      hash =
         | 
| @@ -81,6 +143,8 @@ module CMDx | |
| 81 143 | 
             
                  # @example
         | 
| 82 144 | 
             
                  #   register(:attribute, MyAttribute.new)
         | 
| 83 145 | 
             
                  #   register(:callback, :before, -> { puts "before" })
         | 
| 146 | 
            +
                  #
         | 
| 147 | 
            +
                  # @rbs (Symbol type, untyped object, *untyped) -> void
         | 
| 84 148 | 
             
                  def register(type, object, ...)
         | 
| 85 149 | 
             
                    case type
         | 
| 86 150 | 
             
                    when :attribute then settings[:attributes].register(object, ...)
         | 
| @@ -101,6 +165,8 @@ module CMDx | |
| 101 165 | 
             
                  # @example
         | 
| 102 166 | 
             
                  #   deregister(:attribute, :name)
         | 
| 103 167 | 
             
                  #   deregister(:callback, :before, MyCallback)
         | 
| 168 | 
            +
                  #
         | 
| 169 | 
            +
                  # @rbs (Symbol type, untyped object, *untyped) -> void
         | 
| 104 170 | 
             
                  def deregister(type, object, ...)
         | 
| 105 171 | 
             
                    case type
         | 
| 106 172 | 
             
                    when :attribute then settings[:attributes].deregister(object, ...)
         | 
| @@ -117,6 +183,8 @@ module CMDx | |
| 117 183 | 
             
                  # @example
         | 
| 118 184 | 
             
                  #   attributes :name, :email
         | 
| 119 185 | 
             
                  #   attributes :age, type: Integer, default: 18
         | 
| 186 | 
            +
                  #
         | 
| 187 | 
            +
                  # @rbs (*untyped) -> void
         | 
| 120 188 | 
             
                  def attributes(...)
         | 
| 121 189 | 
             
                    register(:attribute, Attribute.build(...))
         | 
| 122 190 | 
             
                  end
         | 
| @@ -127,6 +195,8 @@ module CMDx | |
| 127 195 | 
             
                  # @example
         | 
| 128 196 | 
             
                  #   optional :description, :notes
         | 
| 129 197 | 
             
                  #   optional :priority, type: Symbol, default: :normal
         | 
| 198 | 
            +
                  #
         | 
| 199 | 
            +
                  # @rbs (*untyped) -> void
         | 
| 130 200 | 
             
                  def optional(...)
         | 
| 131 201 | 
             
                    register(:attribute, Attribute.optional(...))
         | 
| 132 202 | 
             
                  end
         | 
| @@ -136,6 +206,8 @@ module CMDx | |
| 136 206 | 
             
                  # @example
         | 
| 137 207 | 
             
                  #   required :name, :email
         | 
| 138 208 | 
             
                  #   required :age, type: Integer, min: 0
         | 
| 209 | 
            +
                  #
         | 
| 210 | 
            +
                  # @rbs (*untyped) -> void
         | 
| 139 211 | 
             
                  def required(...)
         | 
| 140 212 | 
             
                    register(:attribute, Attribute.required(...))
         | 
| 141 213 | 
             
                  end
         | 
| @@ -144,6 +216,8 @@ module CMDx | |
| 144 216 | 
             
                  #
         | 
| 145 217 | 
             
                  # @example
         | 
| 146 218 | 
             
                  #   remove_attributes :old_field, :deprecated_field
         | 
| 219 | 
            +
                  #
         | 
| 220 | 
            +
                  # @rbs (*Symbol names) -> void
         | 
| 147 221 | 
             
                  def remove_attributes(*names)
         | 
| 148 222 | 
             
                    deregister(:attribute, names)
         | 
| 149 223 | 
             
                  end
         | 
| @@ -160,6 +234,8 @@ module CMDx | |
| 160 234 | 
             
                    #   before { puts "before execution" }
         | 
| 161 235 | 
             
                    #   after :cleanup, priority: :high
         | 
| 162 236 | 
             
                    #   around ->(task) { task.logger.info("starting") }
         | 
| 237 | 
            +
                    #
         | 
| 238 | 
            +
                    # @rbs (*untyped callables, **untyped options) ?{ () -> void } -> void
         | 
| 163 239 | 
             
                    define_method(callback) do |*callables, **options, &block|
         | 
| 164 240 | 
             
                      register(:callback, callback, *callables, **options, &block)
         | 
| 165 241 | 
             
                    end
         | 
| @@ -174,6 +250,8 @@ module CMDx | |
| 174 250 | 
             
                  #   if result.success?
         | 
| 175 251 | 
             
                  #     puts "Task completed successfully"
         | 
| 176 252 | 
             
                  #   end
         | 
| 253 | 
            +
                  #
         | 
| 254 | 
            +
                  # @rbs (*untyped args, **untyped kwargs) ?{ (Result) -> void } -> Result
         | 
| 177 255 | 
             
                  def execute(*args, **kwargs)
         | 
| 178 256 | 
             
                    task = new(*args, **kwargs)
         | 
| 179 257 | 
             
                    task.execute(raise: false)
         | 
| @@ -189,6 +267,8 @@ module CMDx | |
| 189 267 | 
             
                  # @example
         | 
| 190 268 | 
             
                  #   result = MyTask.execute!(name: "example")
         | 
| 191 269 | 
             
                  #   # Will raise an exception if execution fails
         | 
| 270 | 
            +
                  #
         | 
| 271 | 
            +
                  # @rbs (*untyped args, **untyped kwargs) ?{ (Result) -> void } -> Result
         | 
| 192 272 | 
             
                  def execute!(*args, **kwargs)
         | 
| 193 273 | 
             
                    task = new(*args, **kwargs)
         | 
| 194 274 | 
             
                    task.execute(raise: true)
         | 
| @@ -204,6 +284,8 @@ module CMDx | |
| 204 284 | 
             
                # @example
         | 
| 205 285 | 
             
                #   result = task.execute
         | 
| 206 286 | 
             
                #   result = task.execute(raise: true)
         | 
| 287 | 
            +
                #
         | 
| 288 | 
            +
                # @rbs (raise: bool) ?{ (Result) -> void } -> Result
         | 
| 207 289 | 
             
                def execute(raise: false)
         | 
| 208 290 | 
             
                  Executor.execute(self, raise:)
         | 
| 209 291 | 
             
                  block_given? ? yield(result) : result
         | 
| @@ -218,6 +300,8 @@ module CMDx | |
| 218 300 | 
             
                #       puts "Performing work..."
         | 
| 219 301 | 
             
                #     end
         | 
| 220 302 | 
             
                #   end
         | 
| 303 | 
            +
                #
         | 
| 304 | 
            +
                # @rbs () -> void
         | 
| 221 305 | 
             
                def work
         | 
| 222 306 | 
             
                  raise UndefinedMethodError, "undefined method #{self.class.name}#work"
         | 
| 223 307 | 
             
                end
         | 
| @@ -227,6 +311,8 @@ module CMDx | |
| 227 311 | 
             
                # @example
         | 
| 228 312 | 
             
                #   logger.info "Starting task execution"
         | 
| 229 313 | 
             
                #   logger.error "Task failed", error: exception
         | 
| 314 | 
            +
                #
         | 
| 315 | 
            +
                # @rbs () -> Logger
         | 
| 230 316 | 
             
                def logger
         | 
| 231 317 | 
             
                  @logger ||= begin
         | 
| 232 318 | 
             
                    logger = self.class.settings[:logger] || CMDx.configuration.logger
         | 
| @@ -249,6 +335,8 @@ module CMDx | |
| 249 335 | 
             
                #   task_hash = task.to_h
         | 
| 250 336 | 
             
                #   puts "Task type: #{task_hash[:type]}"
         | 
| 251 337 | 
             
                #   puts "Task tags: #{task_hash[:tags].join(', ')}"
         | 
| 338 | 
            +
                #
         | 
| 339 | 
            +
                # @rbs () -> Hash[Symbol, untyped]
         | 
| 252 340 | 
             
                def to_h
         | 
| 253 341 | 
             
                  {
         | 
| 254 342 | 
             
                    index: result.index,
         | 
| @@ -265,6 +353,8 @@ module CMDx | |
| 265 353 | 
             
                # @example
         | 
| 266 354 | 
             
                #   puts task.to_s
         | 
| 267 355 | 
             
                #   # Output: "Task[MyTask] tags: [:important] id: abc123"
         | 
| 356 | 
            +
                #
         | 
| 357 | 
            +
                # @rbs () -> String
         | 
| 268 358 | 
             
                def to_s
         | 
| 269 359 | 
             
                  Utils::Format.to_str(to_h)
         | 
| 270 360 | 
             
                end
         | 
    
        data/lib/cmdx/utils/call.rb
    CHANGED
    
    | @@ -32,6 +32,8 @@ module CMDx | |
| 32 32 | 
             
                  # @example Invoking a callable object
         | 
| 33 33 | 
             
                  #   callable = MyCallable.new
         | 
| 34 34 | 
             
                  #   Call.invoke(user, callable, 'data')
         | 
| 35 | 
            +
                  #
         | 
| 36 | 
            +
                  # @rbs (untyped target, (Symbol | Proc | untyped) callable, *untyped args, **untyped kwargs) ?{ () -> untyped } -> untyped
         | 
| 35 37 | 
             
                  def invoke(target, callable, *args, **kwargs, &)
         | 
| 36 38 | 
             
                    if callable.is_a?(Symbol)
         | 
| 37 39 | 
             
                      target.send(callable, *args, **kwargs, &)
         | 
    
        data/lib/cmdx/utils/condition.rb
    CHANGED
    
    | @@ -11,6 +11,7 @@ module CMDx | |
| 11 11 |  | 
| 12 12 | 
             
                  extend self
         | 
| 13 13 |  | 
| 14 | 
            +
                  # @rbs EVAL: Proc
         | 
| 14 15 | 
             
                  EVAL = proc do |target, callable, *args, **kwargs, &block|
         | 
| 15 16 | 
             
                    case callable
         | 
| 16 17 | 
             
                    when NilClass, FalseClass, TrueClass then !!callable
         | 
| @@ -53,6 +54,8 @@ module CMDx | |
| 53 54 | 
             
                  # @example With arguments and block
         | 
| 54 55 | 
             
                  #   Condition.evaluate(user, if: ->(u) { u.has_permission?(:admin) }, :admin)
         | 
| 55 56 | 
             
                  #   # => true if the proc returns true when called with user and :admin
         | 
| 57 | 
            +
                  #
         | 
| 58 | 
            +
                  # @rbs (untyped target, Hash[Symbol, untyped] options, *untyped) ?{ () -> untyped } -> bool
         | 
| 56 59 | 
             
                  def evaluate(target, options, ...)
         | 
| 57 60 | 
             
                    case options
         | 
| 58 61 | 
             
                    in if: if_cond, unless: unless_cond
         |