cmdx 1.18.0 → 1.20.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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/CHANGELOG.md +73 -9
  4. data/README.md +1 -1
  5. data/lib/cmdx/attribute.rb +88 -20
  6. data/lib/cmdx/attribute_registry.rb +79 -8
  7. data/lib/cmdx/attribute_value.rb +8 -3
  8. data/lib/cmdx/callback_registry.rb +60 -26
  9. data/lib/cmdx/chain.rb +47 -4
  10. data/lib/cmdx/coercion_registry.rb +42 -20
  11. data/lib/cmdx/coercions/array.rb +8 -3
  12. data/lib/cmdx/coercions/big_decimal.rb +1 -1
  13. data/lib/cmdx/coercions/boolean.rb +6 -2
  14. data/lib/cmdx/coercions/complex.rb +1 -1
  15. data/lib/cmdx/coercions/date.rb +2 -7
  16. data/lib/cmdx/coercions/date_time.rb +2 -7
  17. data/lib/cmdx/coercions/float.rb +1 -1
  18. data/lib/cmdx/coercions/hash.rb +1 -1
  19. data/lib/cmdx/coercions/integer.rb +4 -5
  20. data/lib/cmdx/coercions/rational.rb +1 -1
  21. data/lib/cmdx/coercions/string.rb +1 -1
  22. data/lib/cmdx/coercions/symbol.rb +1 -1
  23. data/lib/cmdx/coercions/time.rb +1 -7
  24. data/lib/cmdx/configuration.rb +26 -0
  25. data/lib/cmdx/context.rb +9 -6
  26. data/lib/cmdx/deprecator.rb +27 -14
  27. data/lib/cmdx/errors.rb +3 -4
  28. data/lib/cmdx/exception.rb +7 -0
  29. data/lib/cmdx/executor.rb +77 -54
  30. data/lib/cmdx/identifier.rb +4 -6
  31. data/lib/cmdx/locale.rb +32 -9
  32. data/lib/cmdx/middleware_registry.rb +43 -23
  33. data/lib/cmdx/middlewares/correlate.rb +4 -2
  34. data/lib/cmdx/middlewares/timeout.rb +11 -10
  35. data/lib/cmdx/parallelizer.rb +100 -0
  36. data/lib/cmdx/pipeline.rb +42 -23
  37. data/lib/cmdx/railtie.rb +1 -1
  38. data/lib/cmdx/result.rb +27 -11
  39. data/lib/cmdx/retry.rb +166 -0
  40. data/lib/cmdx/settings.rb +222 -0
  41. data/lib/cmdx/task.rb +53 -61
  42. data/lib/cmdx/utils/format.rb +17 -1
  43. data/lib/cmdx/utils/normalize.rb +52 -0
  44. data/lib/cmdx/utils/wrap.rb +38 -0
  45. data/lib/cmdx/validator_registry.rb +45 -20
  46. data/lib/cmdx/validators/absence.rb +1 -1
  47. data/lib/cmdx/validators/exclusion.rb +2 -2
  48. data/lib/cmdx/validators/format.rb +1 -1
  49. data/lib/cmdx/validators/inclusion.rb +2 -2
  50. data/lib/cmdx/validators/length.rb +1 -1
  51. data/lib/cmdx/validators/numeric.rb +1 -1
  52. data/lib/cmdx/validators/presence.rb +1 -1
  53. data/lib/cmdx/version.rb +1 -1
  54. data/lib/cmdx.rb +12 -0
  55. data/lib/generators/cmdx/templates/install.rb +11 -0
  56. data/mkdocs.yml +5 -1
  57. metadata +6 -15
data/lib/cmdx/task.rb CHANGED
@@ -73,67 +73,60 @@ module CMDx
73
73
  def_delegators :result, :skip!, :fail!, :throw!
74
74
  def_delegators :chain, :dry_run?
75
75
 
76
- # @param context [Hash, Context] The initial context for the task
77
- #
78
- # @option context [Object] :* Any key-value pairs to initialize the context
76
+ # @param context [Hash, Context, nil] The initial context for the task
79
77
  #
80
78
  # @return [Task] A new task instance
81
79
  #
82
80
  # @raise [DeprecationError] If the task class is deprecated
83
81
  #
84
82
  # @example
83
+ # task = MyTask.new
85
84
  # task = MyTask.new(name: "example", priority: :high)
