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
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ # Orchestrates a task's full lifecycle: chain acquisition, middlewares,
5
+ # telemetry, deprecation, callbacks, input resolution, `work` (wrapped in
6
+ # retry), output verification, rollback on failure, result finalization,
7
+ # and teardown (freeze + chain clear).
8
+ #
9
+ # Signal propagation: Runtime wraps `work` in `catch(Signal::TAG)` so
10
+ # `success!` / `skip!` / `fail!` / `throw!` break out cleanly. Raised
11
+ # Faults are converted to echoed signals (carrying the upstream failed
12
+ # result as `:origin`); other `StandardError`s become failed signals with
13
+ # the exception as `:cause`. `execute!` (strict mode) re-raises on failure
14
+ # after the result is finalized, raising a {Fault} built from the deepest
15
+ # originating result so `fault.task` points at the leaf that failed.
16
+ #
17
+ # @note Always used via the class method; never new Runtime manually.
18
+ # @see Task.execute
19
+ # @see Task.execute!
20
+ class Runtime
21
+
22
+ class << self
23
+
24
+ # @param task [Task]
25
+ # @param strict [Boolean] when true, re-raise on failure (`execute!` semantics)
26
+ # @return [Result] the finalized, frozen result
27
+ # @raise [Fault, StandardError] only when `strict: true` and the task failed
28
+ def execute(task, strict: false)
29
+ new(task, strict:).execute
30
+ end
31
+
32
+ end
33
+
34
+ # @param task [Task]
35
+ # @param strict [Boolean]
36
+ def initialize(task, strict: false)
37
+ @task = task
38
+ @strict = strict
39
+ end
40
+
41
+ # Runs the full lifecycle. Teardown runs in `ensure`, guaranteeing the
42
+ # task's context/errors get frozen and the fiber chain is cleared even
43
+ # when strict mode re-raises.
44
+ #
45
+ # @return [Result]
46
+ # @raise [Fault, StandardError] under strict mode on failure
47
+ def execute
48
+ acquire_chain
49
+
50
+ run_middlewares do
51
+ emit_telemetry(:task_started)
52
+ run_deprecation
53
+ run_lifecycle
54
+ finalize_result
55
+ raise_signal! if @strict
56
+ end
57
+
58
+ @result
59
+ ensure
60
+ run_teardown
61
+ end
62
+
63
+ private
64
+
65
+ def acquire_chain
66
+ @root = Chain.current.nil?
67
+ return unless @root
68
+
69
+ xid = @task.class.settings.correlation_id&.call
70
+ Chain.current = Chain.new(xid)
71
+ end
72
+
73
+ def run_middlewares(&)
74
+ middlewares = @task.class.middlewares
75
+ return yield if middlewares.empty?
76
+
77
+ middlewares.process(@task, &)
78
+ end
79
+
80
+ def run_deprecation
81
+ deprecation = @task.class.deprecation
82
+ return unless deprecation
83
+
84
+ deprecation.execute(@task) do
85
+ @deprecated = true
86
+ emit_telemetry(:task_deprecated)
87
+ end
88
+ end
89
+
90
+ def run_lifecycle
91
+ measure_duration do
92
+ run_callbacks(:before_execution)
93
+ run_around(:around_execution) do
94
+ run_callbacks(:before_validation)
95
+ perform_work
96
+ perform_rollback if @signal.failed?
97
+ run_callbacks(:after_execution)
98
+ end
99
+ run_callbacks(:"on_#{@signal.state}")
100
+ run_callbacks(:"on_#{@signal.status}")
101
+ run_callbacks(:on_ok) if @signal.ok?
102
+ run_callbacks(:on_ko) if @signal.ko?
103
+ end
104
+ end
105
+
106
+ def raise_signal!
107
+ return unless @result.failed?
108
+
109
+ cause = @signal.cause
110
+ raise cause if cause && !cause.is_a?(Fault)
111
+
112
+ raise Fault, @result.caused_failure
113
+ end
114
+
115
+ def finalize_result
116
+ @result = Result.new(
117
+ Chain.current,
118
+ @task,
119
+ @signal,
120
+ root: @root,
121
+ tid: @task.tid,
122
+ strict: @strict,
123
+ deprecated: @deprecated,
124
+ rolled_back: @rolled_back,
125
+ retries: @retries,
126
+ duration: @duration
127
+ ).tap do |result|
128
+ @root ? Chain.current.unshift(result) : Chain.current.push(result)
129
+ emit_telemetry(:task_executed, result:)
130
+ @task.logger.info do
131
+ exclusions = @task.class.settings.log_exclusions
132
+ exclusions.empty? ? result : result.to_h.except(*exclusions)
133
+ end
134
+ end
135
+ end
136
+
137
+ def run_teardown
138
+ @task.context.freeze if @root
139
+ @task.errors.freeze
140
+ @task.freeze
141
+ return unless @root
142
+
143
+ Chain.current.freeze
144
+ Chain.clear
145
+ end
146
+
147
+ def measure_duration
148
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
149
+ yield
150
+ ensure
151
+ @duration = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start
152
+ end
153
+
154
+ # @param event [Symbol] callback event from {Callbacks::EVENTS}
155
+ # @return [void]
156
+ def run_callbacks(event)
157
+ callbacks = @task.class.callbacks
158
+ return if callbacks.empty?
159
+
160
+ callbacks.process(event, @task)
161
+ end
162
+
163
+ # @param event [Symbol] callback event from {Callbacks::EVENTS}
164
+ # @yield nested lifecycle segment wrapped by `around_execution` callbacks
165
+ # @return [Object] the wrapped block's return value
166
+ def run_around(event, &)
167
+ callbacks = @task.class.callbacks
168
+ return yield if callbacks.empty?
169
+
170
+ callbacks.around(event, @task, &)
171
+ end
172
+
173
+ def perform_work
174
+ @signal = catch(Signal::TAG) do
175
+ resolve_inputs!
176
+ retry_execution { @task.work }
177
+ verify_outputs!
178
+ Signal.success(nil, metadata: @task.metadata)
179
+ rescue Fault => e
180
+ Signal.echoed(e.result, cause: e, metadata: @task.metadata)
181
+ rescue Error => e
182
+ raise(e)
183
+ rescue StandardError => e
184
+ Signal.failed("[#{e.class}] #{e.message}", cause: e, metadata: @task.metadata)
185
+ end
186
+ end
187
+
188
+ def perform_rollback
189
+ return unless @task.respond_to?(:rollback)
190
+
191
+ @rolled_back = true
192
+ emit_telemetry(:task_rolled_back)
193
+ @task.rollback
194
+ end
195
+
196
+ def resolve_inputs!
197
+ inputs = @task.class.inputs
198
+ return if inputs.empty?
199
+
200
+ inputs.resolve(@task)
201
+ signal_errors!
202
+ end
203
+
204
+ def retry_execution
205
+ @task.class.retry_on.process(@task) do |attempt|
206
+ @retries = attempt
207
+ emit_telemetry(:task_retried, attempt:) if attempt.positive?
208
+ yield
209
+ end
210
+
211
+ signal_errors!
212
+ end
213
+
214
+ def verify_outputs!
215
+ outputs = @task.class.outputs
216
+ return if outputs.empty?
217
+
218
+ outputs.verify(@task)
219
+ signal_errors!
220
+ end
221
+
222
+ def signal_errors!
223
+ return if @task.errors.empty?
224
+
225
+ throw(Signal::TAG, Signal.failed(@task.errors.to_s, metadata: @task.metadata))
226
+ end
227
+
228
+ # @param name [Symbol] telemetry channel from {Telemetry::EVENTS}
229
+ # @param payload [Hash{Symbol => Object}] forwarded onto {Telemetry::Event#payload}
230
+ # @return [void]
231
+ def emit_telemetry(name, payload = EMPTY_HASH)
232
+ telemetry = @task.class.telemetry
233
+ return unless telemetry.subscribed?(name)
234
+
235
+ event = Telemetry::Event.new(
236
+ xid: Chain.current.xid,
237
+ cid: Chain.current.id,
238
+ root: @root,
239
+ type: @task.class.type,
240
+ task: @task.class,
241
+ tid: @task.tid,
242
+ name:,
243
+ payload:,
244
+ timestamp: Time.now.utc
245
+ )
246
+
247
+ telemetry.emit(name, event)
248
+ end
249
+
250
+ end
251
+ end
data/lib/cmdx/settings.rb CHANGED
@@ -1,221 +1,93 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Value object encapsulating all per-task configuration. Registries are
5
- # deep-duped on inheritance; scalar settings delegate to a parent Settings
6
- # or to the global Configuration rather than eagerly copying values.
4
+ # Per-task configuration overrides. Options are frozen on construction;
5
+ # {#build} returns a new instance rather than mutating. Every getter falls
6
+ # back to {CMDx.configuration} when the option wasn't set on the task.
7
7
  class Settings
