cmdx 1.20.0 → 2.0.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 (195) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +131 -1
  3. data/README.md +37 -24
  4. data/lib/cmdx/.DS_Store +0 -0
  5. data/lib/cmdx/callbacks.rb +179 -0
  6. data/lib/cmdx/chain.rb +78 -175
  7. data/lib/cmdx/coercions/array.rb +19 -33
  8. data/lib/cmdx/coercions/big_decimal.rb +12 -29
  9. data/lib/cmdx/coercions/boolean.rb +25 -45
  10. data/lib/cmdx/coercions/coerce.rb +32 -0
  11. data/lib/cmdx/coercions/complex.rb +12 -27
  12. data/lib/cmdx/coercions/date.rb +29 -33
  13. data/lib/cmdx/coercions/date_time.rb +29 -33
  14. data/lib/cmdx/coercions/float.rb +8 -29
  15. data/lib/cmdx/coercions/hash.rb +17 -43
  16. data/lib/cmdx/coercions/integer.rb +8 -32
  17. data/lib/cmdx/coercions/rational.rb +12 -33
  18. data/lib/cmdx/coercions/string.rb +6 -24
  19. data/lib/cmdx/coercions/symbol.rb +12 -26
  20. data/lib/cmdx/coercions/time.rb +31 -35
  21. data/lib/cmdx/coercions.rb +174 -0
  22. data/lib/cmdx/configuration.rb +45 -225
  23. data/lib/cmdx/context.rb +263 -242
  24. data/lib/cmdx/deprecation.rb +67 -0
  25. data/lib/cmdx/deprecators/error.rb +22 -0
  26. data/lib/cmdx/deprecators/log.rb +22 -0
  27. data/lib/cmdx/deprecators/warn.rb +21 -0
  28. data/lib/cmdx/deprecators.rb +101 -0
  29. data/lib/cmdx/errors.rb +145 -79
  30. data/lib/cmdx/executors/fiber.rb +42 -0
  31. data/lib/cmdx/executors/thread.rb +36 -0
  32. data/lib/cmdx/executors.rb +95 -0
  33. data/lib/cmdx/fault.rb +85 -78
  34. data/lib/cmdx/i18n_proxy.rb +104 -0
  35. data/lib/cmdx/input.rb +294 -0
  36. data/lib/cmdx/inputs.rb +218 -0
  37. data/lib/cmdx/log_formatters/json.rb +9 -20
  38. data/lib/cmdx/log_formatters/key_value.rb +10 -21
  39. data/lib/cmdx/log_formatters/line.rb +7 -19
  40. data/lib/cmdx/log_formatters/logstash.rb +8 -21
  41. data/lib/cmdx/log_formatters/raw.rb +8 -20
  42. data/lib/cmdx/logger_proxy.rb +30 -0
  43. data/lib/cmdx/mergers/deep_merge.rb +23 -0
  44. data/lib/cmdx/mergers/last_write_wins.rb +23 -0
  45. data/lib/cmdx/mergers/no_merge.rb +20 -0
  46. data/lib/cmdx/mergers.rb +95 -0
  47. data/lib/cmdx/middlewares.rb +128 -0
  48. data/lib/cmdx/output.rb +115 -0
  49. data/lib/cmdx/outputs.rb +66 -0
  50. data/lib/cmdx/pipeline.rb +144 -131
  51. data/lib/cmdx/railtie.rb +10 -36
  52. data/lib/cmdx/result.rb +252 -473
  53. data/lib/cmdx/retriers/bounded_random.rb +24 -0
  54. data/lib/cmdx/retriers/decorrelated_jitter.rb +28 -0
  55. data/lib/cmdx/retriers/exponential.rb +23 -0
  56. data/lib/cmdx/retriers/fibonacci.rb +39 -0
  57. data/lib/cmdx/retriers/full_random.rb +23 -0
  58. data/lib/cmdx/retriers/half_random.rb +24 -0
  59. data/lib/cmdx/retriers/linear.rb +23 -0
  60. data/lib/cmdx/retriers.rb +106 -0
  61. data/lib/cmdx/retry.rb +117 -138
  62. data/lib/cmdx/runtime.rb +251 -0
  63. data/lib/cmdx/settings.rb +68 -196
  64. data/lib/cmdx/signal.rb +165 -0
  65. data/lib/cmdx/task.rb +443 -336
  66. data/lib/cmdx/telemetry.rb +108 -0
  67. data/lib/cmdx/util.rb +73 -0
  68. data/lib/cmdx/validators/absence.rb +10 -39
  69. data/lib/cmdx/validators/exclusion.rb +33 -52
  70. data/lib/cmdx/validators/format.rb +19 -49
  71. data/lib/cmdx/validators/inclusion.rb +33 -54
  72. data/lib/cmdx/validators/length.rb +125 -127
  73. data/lib/cmdx/validators/numeric.rb +123 -123
  74. data/lib/cmdx/validators/presence.rb +10 -39
  75. data/lib/cmdx/validators/validate.rb +31 -0
  76. data/lib/cmdx/validators.rb +161 -0
  77. data/lib/cmdx/version.rb +2 -4
  78. data/lib/cmdx/workflow.rb +74 -82
  79. data/lib/cmdx.rb +111 -42
  80. data/lib/generators/cmdx/install_generator.rb +7 -17
  81. data/lib/generators/cmdx/task_generator.rb +12 -29
  82. data/lib/generators/cmdx/templates/install.rb +128 -52
  83. data/lib/generators/cmdx/templates/task.rb.tt +1 -1
  84. data/lib/generators/cmdx/templates/workflow.rb.tt +1 -2
  85. data/lib/generators/cmdx/workflow_generator.rb +12 -29
  86. data/lib/locales/en.yml +9 -6
  87. data/mkdocs.yml +25 -23
  88. metadata +39 -138
  89. data/lib/cmdx/attribute.rb +0 -440
  90. data/lib/cmdx/attribute_registry.rb +0 -185
  91. data/lib/cmdx/attribute_value.rb +0 -252
  92. data/lib/cmdx/callback_registry.rb +0 -169
  93. data/lib/cmdx/coercion_registry.rb +0 -138
  94. data/lib/cmdx/deprecator.rb +0 -77
  95. data/lib/cmdx/exception.rb +0 -46
  96. data/lib/cmdx/executor.rb +0 -374
  97. data/lib/cmdx/identifier.rb +0 -30
  98. data/lib/cmdx/locale.rb +0 -78
  99. data/lib/cmdx/middleware_registry.rb +0 -148
  100. data/lib/cmdx/middlewares/correlate.rb +0 -140
  101. data/lib/cmdx/middlewares/runtime.rb +0 -62
  102. data/lib/cmdx/middlewares/timeout.rb +0 -78
  103. data/lib/cmdx/parallelizer.rb +0 -100
  104. data/lib/cmdx/utils/call.rb +0 -53
  105. data/lib/cmdx/utils/condition.rb +0 -71
  106. data/lib/cmdx/utils/format.rb +0 -82
  107. data/lib/cmdx/utils/normalize.rb +0 -52
  108. data/lib/cmdx/utils/wrap.rb +0 -38
  109. data/lib/cmdx/validator_registry.rb +0 -143
  110. data/lib/generators/cmdx/locale_generator.rb +0 -39
  111. data/lib/locales/af.yml +0 -53
  112. data/lib/locales/ar.yml +0 -53
  113. data/lib/locales/az.yml +0 -53
  114. data/lib/locales/be.yml +0 -53
  115. data/lib/locales/bg.yml +0 -53
  116. data/lib/locales/bn.yml +0 -53
  117. data/lib/locales/bs.yml +0 -53
  118. data/lib/locales/ca.yml +0 -53
  119. data/lib/locales/cnr.yml +0 -53
  120. data/lib/locales/cs.yml +0 -53
  121. data/lib/locales/cy.yml +0 -53
  122. data/lib/locales/da.yml +0 -53
  123. data/lib/locales/de.yml +0 -53
  124. data/lib/locales/dz.yml +0 -53
  125. data/lib/locales/el.yml +0 -53
  126. data/lib/locales/eo.yml +0 -53
  127. data/lib/locales/es.yml +0 -53
  128. data/lib/locales/et.yml +0 -53
  129. data/lib/locales/eu.yml +0 -53
  130. data/lib/locales/fa.yml +0 -53
  131. data/lib/locales/fi.yml +0 -53
  132. data/lib/locales/fr.yml +0 -53
  133. data/lib/locales/fy.yml +0 -53
  134. data/lib/locales/gd.yml +0 -53
  135. data/lib/locales/gl.yml +0 -53
  136. data/lib/locales/he.yml +0 -53
  137. data/lib/locales/hi.yml +0 -53
  138. data/lib/locales/hr.yml +0 -53
  139. data/lib/locales/hu.yml +0 -53
  140. data/lib/locales/hy.yml +0 -53
  141. data/lib/locales/id.yml +0 -53
  142. data/lib/locales/is.yml +0 -53
  143. data/lib/locales/it.yml +0 -53
  144. data/lib/locales/ja.yml +0 -53
  145. data/lib/locales/ka.yml +0 -53
  146. data/lib/locales/kk.yml +0 -53
  147. data/lib/locales/km.yml +0 -53
  148. data/lib/locales/kn.yml +0 -53
  149. data/lib/locales/ko.yml +0 -53
  150. data/lib/locales/lb.yml +0 -53
  151. data/lib/locales/lo.yml +0 -53
  152. data/lib/locales/lt.yml +0 -53
  153. data/lib/locales/lv.yml +0 -53
  154. data/lib/locales/mg.yml +0 -53
  155. data/lib/locales/mk.yml +0 -53
  156. data/lib/locales/ml.yml +0 -53
  157. data/lib/locales/mn.yml +0 -53
  158. data/lib/locales/mr-IN.yml +0 -53
  159. data/lib/locales/ms.yml +0 -53
  160. data/lib/locales/nb.yml +0 -53
  161. data/lib/locales/ne.yml +0 -53
  162. data/lib/locales/nl.yml +0 -53
  163. data/lib/locales/nn.yml +0 -53
  164. data/lib/locales/oc.yml +0 -53
  165. data/lib/locales/or.yml +0 -53
  166. data/lib/locales/pa.yml +0 -53
  167. data/lib/locales/pl.yml +0 -53
  168. data/lib/locales/pt.yml +0 -53
  169. data/lib/locales/rm.yml +0 -53
  170. data/lib/locales/ro.yml +0 -53
  171. data/lib/locales/ru.yml +0 -53
  172. data/lib/locales/sc.yml +0 -53
  173. data/lib/locales/sk.yml +0 -53
  174. data/lib/locales/sl.yml +0 -53
  175. data/lib/locales/sq.yml +0 -53
  176. data/lib/locales/sr.yml +0 -53
  177. data/lib/locales/st.yml +0 -53
  178. data/lib/locales/sv.yml +0 -53
  179. data/lib/locales/sw.yml +0 -53
  180. data/lib/locales/ta.yml +0 -53
  181. data/lib/locales/te.yml +0 -53
  182. data/lib/locales/th.yml +0 -53
  183. data/lib/locales/tl.yml +0 -53
  184. data/lib/locales/tr.yml +0 -53
  185. data/lib/locales/tt.yml +0 -53
  186. data/lib/locales/ug.yml +0 -53
  187. data/lib/locales/uk.yml +0 -53
  188. data/lib/locales/ur.yml +0 -53
  189. data/lib/locales/uz.yml +0 -53
  190. data/lib/locales/vi.yml +0 -53
  191. data/lib/locales/wo.yml +0 -53
  192. data/lib/locales/zh-CN.yml +0 -53
  193. data/lib/locales/zh-HK.yml +0 -53
  194. data/lib/locales/zh-TW.yml +0 -53
  195. data/lib/locales/zh-YUE.yml +0 -53