86
85
  # task = MyTask.new(Context.build(name: "example"))
87
86
  #
88
87
  # @rbs (untyped context) -> void
89
- def initialize(context = {})
88
+ def initialize(context = nil)
90
89
  Deprecator.restrict(self)
91
90
 
92
- @attributes = {}
93
- @errors = Errors.new
94
-
95
91
  @id = Identifier.generate
96
92
  @context = Context.build(context)
93
+ @errors = Errors.new
97
94
  @result = Result.new(self)
98
95
  @chain = Chain.build(@result, dry_run: @context.delete(:dry_run))
96
+
97
+ @attributes = {}
99
98
  end
100
99
 
101
100
  class << self
102
101
 
103
- # @param options [Hash] Configuration options to merge with existing settings
104
- # @option options [Object] :* Any configuration option key-value pairs
102
+ # Returns the cached task type string for this class.
105
103
  #
106
- # @return [Hash] The merged settings hash
104
+ # @return [String] "Workflow" or "Task"
105
+ #
106
+ # @rbs () -> String
107
+ def type
108
+ @type ||= include?(Workflow) ? "Workflow" : "Task"
109
+ end
110
+
111
+ # Returns (and lazily creates) the task-level Settings object.
112
+ # On first access, inherits from the superclass settings or
113
+ # the global Configuration. Optional overrides are applied once.
114
+ #
115
+ # @param overrides [Hash] Configuration overrides applied on first access
116
+ # @option overrides [Object] :* Any configuration override key-value pairs
117
+ #
118
+ # @return [Settings] The settings instance for this task class
107
119
  #
108
120
  # @example
109
121
  # class MyTask < Task
110
122
  # settings deprecate: true, tags: [:experimental]
111
123
  # end
112
124
  #
113
- # @rbs (**untyped options) -> Hash[Symbol, untyped]
114
- def settings(**options)
125
+ # @rbs (**untyped overrides) -> Settings
126
+ def settings(**overrides)
115
127
  @settings ||= begin
116
- hash =
117
- if superclass.respond_to?(:settings)
118
- parent = superclass.settings
119
- parent
120
- .except(:backtrace_cleaner, :exception_handler, :logger, :deprecate)
121
- .transform_values!(&:dup)
122
- .merge!(
123
- backtrace_cleaner: parent[:backtrace_cleaner] || CMDx.configuration.backtrace_cleaner,
124
- exception_handler: parent[:exception_handler] || CMDx.configuration.exception_handler,
125
- logger: parent[:logger] || CMDx.configuration.logger,
126
- deprecate: parent[:deprecate]
127
- )
128
- else
129
- CMDx.configuration.to_h
130
- end
131
-
132
- hash[:attributes] ||= AttributeRegistry.new
133
- hash[:returns] ||= []
134
- hash[:tags] ||= []
135
-
136
- hash.merge!(options)
128
+ parent = superclass.settings if superclass.respond_to?(:settings)
129
+ Settings.new(parent:, **overrides)
137
130
  end
138
131
  end
139
132
 
@@ -149,11 +142,13 @@ module CMDx
149
142
  # @rbs (Symbol type, untyped object, *untyped) -> void
150
143
  def register(type, object, ...)
151
144
  case type
152
- when :attribute then settings[:attributes].register(object, ...)
153
- when :callback then settings[:callbacks].register(object, ...)
154
- when :coercion then settings[:coercions].register(object, ...)
155
- when :middleware then settings[:middlewares].register(object, ...)
156
- when :validator then settings[:validators].register(object, ...)
145
+ when :attribute
146
+ settings.attributes.register(object)
147
+ settings.attributes.define_readers_on!(self, Utils::Wrap.array(object))
148
+ when :callback then settings.callbacks.register(object, ...)
149
+ when :middleware then settings.middlewares.register(object, ...)
150
+ when :validator then settings.validators.register(object, ...)
151
+ when :coercion then settings.coercions.register(object, ...)
157
152
  else raise "unknown registry type #{type.inspect}"
158
153
  end
159
154
  end
@@ -170,11 +165,13 @@ module CMDx
170
165
  # @rbs (Symbol type, untyped object, *untyped) -> void
171
166
  def deregister(type, object, ...)
172
167
  case type