8
8
 
9
- class << self
10
-
11
- private
12
-
13
- # Defines a reader that delegates to the parent Settings chain,
14
- # falling through to Configuration when no parent exists.
15
- #
16
- # @param names [Array<Symbol>] Setting names to define
17
- #
18
- # @rbs (*Symbol names) -> void
19
- def delegate_to_configuration(*names)
20
- names.each do |name|
21
- ivar = :"@#{name}"
22
-
23
- attr_writer(name)
9
+ # @param options [Hash{Symbol => Object}] task-specific overrides
10
+ # @option options [Logger] :logger
11
+ # @option options [#call] :log_formatter
12
+ # @option options [Integer] :log_level
13
+ # @option options [#call] :backtrace_cleaner
14
+ # @option options [Array<Symbol>] :log_exclusions
15
+ # @option options [Array<Symbol, String>] :tags
16
+ # @option options [Boolean] :strict_context
17
+ def initialize(options = EMPTY_HASH)
18
+ @options = options.freeze
19
+ end
24
20
 
25
- define_method(name) do
26
- return instance_variable_get(ivar) if instance_variable_defined?(ivar)
21
+ # Returns a new Settings with `new_options` merged on top. Returns `self`
22
+ # unchanged when `new_options` is empty (used by Task inheritance).
23
+ #
24
+ # @param new_options [Hash{Symbol => Object}] overrides to layer on top
25
+ # @return [Settings] merged instance (or `self` when no changes)
26
+ def build(new_options)
27
+ return self if new_options.empty?
27
28
 