data/lib/cmdx/locale.rb DELETED
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CMDx
4
- # Provides internationalization and localization support for CMDx.
5
- # Handles translation lookups with fallback to the configured locale's
6
- # default messages when I18n gem is not available.
7
- module Locale
8
-
9
- extend self
10
-
11
- # Translates a key to the current locale with optional interpolation.
12
- # Falls back to the configured locale's YAML file translations if
13
- # I18n gem is unavailable.
14
- #
15
- # @param key [String, Symbol] The translation key (supports dot notation)
16
- # @param options [Hash] Translation options
17
- # @option options [String] :default Fallback message if translation missing
18
- # @option options [String] :locale Target locale (when I18n available)
19
- # @option options [Hash] :scope Translation scope (when I18n available)
20
- # @option options [Object] :* Any other options passed to I18n.t or string interpolation
21
- #
22
- # @return [String] The translated message
23
- #
24
- # @raise [ArgumentError] When interpolation fails due to missing keys
25
- #
26
- # @example Basic translation
27
- # Locale.translate("errors.invalid_input")
28
- # # => "Invalid input provided"
29
- # @example With interpolation
30
- # Locale.translate("welcome.message", name: "John")
31
- # # => "Welcome, John!"
32
- # @example With fallback
33
- # Locale.translate("missing.key", default: "Custom fallback message")
34
- # # => "Custom fallback message"
35
- #
36
- # @rbs ((String | Symbol) key, **untyped options) -> String
37
- def translate(key, **options)
38
- options[:default] ||= translation_default(key)
39
- return ::I18n.t(key, **options) if defined?(::I18n)
40
-
41
- case message = options.delete(:default)
42
- when String then message % options
43
- when NilClass then "Translation missing: #{key}"
44
- else message
45
- end
46
- end
47
-
48
- # @see #translate
49
- alias t translate
50
-
51
- private
52
-
53
- # Resolves and caches the default translation for a key by digging
54
- # into the configured locale's YAML translations.
55
- #
56
- # @param key [String, Symbol] The translation key
57
- #
58
- # @return [String, nil] The resolved translation or nil
59
- #
60
- # @rbs ((String | Symbol) key) -> String?
61
- def translation_default(key)
62
- tkey = "#{CMDx.configuration.default_locale}.#{key}"
63
-
64
- @translation_defaults ||= {}
65
- return @translation_defaults[tkey] if @translation_defaults.key?(tkey)
66
-
67
- @default_translations ||= begin
68
- path = CMDx.gem_path.join("lib/locales/#{CMDx.configuration.default_locale}.yml")
69
- raise ArgumentError, "locale file not found: #{path}" unless path.exist?
70
-
71
- YAML.load_file(path).freeze
72
- end
73
-
74
- @translation_defaults[tkey] = @default_translations.dig(*tkey.split("."))
75
- end
76
-
77
- end
78
- end
@@ -1,148 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CMDx
4
- # Registry for managing middleware components in a task execution chain.
5
- #
6
- # The MiddlewareRegistry maintains an ordered list of middleware components
7
- # that can be inserted, removed, and executed in sequence. Each middleware
8
- # can be configured with specific options and is executed in the order
9
- # they were registered.
10
- #
11
- # Supports copy-on-write semantics: a duped registry shares the parent's
12
- # data until a write operation triggers materialization.
13
- class MiddlewareRegistry
14
-
15
- # Initialize a new middleware registry.
16
- #
17
- # @param registry [Array, nil] Initial array of middleware entries
18
- #
19
- # @example
20
- # registry = MiddlewareRegistry.new
21
- # registry = MiddlewareRegistry.new([[MyMiddleware, {option: 'value'}]])
22
- #
23
- # @rbs (?Array[Array[untyped]]? registry) -> void
24
- def initialize(registry = nil)
25
- @registry = registry || []
26
- end
27
-
28
- # Sets up copy-on-write state when duplicated via dup.
29
- #
30
- # @param source [MiddlewareRegistry] The registry being duplicated
31
- #
32
- # @rbs (MiddlewareRegistry source) -> void
33
- def initialize_dup(source)
34
- @parent = source
35
- @registry = nil
36
- super
37
- end
38
-
39
- # Returns the ordered collection of middleware entries.
40
- # Delegates to the parent registry when not yet materialized.
41
- #
42
- # @return [Array<Array>] Array of middleware-options pairs
43
- #
44
- # @example
45
- # registry.registry # => [[LoggingMiddleware, {level: :debug}], [AuthMiddleware, {}]]
46
- #
47
- # @rbs () -> Array[Array[untyped]]
48
- def registry
49
- @registry || @parent.registry
50
- end
51
- alias to_a registry
52
-
53
- # Register a middleware component in the registry.
54
- #
55
- # @param middleware [Object] The middleware object to register
56
- # @param at [Integer] Position to insert the middleware (default: -1, end of list)
57
- # @param options [Hash] Configuration options for the middleware
58
- # @option options [Symbol] :key Configuration key for the middleware
59
- # @option options [Object] :value Configuration value for the middleware
60
- #
61
- # @return [MiddlewareRegistry] Returns self for method chaining
62
- #
63
- # @example
64
- # registry.register(LoggingMiddleware, at: 0, log_level: :debug)
65
- # registry.register(AuthMiddleware, at: -1, timeout: 30)
66
- #
67
- # @rbs (untyped middleware, ?at: Integer, **untyped options) -> self
68
- def register(middleware, at: -1, **options)
69
- materialize!
70
-
71
- @registry.insert(at, [middleware, options])
72
- self
73
- end
74
-
75
- # Remove a middleware component from the registry.
76
- #
77
- # @param middleware [Object] The middleware object to remove
78
- #
79
- # @return [MiddlewareRegistry] Returns self for method chaining
80
- #
81
- # @example
82
- # registry.deregister(LoggingMiddleware)
83
- #
84
- # @rbs (untyped middleware) -> self
85
- def deregister(middleware)
86
- materialize!
87
-
88
- @registry.reject! { |mw, _opts| mw == middleware }
89
- self
90
- end
91
-
92
- # Execute the middleware chain for a given task.
93
- #
94
- # @param task [Object] The task object to process through middleware
95
- #
96
- # @yield [task] Block to execute after all middleware processing
97
- # @yieldparam task [Object] The processed task object
98
- #
99
- # @return [Object] Result of the block execution
100
- #
101
- # @raise [ArgumentError] When no block is provided
102
- #
103
- # @example
104
- # result = registry.call!(my_task) do |processed_task|
105
- # processed_task.execute
106
- # end
107
- #
108
- # @rbs (untyped task) { (untyped) -> untyped } -> untyped
109
- def call!(task, &)
110
- raise ArgumentError, "block required" unless block_given?
111
-
112
- recursively_call_middleware(0, task, &)
113
- end
114
-
115
- private
116
-
117
- # Copies the parent's registry data into this instance,
118
- # severing the copy-on-write link.
119
- #
120
- # @rbs () -> void
121
- def materialize!
122
- return if @registry
123
-
124
- @registry = @parent.registry.map(&:dup)
125
- @parent = nil
126
- end
127
-
128
- # Recursively execute middleware in the chain.
129
- #
130
- # @param index [Integer] Current middleware index in the chain
131
- # @param task [Object] The task object being processed
132
- # @param block [Proc] Block to execute after middleware processing
133
- #
134
- # @yield [task] Block to execute after middleware processing
135
- # @yieldparam task [Object] The processed task object
136
- #
137
- # @return [Object] Result of the block execution or next middleware call
138
- #
139
- # @rbs (Integer index, untyped task) { (untyped) -> untyped } -> untyped
140
- def recursively_call_middleware(index, task, &block)
141
- return yield(task) if index >= registry.size
142
-
143
- middleware, options = registry[index]
144
- middleware.call(task, **options) { recursively_call_middleware(index + 1, task, &block) }
145
- end
146
-
147
- end
148
- end
@@ -1,140 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CMDx
4
- module Middlewares
5
- # Middleware for correlating task executions with unique identifiers.
6
- #
7
- # The Correlate middleware provides thread and fiber safe correlation ID management
8
- # for tracking task execution flows across different operations. It automatically
9
- # generates correlation IDs when none are provided and stores them in task result
10
- # metadata for traceability.
11
- module Correlate
12
-
13
- extend self
14
-
15
- # @rbs CONCURRENCY_KEY: Symbol
16
- CONCURRENCY_KEY = :cmdx_correlate
17
-
18
- # Retrieves the current correlation ID from local storage.
19
- #
20
- # @return [String, nil] The current correlation ID or nil if not set
21
- #
22
- # @example Get current correlation ID
23
- # Correlate.id # => "550e8400-e29b-41d4-a716-446655440000"
24
- #
25
- # @rbs () -> String?
26
- def id
27
- thread_or_fiber[CONCURRENCY_KEY]
28
- end
29
-
30
- # Sets the correlation ID in local storage.
31
- #
32
- # @param id [String] The correlation ID to set
33
- # @return [String] The set correlation ID
34
- #
35
- # @example Set correlation ID
36
- # Correlate.id = "abc-123-def"
37
- #
38
- # @rbs (String id) -> String
39
- def id=(id)
40
- thread_or_fiber[CONCURRENCY_KEY] = id
41
- end
42
-
43
- # Clears the current correlation ID from local storage.
44
- #
45
- # @return [nil] Always returns nil
46
- #
47
- # @example Clear correlation ID
48
- # Correlate.clear
49
- #
50
- # @rbs () -> nil
51
- def clear
52
- thread_or_fiber[CONCURRENCY_KEY] = nil
53
- end
54
-
55
- # Temporarily uses a new correlation ID for the duration of a block.
56
- # Restores the previous ID after the block completes, even if an error occurs.
57
- #
58
- # @param new_id [String] The correlation ID to use temporarily
59
- # @yield The block to execute with the new correlation ID
60
- # @return [Object] The result of the yielded block
61
- #
62
- # @example Use temporary correlation ID
63
- # Correlate.use("temp-id") do
64
- # # Operations here use "temp-id"
65
- # perform_operation
66
- # end
67
- # # Previous ID is restored
68
- #
69
- # @rbs (String new_id) { () -> untyped } -> untyped
70
- def use(new_id)
71
- old_id = id
72
- self.id = new_id
73
- yield
74
- ensure
75
- self.id = old_id
76
- end
77
-
78
- # Middleware entry point that applies correlation ID logic to task execution.
79
- #
80
- # Evaluates the condition from options and applies correlation ID handling
81
- # if enabled. Generates or retrieves correlation IDs based on the :id option
82
- # and stores them in task result metadata.
83
- #
84
- # @param task [Task] The task being executed
85
- # @param options [Hash] Configuration options for correlation
86
- # @option options [Symbol, Proc, Object, nil] :id The correlation ID source
87
- # @option options [Symbol, Proc, Object, nil] :if Condition to enable correlation
88
- # @option options [Symbol, Proc, Object, nil] :unless Condition to disable correlation
89
- #
90
- # @yield The task execution block
91
- #
92
- # @return [Object] The result of task execution
93
- #
94
- # @example Basic usage with automatic ID generation
95
- # Correlate.call(task, &block)
96
- # @example Use custom correlation ID
97
- # Correlate.call(task, id: "custom-123", &block)
98
- # @example Use task method for ID
99
- # Correlate.call(task, id: :correlation_id, &block)
100
- # @example Use proc for dynamic ID generation
101
- # Correlate.call(task, id: -> { "dynamic-#{Time.now.to_i}" }, &block)
102
- # @example Conditional correlation
103
- # Correlate.call(task, if: :enable_correlation, &block)
104
- #
105
- # @rbs (Task task, **untyped options) { () -> untyped } -> untyped
106
- def call(task, **options, &)
107
- return yield unless Utils::Condition.evaluate(task, options)
108
-
109
- correlation_id = task.result.metadata[:correlation_id] ||=
110
- id ||
111
- case callable = options[:id]
112
- when Symbol then task.send(callable)
113
- when Proc then task.instance_eval(&callable)
114
- else
115
- if callable.respond_to?(:call)
116
- callable.call(task)
117
- else
118
- callable || id || Identifier.generate
119
- end
120
- end
121
-
122
- use(correlation_id, &)
123
- end
124
-
125
- private
126
-
127
- # Returns the thread or fiber storage for the current execution context.
128
- #
129
- # @return [Hash] The thread or fiber storage
130
- #
131
- # @rbs () -> Hash
132
- if Fiber.respond_to?(:storage)
133
- def thread_or_fiber = Fiber.storage
134
- else
135
- def thread_or_fiber = Thread.current
136
- end
137
-
138
- end
139
- end
140
- end
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CMDx
4
- module Middlewares
5
- # Middleware for measuring task execution runtime.
6
- #
7
- # The Runtime middleware provides performance monitoring by measuring
8
- # the execution time of tasks using monotonic clock for accuracy.
9
- # It stores runtime measurements in task result metadata for analysis.
10
- module Runtime
11
-
12
- extend self
13
-
14
- # Middleware entry point that measures task execution runtime.
15
- #
16
- # Evaluates the condition from options and measures execution time
17
- # if enabled. Uses monotonic clock for precise timing measurements
18
- # and stores the result in task metadata.
19
- #
20
- # @param task [Task] The task being executed
21
- # @param options [Hash] Configuration options for runtime measurement
22
- # @option options [Symbol, Proc, Object, nil] :if Condition to enable runtime measurement
23
- # @option options [Symbol, Proc, Object, nil] :unless Condition to disable runtime measurement
24
- #
25
- # @yield The task execution block
26
- #
27
- # @return [Object] The result of task execution
28
- #
29
- # @example Basic usage with automatic runtime measurement
30
- # Runtime.call(task, &block)
31
- # @example Conditional runtime measurement
32
- # Runtime.call(task, if: :enable_profiling, &block)
33
- # @example Disable runtime measurement
34
- # Runtime.call(task, unless: :skip_profiling, &block)
35
- #
36
- # @rbs (Task task, **untyped options) { () -> untyped } -> untyped
37
- def call(task, **options)
38
- return yield unless Utils::Condition.evaluate(task, options)
39
-
40
- now = monotonic_time
41
- result = yield
42
- task.result.metadata[:runtime] = monotonic_time - now
43
- result
44
- end
45
-
46
- private
47
-
48
- # Gets the current monotonic time in milliseconds.
49
- #
50
- # Uses Process.clock_gettime with CLOCK_MONOTONIC for consistent
51
- # timing measurements that are not affected by system clock changes.
52
- #
53
- # @return [Integer] Current monotonic time in milliseconds
54
- #
55
- # @rbs () -> Integer
56
- def monotonic_time
57
- Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
58
- end
59
-
60
- end
61
- end
62
- end
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CMDx
4
- module Middlewares
5
- # Middleware for enforcing execution time limits on tasks.
6
- #
7
- # The Timeout middleware provides execution time control by wrapping
8
- # task execution with Ruby's Timeout module. It automatically fails
9
- # tasks that exceed the configured time limit and provides detailed
10
- # error information including the exceeded limit.
11
- module Timeout
12
-
13
- extend self
14
-
15
- # Default timeout limit in seconds when none is specified.
16
- #
17
- # @rbs DEFAULT_LIMIT: Integer
18
- DEFAULT_LIMIT = 3
19
-
20
- # Middleware entry point that enforces execution time limits.
21
- #
22
- # Evaluates the condition from options and applies timeout control
23
- # if enabled. Supports various timeout limit configurations including
24
- # numeric values, task method calls, and dynamic proc evaluation.
25
- #
26
- # @param task [Task] The task being executed
27
- # @param options [Hash] Configuration options for timeout control
28
- # @option options [Numeric, Symbol, Proc, Object] :seconds The timeout limit source
29
- # @option options [Symbol, Proc, Object, nil] :if Condition to enable timeout control
30
- # @option options [Symbol, Proc, Object, nil] :unless Condition to disable timeout control
31
- #
32
- # @yield The task execution block
33
- #
34
- # @return [Object] The result of task execution
35
- #
36
- # @raise [TimeoutError] When execution exceeds the configured limit
37
- #
38
- # @example Basic usage with default 3 second timeout
39
- # Timeout.call(task, &block)
40
- # @example Custom timeout limit in seconds
41
- # Timeout.call(task, seconds: 10, &block)
42
- # @example Use task method for timeout limit
43
- # Timeout.call(task, seconds: :timeout_limit, &block)
44
- # @example Use proc for dynamic timeout calculation
45
- # Timeout.call(task, seconds: -> { calculate_timeout }, &block)
46
- # @example Conditional timeout control
47
- # Timeout.call(task, if: :enable_timeout, &block)
48
- #
49
- # @rbs (Task task, **untyped options) { () -> untyped } -> untyped
50
- def call(task, **options, &)
51
- return yield unless Utils::Condition.evaluate(task, options)
52
-
53
- limit =
54
- case callable = options[:seconds]
55
- when Numeric then callable
56
- when Symbol then task.send(callable)
57
- when Proc then task.instance_eval(&callable)
58
- else callable.respond_to?(:call) ? callable.call(task) : DEFAULT_LIMIT
59
- end
60
-
61
- limit = Float(limit)
62
- return yield unless limit.positive?
63
-
64
- ::Timeout.timeout(limit, TimeoutError, "execution exceeded #{limit} seconds", &)
65
- rescue TimeoutError => e
66
- task.result.tap do |r|
67
- r.fail!(
68
- Utils::Normalize.exception(e),
69
- cause: e,
70
- source: :timeout,
71
- limit:
72
- )
73
- end
74
- end
75
-
76
- end
77
- end
78
- end
@@ -1,100 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CMDx
4
- # Bounded thread pool that processes items concurrently.
5
- #
6
- # Distributes work across a fixed number of threads using a queue,
7
- # collecting results in submission order.
8
- class Parallelizer
9
-
10
- # Returns the items to process.
11
- #
12
- # @return [Array] the items to process
13
- #
14
- # @example
15
- # parallelizer.items # => [1, 2, 3]
16
- #
17
- # @rbs @items: Array[untyped]
18
- attr_reader :items
19
-
20
- # Returns the number of threads in the pool.
21
- #
22
- # @return [Integer] the thread pool size
23
- #
24
- # @example
25
- # parallelizer.pool_size # => 4
26
- #
27
- # @rbs @pool_size: Integer
28
- attr_reader :pool_size
29
-
30
- # Creates a new Parallelizer instance.
31
- #
32
- # @param items [Array] the items to process concurrently
33
- # @param pool_size [Integer] number of threads (defaults to item count)
34
- #
35
- # @return [Parallelizer] a new parallelizer instance
36
- #
37
- # @example
38
- # Parallelizer.new([1, 2, 3], pool_size: 2)
39
- #
40
- # @rbs (Array[untyped] items, ?pool_size: Integer) -> void
41
- def initialize(items, pool_size: nil)
42
- @items = items
43
- @pool_size = Integer(pool_size || items.size)
44
- end
45
-
46
- # Processes items concurrently and returns results in submission order.
47
- #
48
- # @param items [Array] the items to process concurrently
49
- # @param pool_size [Integer] number of threads (defaults to item count)
50
- #
51
- # @yield [item] block called for each item in a worker thread
52
- # @yieldparam item [Object] an item from the items array
53
- # @yieldreturn [Object] the result for this item
54
- #
55
- # @return [Array] results in the same order as input items
56
- #
57
- # @example
58
- # Parallelizer.call([1, 2, 3], pool_size: 2) { |n| n * 10 }
59
- # # => [10, 20, 30]
60
- #
61
- # @rbs [T, R] (Array[T] items, ?pool_size: Integer) { (T) -> R } -> Array[R]
62
- def self.call(items, pool_size: nil, &block)
63
- new(items, pool_size:).call(&block)
64
- end
65
-
66
- # Distributes items across the thread pool and returns results
67
- # in submission order.
68
- #
69
- # @yield [item] block called for each item in a worker thread
70
- # @yieldparam item [Object] an item from the items array
71
- # @yieldreturn [Object] the result for this item
72
- #
73
- # @return [Array] results in the same order as input items
74
- #
75
- # @example
76
- # Parallelizer.new(%w[a b c]).call { |s| s.upcase }
77
- # # => ["A", "B", "C"]
78
- #
79
- # @rbs [T, R] () { (T) -> R } -> Array[R]
80
- def call(&block)
81
- results = Array.new(items.size)
82
- queue = Queue.new
83
-
84
- items.each_with_index { |item, i| queue << [item, i] }
85
- pool_size.times { queue << nil }
86
-
87
- Array.new(pool_size) do
88
- Thread.new do
89
- while (entry = queue.pop)
90
- item, index = entry
91
- results[index] = block.call(item) # rubocop:disable Performance/RedundantBlockCall
92
- end
93
- end
94
- end.each(&:join)
95
-
96
- results
97
- end
98
-
99
- end
100
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CMDx
4
- module Utils
5
- # Utility module for invoking callable objects with different invocation strategies.
6
- #
7
- # This module provides a unified interface for calling methods, procs, and other
8
- # callable objects on target objects, handling the appropriate invocation method
9
- # based on the callable type.
10
- module Call
11
-
12
- extend self
13
-
14
- # Invokes a callable object on the target with the given arguments.
15
- #
16
- # @param target [Object] The target object to invoke the callable on
17
- # @param callable [Symbol, Proc, #call] The callable to invoke
18
- # @param args [Array] Positional arguments to pass to the callable
19
- # @param kwargs [Hash] Keyword arguments to pass to the callable
20
- # @option kwargs [Object] :* Any keyword arguments to pass to the callable
21
- #
22
- # @yield [Object] Block to pass to the callable
23
- #
24
- # @return [Object] The result of invoking the callable
25
- #
26
- # @raise [RuntimeError] When the callable cannot be invoked
27
- #
28
- # @example Invoking a method by symbol
29
- # Call.invoke(user, :name)
30
- # Call.invoke(user, :update, { name: 'John' })
31
- # @example Invoking a proc
32
- # proc = ->(name) { "Hello #{name}" }
33
- # Call.invoke(user, proc, 'John')
34
- # @example Invoking a callable object
35
- # callable = MyCallable.new
36
- # Call.invoke(user, callable, 'data')
37
- #
38
- # @rbs (untyped target, (Symbol | Proc | untyped) callable, *untyped args, **untyped kwargs) ?{ () -> untyped } -> untyped
39
- def invoke(target, callable, *args, **kwargs, &)
40
- if callable.is_a?(Symbol)
41
- target.send(callable, *args, **kwargs, &)
42
- elsif callable.is_a?(Proc)
43
- target.instance_exec(*args, **kwargs, &callable)
44
- elsif callable.respond_to?(:call)
45
- callable.call(*args, **kwargs, &)
46
- else
47
- raise "cannot invoke #{callable}"
48
- end
49
- end
50
-
51
- end
52
- end
53
- end