173
- when :attribute then settings[:attributes].deregister(object, ...)
174
- when :callback then settings[:callbacks].deregister(object, ...)
175
- when :coercion then settings[:coercions].deregister(object, ...)
176
- when :middleware then settings[:middlewares].deregister(object, ...)
177
- when :validator then settings[:validators].deregister(object, ...)
168
+ when :attribute
169
+ settings.attributes.undefine_readers_on!(self, object)
170
+ settings.attributes.deregister(object)
171
+ when :callback then settings.callbacks.deregister(object, ...)
172
+ when :middleware then settings.middlewares.deregister(object, ...)
173
+ when :validator then settings.validators.deregister(object, ...)
174
+ when :coercion then settings.coercions.deregister(object, ...)
178
175
  else raise "unknown registry type #{type.inspect}"
179
176
  end
180
177
  end
@@ -229,7 +226,7 @@ module CMDx
229
226
  #
230
227
  # @rbs (*untyped names) -> void
231
228
  def returns(*names)
232
- settings[:returns] |= names.map(&:to_sym)
229
+ settings.returns |= names.map(&:to_sym)
233
230
  end
234
231
 
235
232
  # Removes declared returns from the task.
@@ -241,7 +238,7 @@ module CMDx
241
238
  #
242
239
  # @rbs (*Symbol names) -> void
243
240
  def remove_returns(*names)
244
- settings[:returns] -= names.map(&:to_sym)
241
+ settings.returns -= names.map(&:to_sym)
245
242
  end
246
243
  alias remove_return remove_returns
247
244
 
@@ -259,7 +256,7 @@ module CMDx
259
256
  #
260
257
  # @rbs () -> Hash[Symbol, Hash[Symbol, untyped]]
261
258
  def attributes_schema
262
- Array(settings[:attributes]).to_h do |attr|
259
+ Utils::Wrap.array(settings.attributes).to_h do |attr|
263
260
  [attr.method_name, attr.to_h]
264
261
  end
265
262
  end
@@ -290,11 +287,8 @@ module CMDx
290
287
  #
291
288
  # @example
292
289
  # result = MyTask.execute(name: "example")
293
- # if result.success?
294
- # puts "Task completed successfully"
295
- # end
296
290
  #
297
- # @rbs (*untyped args, **untyped kwargs) ?{ (Result) -> void } -> Result
291
+ # @rbs (*untyped args, dry_run: bool, **untyped kwargs) ?{ (Result) -> void } -> Result
298
292
  def execute(*args, **kwargs)
299
293
  task = new(*args, **kwargs)
300
294
  task.execute(raise: false)
@@ -311,9 +305,8 @@ module CMDx
311
305
  #
312
306
  # @example
313
307
  # result = MyTask.execute!(name: "example")
314
- # # Will raise an exception if execution fails
315
308
  #
316
- # @rbs (*untyped args, **untyped kwargs) ?{ (Result) -> void } -> Result
309
+ # @rbs (*untyped args, dry_run: bool, **untyped kwargs) ?{ (Result) -> void } -> Result
317
310
  def execute!(*args, **kwargs)
318
311
  task = new(*args, **kwargs)
319
312
  task.execute(raise: true)
@@ -364,14 +357,13 @@ module CMDx
364
357
  # @rbs () -> Logger
365
358
  def logger
366
359
  @logger ||= begin
367
- log_instance = self.class.settings[:logger] || CMDx.configuration.logger
368
- log_level = self.class.settings[:log_level]
369
- log_formatter = self.class.settings[:log_formatter]
360
+ settings = self.class.settings
361
+ log_instance = settings.logger || CMDx.configuration.logger
370
362
 
371
- if log_level || log_formatter
363
+ if settings.log_level || settings.log_formatter
372
364
  log_instance = log_instance.dup
373
- log_instance.level = log_level if log_level
374
- log_instance.formatter = log_formatter if log_formatter
365
+ log_instance.level = settings.log_level if settings.log_level
366
+ log_instance.formatter = settings.log_formatter if settings.log_formatter
375
367
  end
376
368
 
377
369
  log_instance
@@ -397,11 +389,11 @@ module CMDx
397
389
  {
398
390
  index: result.index,
399
391
  chain_id: chain.id,
400
- type: self.class.include?(Workflow) ? "Workflow" : "Task",
401
- tags: self.class.settings[:tags],
392
+ type: self.class.type,
402
393
  class: self.class.name,
394
+ id:,
403
395
  dry_run: dry_run?,
404
- id:
396
+ tags: self.class.settings.tags
405
397
  }