28
- value = @parent ? @parent.public_send(name) : CMDx.configuration.public_send(name)
29
- instance_variable_set(ivar, value)
29
+ self.class.new(@options.merge(new_options))
30
+ end
30
31
 
31
- value
32
- end
33
- end
32
+ # @return [Logger] task-level logger or the global configuration's logger
33
+ def logger
34
+ @options.fetch(:logger) do
35
+ CMDx.configuration.logger
34
36
  end
37
+ end
35
38
 
36
- # Defines a reader that delegates to the parent Settings only.
37
- # Returns nil when the chain is exhausted.
38
- #
39
- # @param names [Array<Symbol>] Setting names to define
40
- # @param with_fallback [Boolean] Whether to fall back to Configuration
41
- #
42
- # @rbs (*Symbol names, with_fallback: bool) -> void
43
- def delegate_to_parent(*names, with_fallback: false)
44
- names.each do |name|
45
- ivar = :"@#{name}"
46
-
47
- attr_writer(name)
48
-
49
- define_method(name) do
50
- return instance_variable_get(ivar) if instance_variable_defined?(ivar)
51
-
52
- value = @parent&.public_send(name)
53
- value ||= CMDx.configuration.public_send(name) if with_fallback
54
- instance_variable_set(ivar, value)
55
-
56
- value
57
- end
58
- end
39
+ # @return [#call] Logger formatter used when logging task results
40
+ def log_formatter
41
+ @options.fetch(:log_formatter) do
42
+ CMDx.configuration.log_formatter
59
43
  end
60
-
61
44
  end
62
45
 
63
- # Returns the attribute registry for task parameters.
64
- #
65
- # @return [AttributeRegistry] The attribute registry
66
- #
67
- # @rbs @attributes: AttributeRegistry
68
- attr_accessor :attributes
69
-
70
- # Returns the callback registry for task lifecycle hooks.
71
- #
72
- # @return [CallbackRegistry] The callback registry
73
- #
74
- # @rbs @callbacks: CallbackRegistry
75
- attr_accessor :callbacks
76
-
77
- # Returns the coercion registry for type conversions.
78
- #
79
- # @return [CoercionRegistry] The coercion registry
80
- #
81
- # @rbs @coercions: CoercionRegistry
82
- attr_accessor :coercions
83
-
84
- # Returns the middleware registry for task execution.
85
- #
86
- # @return [MiddlewareRegistry] The middleware registry
87
- #
88
- # @rbs @middlewares: MiddlewareRegistry
89
- attr_accessor :middlewares
90
-
91
- # Returns the validator registry for attribute validation.
92
- #
93
- # @return [ValidatorRegistry] The validator registry
94
- #
95
- # @rbs @validators: ValidatorRegistry
96
- attr_accessor :validators
97
-
98
- # Returns the expected return keys after execution.
99
- #
100
- # @return [Array<Symbol>] Expected return keys after execution
101
- #
102
- # @rbs @returns: Array[Symbol]
103
- attr_accessor :returns
104
-
105
- # Returns the tags for task categorization.
106
- #
107
- # @return [Array<Symbol>] Tags for categorization
108
- #
109
- # @rbs @tags: Array[Symbol]
110
- attr_accessor :tags
111
-
112
- # @!attribute [rw] backtrace
113
- # @return [Boolean] true if backtraces should be logged
114
- delegate_to_configuration :backtrace
115
-
116
- # @!attribute [rw] rollback_on
117
- # @return [Array<String>] Statuses that trigger rollback
118
- delegate_to_configuration :rollback_on
119
-
120
- # @!attribute [rw] task_breakpoints
121
- # @return [Array<String>] Default task breakpoint statuses
122
- delegate_to_configuration :task_breakpoints
123
-
124
- # @!attribute [rw] workflow_breakpoints
125
- # @return [Array<String>] Default workflow breakpoint statuses
126
- delegate_to_configuration :workflow_breakpoints
127
-
128
- # @!attribute [rw] backtrace_cleaner
129
- # @return [Proc, nil] The backtrace cleaner proc
130
- delegate_to_parent :backtrace_cleaner, with_fallback: true
131
-
132
- # @!attribute [rw] breakpoints
133
- # @return [Array<String>, nil] Per-task breakpoints override
134
- delegate_to_parent :breakpoints
135
-
136
- # @!attribute [rw] deprecate
137
- # @return [Symbol, Proc, Boolean, nil] Deprecation behavior
138
- delegate_to_parent :deprecate
139
-
140
- # @!attribute [rw] exception_handler
141
- # @return [Proc, nil] The exception handler proc
142
- delegate_to_parent :exception_handler, with_fallback: true
143
-
144
- # @!attribute [rw] logger
145
- # @return [Logger] The logger instance
146
- delegate_to_parent :logger, with_fallback: true
147
-
148
- # @!attribute [rw] log_formatter
149
- # @return [Proc, nil] Per-task log formatter override
150
- delegate_to_parent :log_formatter
151
-
152
- # @!attribute [rw] log_level
153
- # @return [Integer, nil] Per-task log level override
154
- delegate_to_parent :log_level
155
-
156
- # @!attribute [rw] retries
157
- # @return [Integer, nil] Number of retries on failure
158
- delegate_to_parent :retries
159
-
160
- # @!attribute [rw] retry_jitter
161
- # @return [Numeric, Symbol, Proc, nil] Jitter between retries
162
- delegate_to_parent :retry_jitter
163
-
164
- # @!attribute [rw] retry_on
165
- # @return [Array<Class>, Class, nil] Exception classes to retry on
166
- delegate_to_parent :retry_on
167
-
168
- # Creates a new Settings instance, inheriting registries from a parent
169
- # Settings or the global Configuration. Scalar settings are resolved
170
- # lazily via delegation rather than eagerly copied.
171
- #
172
- # @param parent [Settings, nil] Parent settings to inherit from
173
- # @param overrides [Hash] Field values to override after inheritance
174
- #
175
- # @example
176
- # Settings.new(parent: ParentTask.settings, deprecate: true)
177
- #
178
- # @rbs (?parent: Settings?, **untyped overrides) -> void
179
- def initialize(parent: nil, **overrides)
180
- @parent = parent
181
-
182
- init_registries
183
- init_collections
46
+ # @return [Integer] `Logger` severity level
47
+ def log_level
48
+ @options.fetch(:log_level) do
49
+ CMDx.configuration.log_level
50
+ end
51
+ end
184
52
 