406
398
  end
407
399
 
@@ -32,7 +32,7 @@ module CMDx
32
32
  #
33
33
  # @rbs (untyped message) -> untyped
34
34
  def to_log(message)
35
- if message.respond_to?(:to_h) && message.class.ancestors.any? { |a| a.name&.start_with?("CMDx::") }
35
+ if message.respond_to?(:to_h) && cmdx_based_object?(message.class)
36
36
  message.to_h
37
37
  else
38
38
  message
@@ -61,6 +61,22 @@ module CMDx
61
61
  hash.map(&block).join(" ")
62
62
  end
63
63
 
64
+ private
65
+
66
+ # Checks if a class belongs to the CMDx namespace, caching per class.
67
+ #
68
+ # @param klass [Class] The class to check
69
+ #
70
+ # @return [Boolean] true if the class is in the CMDx namespace
71
+ #
72
+ # @rbs (Class klass) -> bool
73
+ def cmdx_based_object?(klass)
74
+ @cmdx_classes ||= {}
75
+ return @cmdx_classes[klass] if @cmdx_classes.key?(klass)
76
+
77
+ @cmdx_classes[klass] = klass.ancestors.any? { |a| a.name&.start_with?("CMDx::") }
78
+ end
79
+
64
80
  end
65
81
  end
66
82
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Utils
5
+ # Provides normalization utilities for a variety of objects
6
+ # into consistent formats.
7
+ module Normalize
8
+
9
+ extend self
10
+
11
+ # Normalizes an exception into a string representation.
12
+ #
13
+ # @param exception [Exception] The exception to normalize
14
+ #
15
+ # @return [String] The normalized exception string
16
+ #
17
+ # @example From exception
18
+ # Normalize.exception(StandardError.new("test"))
19
+ # # => "[StandardError] test"
20
+ #
21
+ # @rbs (Exception exception) -> String
22
+ def exception(exception)
23
+ "[#{exception.class}] #{exception.message}"
24
+ end
25
+
26
+ # Normalizes an object into an array of unique status strings.
27
+ #
28
+ # @param object [Object] The object to normalize into status strings
29
+ #
30
+ # @return [Array<String>] Unique status strings
31
+ #
32
+ # @example From array of symbols
33
+ # Normalize.statuses([:success, :pending, :success])
34
+ # # => ["success", "pending"]
35
+ # @example From single value
36
+ # Normalize.statuses(:success)
37
+ # # => ["success"]
38
+ # @example From nil
39
+ # Normalize.statuses(nil)
40
+ # # => []
41
+ #
42
+ # @rbs (untyped object) -> Array[String]
43
+ def statuses(object)
44
+ ary = Wrap.array(object)
45
+ return EMPTY_ARRAY if ary.empty?
46
+
47
+ ary.map(&:to_s).uniq
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Utils
5
+ # Provides array wrapping utilities for normalizing input values
6
+ # into consistent array structures.
7
+ module Wrap
8
+
9
+ extend self
10
+
11
+ # Wraps an object in an array if it is not already an array.
12
+ #
13
+ # @param object [Object] The object to wrap in an array
14
+ #
15
+ # @return [Array] The wrapped array
16
+ #
17
+ # @example Already an array
18
+ # Wrap.array([1, 2, 3])
19
+ # # => [1, 2, 3]
20
+ # @example Single value
21
+ # Wrap.array(1)
22
+ # # => [1]
23
+ # @example Nil value
24
+ # Wrap.array(nil)
25
+ # # => []
26
+ #
27
+ # @rbs (untyped object) -> Array[untyped]
28
+ def array(object)
29
+ case object
30
+ when Array then object
31
+ when NilClass then EMPTY_ARRAY
32
+ else Array(object)
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -3,21 +3,13 @@
3
3
  module CMDx
4
4
  # Registry for managing validation rules and their corresponding validator classes.
5
5
  # Provides methods to register, deregister, and execute validators against task values.
6
+ #
7
+ # Supports copy-on-write semantics: a duped registry shares the parent's
8
+ # data until a write operation triggers materialization.
6
9
  class ValidatorRegistry
7
10
 
8
11
  extend Forwardable
9
12
 
10
- # Returns the internal registry mapping validator types to classes.
11
- #
12
- # @return [Hash{Symbol => Class}] Hash of validator type names to validator classes
13
- #
14
- # @example
15
- # registry.registry # => { presence: Validators::Presence, format: Validators::Format }
16
- #
17
- # @rbs @registry: Hash[Symbol, Class]
18
- attr_reader :registry
19
- alias to_h registry
20
-
21
13
  def_delegators :registry, :keys
22
14
 
23
15
  # Initialize a new validator registry with default validators.
@@ -39,14 +31,30 @@ module CMDx
39
31
  }
40
32
  end
41
33
 
42
- # Create a duplicate of the registry with copied internal state.
34
+ # Sets up copy-on-write state when duplicated via dup.
35
+ #
36
+ # @param source [ValidatorRegistry] The registry being duplicated
37
+ #
38
+ # @rbs (ValidatorRegistry source) -> void
39
+ def initialize_dup(source)
40
+ @parent = source
41
+ @registry = nil
42
+ super
43
+ end
44
+
45
+ # Returns the internal registry mapping validator types to classes.
46
+ # Delegates to the parent registry when not yet materialized.
43
47
  #
44
- # @return [ValidatorRegistry] A new validator registry with duplicated registry hash
48
+ # @return [Hash{Symbol => Class}] Hash of validator type names to validator classes
45
49
  #
46
- # @rbs () -> ValidatorRegistry
47
- def dup
48
- self.class.new(registry.dup)
50
+ # @example
51
+ # registry.registry # => { presence: Validators::Presence, format: Validators::Format }
52
+ #
53
+ # @rbs () -> Hash[Symbol, Class]
54
+ def registry
55
+ @registry || @parent.registry
49
56
  end
57
+ alias to_h registry
50
58
 
51
59
  # Register a new validator class with the given name.
52
60
  #
@@ -61,7 +69,9 @@ module CMDx
61
69
  #
62
70
  # @rbs ((String | Symbol) name, Class validator) -> self
63
71
  def register(name, validator)
64
- registry[name.to_sym] = validator
72
+ materialize!
73
+
74
+ @registry[name.to_sym] = validator
65
75
  self
66
76
  end
67
77
 
@@ -77,7 +87,9 @@ module CMDx
77
87
  #
78
88
  # @rbs ((String | Symbol) name) -> self
79
89
  def deregister(name)
80
- registry.delete(name.to_sym)
90
+ materialize!
91
+
92
+ @registry.delete(name.to_sym)
81
93
  self
82
94
  end
83
95
 
@@ -96,13 +108,13 @@ module CMDx
96
108
  # registry.validate(:length, task, password, { min: 8, allow_nil: false })
97
109
  #
98
110
  # @rbs (Symbol type, Task task, untyped value, untyped options) -> untyped
99
- def validate(type, task, value, options = {})
111
+ def validate(type, task, value, options = EMPTY_HASH)
100
112
  raise TypeError, "unknown validator type #{type.inspect}" unless registry.key?(type)
101
113
 
102
114
  match =
103
115
  if options.is_a?(Hash)
104
116
  case options
105
- in allow_nil: then allow_nil && value.nil?
117
+ in allow_nil: then !(allow_nil && value.nil?)
106
118
  else Utils::Condition.evaluate(task, options, value)
107
119
  end
108
120
  else
@@ -114,5 +126,18 @@ module CMDx
114
126
  Utils::Call.invoke(task, registry[type], value, options)
115
127
  end
116
128
 
129
+ private
130
+
131
+ # Copies the parent's registry data into this instance,
132
+ # severing the copy-on-write link.
133
+ #
134
+ # @rbs () -> void
135
+ def materialize!
136
+ return if @registry
137
+
138
+ @registry = @parent.registry.dup
139
+ @parent = nil
140
+ end
141
+
117
142
  end
118
143
  end
@@ -40,7 +40,7 @@ module CMDx
40
40
  # # => raises ValidationError with custom message
41
41
  #
42
42
  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> nil
43
- def call(value, options = {})
43
+ def call(value, options = EMPTY_HASH)
44
44
  match =
45
45
  if value.is_a?(String)
46
46
  /\S/.match?(value)