185
- overrides.each { |key, value| public_send(:"#{key}=", value) }
53
+ # @return [Array<Symbol>] keys to exclude from logging
54
+ def log_exclusions
55
+ @options.fetch(:log_exclusions) do
56
+ CMDx.configuration.log_exclusions
57
+ end
186
58
  end
187
59
 
188
- private
60
+ # @return [#call, nil] callable that cleans fault backtrace frames
61
+ def backtrace_cleaner
62
+ @options.fetch(:backtrace_cleaner) do
63
+ CMDx.configuration.backtrace_cleaner
64
+ end
65
+ end
189
66
 
190
- # Dups registries from the parent Settings or global Configuration
191
- # so each task class gets its own mutable copy.
67
+ # Returns a fresh array each call so callers can mutate the result
68
+ # without affecting other tasks (or hitting `FrozenError` on the
69
+ # shared sentinel).
192
70
  #
193
- # @rbs () -> void
194
- def init_registries
195
- if @parent
196
- @middlewares = @parent.middlewares.dup
197
- @callbacks = @parent.callbacks.dup
198
- @coercions = @parent.coercions.dup
199
- @validators = @parent.validators.dup
200
- @attributes = @parent.attributes.dup
201
- else
202
- config = CMDx.configuration
71
+ # @return [Array<Symbol, String>] task tags, surfaced on result hashes
72
+ def tags
73
+ tags = @options[:tags]
74
+ tags ? tags.dup : []
75
+ end
203
76
 
204
- @middlewares = config.middlewares.dup
205
- @callbacks = config.callbacks.dup
206
- @coercions = config.coercions.dup
207
- @validators = config.validators.dup
208
- @attributes = AttributeRegistry.new
77
+ # @return [Boolean] whether this task's {Context} should raise on
78
+ # unknown dynamic reads; falls back to
79
+ # {Configuration#strict_context}
80
+ def strict_context
81
+ @options.fetch(:strict_context) do
82
+ CMDx.configuration.strict_context
209
83
  end
210
84
  end
211
85
 
212
- # Initializes array-valued settings that need their own copy
213
- # to avoid cross-class mutation.
214
- #
215
- # @rbs () -> void
216
- def init_collections
217
- @returns = @parent&.returns&.dup || EMPTY_ARRAY
218
- @tags = @parent&.tags&.dup || EMPTY_ARRAY
86
+ # @return [String, nil] correlation id or the global configuration's correlation id
87
+ def correlation_id
88
+ @options.fetch(:correlation_id) do
89
+ CMDx.configuration.correlation_id
90
+ end
219
91
  end
220
92
 
221
93
  end