@@ -34,12 +34,12 @@ module CMDx
34
34
  # Exclusion.call("test", in: ["test", "demo"], message: "value %{values} is forbidden")
35
35
  #
36
36
  # @rbs (untyped value, Hash[Symbol, untyped] options) -> nil
37
- def call(value, options = {})
37
+ def call(value, options = EMPTY_HASH)
38
38
  values = options[:in] || options[:within]
39
39
 
40
40
  if values.is_a?(Range)
41
41
  raise_within_validation_error!(values.begin, values.end, options) if values.cover?(value)
42
- elsif Array(values).any? { |v| v === value }
42
+ elsif Utils::Wrap.array(values).any? { |v| v === value }
43
43
  raise_of_validation_error!(values, options)
44
44
  end
45
45
  end
@@ -40,7 +40,7 @@ module CMDx
40
40
  # # => raises ValidationError with custom message
41
41
  #
42
42
  # @rbs (untyped value, (Hash[Symbol, untyped] | Regexp) options) -> nil
43
- def call(value, options = {})
43
+ def call(value, options = EMPTY_HASH)
44
44
  match =
45
45
  if options.is_a?(Regexp)
46
46
  value&.match?(options)
@@ -36,12 +36,12 @@ module CMDx
36
36
  # Inclusion.call("test", in: ["admin", "user"], message: "must be one of: %{values}")
37
37
  #
38
38
  # @rbs (untyped value, Hash[Symbol, untyped] options) -> nil
39
- def call(value, options = {})
39
+ def call(value, options = EMPTY_HASH)
40
40
  values = options[:in] || options[:within]
41
41
 
42
42
  if values.is_a?(Range)
43
43
  raise_within_validation_error!(values.begin, values.end, options) unless values.cover?(value)
44
- elsif Array(values).none? { |v| v === value }
44
+ elsif Utils::Wrap.array(values).none? { |v| v === value }
45
45
  raise_of_validation_error!(values, options)
46
46
  end
47
47
  end
@@ -53,7 +53,7 @@ module CMDx
53
53
  # # => nil (validation passes - length 5 is not in excluded range)
54
54
  #
55
55
  # @rbs (untyped value, Hash[Symbol, untyped] options) -> nil
56
- def call(value, options = {})
56
+ def call(value, options = EMPTY_HASH)
57
57
  length = value&.length
58
58
 
59
59
  case options
@@ -50,7 +50,7 @@ module CMDx
50
50
  # # => nil (validation passes - 5 is not in 1..10)
51
51
  #
52
52
  # @rbs (Numeric value, Hash[Symbol, untyped] options) -> nil
53
- def call(value, options = {})
53
+ def call(value, options = EMPTY_HASH)
54
54
  case options
55
55
  in within:
56
56
  raise_within_validation_error!(within.begin, within.end, options) unless within&.cover?(value)
@@ -40,7 +40,7 @@ module CMDx
40
40
  # # => raises ValidationError with custom message
41
41
  #
42
42
  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> nil
43
- def call(value, options = {})
43
+ def call(value, options = EMPTY_HASH)
44
44
  match =
45
45
  if value.is_a?(String)
46
46
  /\S/.match?(value)
data/lib/cmdx/version.rb CHANGED
@@ -5,6 +5,6 @@ module CMDx
5
5
  # @return [String] the version of the CMDx gem
6
6
  #
7
7
  # @rbs return: String
8
- VERSION = "1.18.0"
8
+ VERSION = "1.20.0"
9
9
 
10
10
  end
data/lib/cmdx.rb CHANGED
@@ -15,6 +15,18 @@ require "zeitwerk"
15
15
 
16
16
  module CMDx
17
17
 
18
+ # @rbs EMPTY_ARRAY: Array[untyped]
19
+ EMPTY_ARRAY = [].freeze
20
+ private_constant :EMPTY_ARRAY
21
+
22
+ # @rbs EMPTY_HASH: Hash[untyped, untyped]
23
+ EMPTY_HASH = {}.freeze
24
+ private_constant :EMPTY_HASH
25
+
26
+ # @rbs EMPTY_STRING: String
27
+ EMPTY_STRING = ""
28
+ private_constant :EMPTY_STRING
29
+
18
30
  extend self
19
31
 
20
32
  # Returns the path to the CMDx gem.
@@ -32,6 +32,17 @@ CMDx.configure do |config|
32
32
  level: Logger::INFO
33
33
  )
34
34
 
35
+ # Rollback configuration - controls which statuses trigger task rollback
36
+ # See https://github.com/drexed/cmdx/blob/main/docs/outcomes/statuses.md for more details
37
+ #
38
+ # Available statuses: "success", "skipped", "failed"
39
+ # If set to an empty array, task will never rollback
40
+ config.rollback_on = %w[failed]
41
+
42
+ # Default locale configuration - used for built-in translation lookups
43
+ # Must match the basename of a YAML file in lib/locales/ (e.g. "en", "es", "ja")
44
+ # config.default_locale = "en"
45
+
35
46
  # Backtrace configuration - controls whether to log backtraces on faults and exceptions
36
47
  # https://github.com/drexed/cmdx/blob/main/docs/getting_started.md#backtraces
37
48
  # config.backtrace = false
data/mkdocs.yml CHANGED
@@ -144,6 +144,8 @@ plugins:
144
144
  - returns.md: Declaring expected context outputs that must be set after task execution
145
145
  - workflows.md: Composing multiple tasks into sequential pipelines with conditional execution
146
146
  More:
147
+ - testing.md: Best practices for testing CMDx tasks and workflows with RSpec
148
+ - exceptions.md: Complete exception hierarchy and error type reference
147
149
  - tips_and_tricks.md: Best practices, patterns, and techniques for maintainable CMDx applications
148
150
  - comparison.md: Comparison with other command/service object frameworks
149
151
  nav:
@@ -181,13 +183,15 @@ nav:
181
183
  - Returns: returns.md
182
184
  - Workflows: workflows.md
183
185
  - More:
186
+ - Testing: testing.md
187
+ - Exceptions: exceptions.md
184
188
  - Tips and Tricks: tips_and_tricks.md
185
189
  - Comparison: comparison.md
186
190
  - References:
187
191
  - API Documentation: https://drexed.github.io/cmdx/api/index.html
188
192
  - llms.txt: https://drexed.github.io/cmdx/llms.txt
189
193
  - llms-full.txt: https://drexed.github.io/cmdx/llms-full.txt
190
- - SKILL.md: https://github.com/drexed/cmdx/blob/main/docs/SKILL.md
194
+ - AI Skills: https://github.com/drexed/cmdx/blob/main/skills
191
195
  - Ecosystem:
192
196
  - cmdx-rspec: https://github.com/drexed/cmdx-rspec
193
197
  - Blog: blog/index.md
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cmdx
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.18.0
4
+ version: 1.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juan Gomez
@@ -93,20 +93,6 @@ dependencies:
93
93
  - - ">="
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0'
96
- - !ruby/object:Gem::Dependency
97
- name: parallel
98
- requirement: !ruby/object:Gem::Requirement
99
- requirements:
100
- - - ">="
101
- - !ruby/object:Gem::Version
102
- version: '0'
103
- type: :development
104
- prerelease: false
105
- version_requirements: !ruby/object:Gem::Requirement
106
- requirements:
107
- - - ">="
108
- - !ruby/object:Gem::Version
109
- version: '0'
110
96
  - !ruby/object:Gem::Dependency
111
97
  name: rake
112
98
  requirement: !ruby/object:Gem::Requirement
@@ -285,13 +271,18 @@ files:
285
271
  - lib/cmdx/middlewares/correlate.rb
286
272
  - lib/cmdx/middlewares/runtime.rb
287
273
  - lib/cmdx/middlewares/timeout.rb
274
+ - lib/cmdx/parallelizer.rb
288
275
  - lib/cmdx/pipeline.rb
289
276
  - lib/cmdx/railtie.rb
290
277
  - lib/cmdx/result.rb
278
+ - lib/cmdx/retry.rb
279
+ - lib/cmdx/settings.rb
291
280
  - lib/cmdx/task.rb
292
281
  - lib/cmdx/utils/call.rb
293
282
  - lib/cmdx/utils/condition.rb
294
283
  - lib/cmdx/utils/format.rb
284
+ - lib/cmdx/utils/normalize.rb
285
+ - lib/cmdx/utils/wrap.rb
295
286
  - lib/cmdx/validator_registry.rb
296
287
  - lib/cmdx/validators/absence.rb
297
288
  - lib/cmdx/validators/exclusion.rb