cmdx 1.21.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 +118 -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 -237
  23. data/lib/cmdx/context.rb +264 -243
  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 +247 -524
  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 -200
  64. data/lib/cmdx/signal.rb +165 -0
  65. data/lib/cmdx/task.rb +443 -343
  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 +71 -96
  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 +120 -48
  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 +8 -7
  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 -378
  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 -77
  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 -55
  112. data/lib/locales/ar.yml +0 -55
  113. data/lib/locales/az.yml +0 -55
  114. data/lib/locales/be.yml +0 -55
  115. data/lib/locales/bg.yml +0 -55
  116. data/lib/locales/bn.yml +0 -55
  117. data/lib/locales/bs.yml +0 -55
  118. data/lib/locales/ca.yml +0 -55
  119. data/lib/locales/cnr.yml +0 -55
  120. data/lib/locales/cs.yml +0 -55
  121. data/lib/locales/cy.yml +0 -55
  122. data/lib/locales/da.yml +0 -55
  123. data/lib/locales/de.yml +0 -55
  124. data/lib/locales/dz.yml +0 -55
  125. data/lib/locales/el.yml +0 -55
  126. data/lib/locales/eo.yml +0 -55
  127. data/lib/locales/es.yml +0 -55
  128. data/lib/locales/et.yml +0 -55
  129. data/lib/locales/eu.yml +0 -55
  130. data/lib/locales/fa.yml +0 -55
  131. data/lib/locales/fi.yml +0 -55
  132. data/lib/locales/fr.yml +0 -55
  133. data/lib/locales/fy.yml +0 -55
  134. data/lib/locales/gd.yml +0 -55
  135. data/lib/locales/gl.yml +0 -55
  136. data/lib/locales/he.yml +0 -55
  137. data/lib/locales/hi.yml +0 -55
  138. data/lib/locales/hr.yml +0 -55
  139. data/lib/locales/hu.yml +0 -55
  140. data/lib/locales/hy.yml +0 -55
  141. data/lib/locales/id.yml +0 -55
  142. data/lib/locales/is.yml +0 -55
  143. data/lib/locales/it.yml +0 -55
  144. data/lib/locales/ja.yml +0 -55
  145. data/lib/locales/ka.yml +0 -55
  146. data/lib/locales/kk.yml +0 -55
  147. data/lib/locales/km.yml +0 -55
  148. data/lib/locales/kn.yml +0 -55
  149. data/lib/locales/ko.yml +0 -55
  150. data/lib/locales/lb.yml +0 -55
  151. data/lib/locales/lo.yml +0 -55
  152. data/lib/locales/lt.yml +0 -55
  153. data/lib/locales/lv.yml +0 -55
  154. data/lib/locales/mg.yml +0 -55
  155. data/lib/locales/mk.yml +0 -55
  156. data/lib/locales/ml.yml +0 -55
  157. data/lib/locales/mn.yml +0 -55
  158. data/lib/locales/mr-IN.yml +0 -55
  159. data/lib/locales/ms.yml +0 -55
  160. data/lib/locales/nb.yml +0 -55
  161. data/lib/locales/ne.yml +0 -55
  162. data/lib/locales/nl.yml +0 -55
  163. data/lib/locales/nn.yml +0 -55
  164. data/lib/locales/oc.yml +0 -55
  165. data/lib/locales/or.yml +0 -55
  166. data/lib/locales/pa.yml +0 -55
  167. data/lib/locales/pl.yml +0 -55
  168. data/lib/locales/pt.yml +0 -55
  169. data/lib/locales/rm.yml +0 -55
  170. data/lib/locales/ro.yml +0 -55
  171. data/lib/locales/ru.yml +0 -55
  172. data/lib/locales/sc.yml +0 -55
  173. data/lib/locales/sk.yml +0 -55
  174. data/lib/locales/sl.yml +0 -55
  175. data/lib/locales/sq.yml +0 -55
  176. data/lib/locales/sr.yml +0 -55
  177. data/lib/locales/st.yml +0 -55
  178. data/lib/locales/sv.yml +0 -55
  179. data/lib/locales/sw.yml +0 -55
  180. data/lib/locales/ta.yml +0 -55
  181. data/lib/locales/te.yml +0 -55
  182. data/lib/locales/th.yml +0 -55
  183. data/lib/locales/tl.yml +0 -55
  184. data/lib/locales/tr.yml +0 -55
  185. data/lib/locales/tt.yml +0 -55
  186. data/lib/locales/ug.yml +0 -55
  187. data/lib/locales/uk.yml +0 -55
  188. data/lib/locales/ur.yml +0 -55
  189. data/lib/locales/uz.yml +0 -55
  190. data/lib/locales/vi.yml +0 -55
  191. data/lib/locales/wo.yml +0 -55
  192. data/lib/locales/zh-CN.yml +0 -55
  193. data/lib/locales/zh-HK.yml +0 -55
  194. data/lib/locales/zh-TW.yml +0 -55
  195. data/lib/locales/zh-YUE.yml +0 -55
@@ -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,225 +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] dump_context
117
- # @return [Boolean] true if context should be included in Task#to_h
118
- delegate_to_configuration :dump_context
119
-
120
- # @!attribute [rw] rollback_on
121
- # @return [Array<String>] Statuses that trigger rollback
122
- delegate_to_configuration :rollback_on
123
-
124
- # @!attribute [rw] task_breakpoints
125
- # @return [Array<String>] Default task breakpoint statuses
126
- delegate_to_configuration :task_breakpoints
127
-
128
- # @!attribute [rw] workflow_breakpoints
129
- # @return [Array<String>] Default workflow breakpoint statuses
130
- delegate_to_configuration :workflow_breakpoints
131
-
132
- # @!attribute [rw] backtrace_cleaner
133
- # @return [Proc, nil] The backtrace cleaner proc
134
- delegate_to_parent :backtrace_cleaner, with_fallback: true
135
-
136
- # @!attribute [rw] breakpoints
137
- # @return [Array<String>, nil] Per-task breakpoints override
138
- delegate_to_parent :breakpoints
139
-
140
- # @!attribute [rw] deprecate
141
- # @return [Symbol, Proc, Boolean, nil] Deprecation behavior
142
- delegate_to_parent :deprecate
143
-
144
- # @!attribute [rw] exception_handler
145
- # @return [Proc, nil] The exception handler proc
146
- delegate_to_parent :exception_handler, with_fallback: true
147
-
148
- # @!attribute [rw] logger
149
- # @return [Logger] The logger instance
150
- delegate_to_parent :logger, with_fallback: true
151
-
152
- # @!attribute [rw] log_formatter
153
- # @return [Proc, nil] Per-task log formatter override
154
- delegate_to_parent :log_formatter
155
-
156
- # @!attribute [rw] log_level
157
- # @return [Integer, nil] Per-task log level override
158
- delegate_to_parent :log_level
159
-
160
- # @!attribute [rw] retries
161
- # @return [Integer, nil] Number of retries on failure
162
- delegate_to_parent :retries
163
-
164
- # @!attribute [rw] retry_jitter
165
- # @return [Numeric, Symbol, Proc, nil] Jitter between retries
166
- delegate_to_parent :retry_jitter
167
-
168
- # @!attribute [rw] retry_on
169
- # @return [Array<Class>, Class, nil] Exception classes to retry on
170
- delegate_to_parent :retry_on
171
-
172
- # Creates a new Settings instance, inheriting registries from a parent
173
- # Settings or the global Configuration. Scalar settings are resolved
174
- # lazily via delegation rather than eagerly copied.
175
- #
176
- # @param parent [Settings, nil] Parent settings to inherit from
177
- # @param overrides [Hash] Field values to override after inheritance
178
- #
179
- # @example
180
- # Settings.new(parent: ParentTask.settings, deprecate: true)
181
- #
182
- # @rbs (?parent: Settings?, **untyped overrides) -> void
183
- def initialize(parent: nil, **overrides)
184
- @parent = parent
185
-
186
- init_registries
187
- 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
188
52
 
189
- 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
190
58
  end
191
59
 
192
- 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
193
66
 
194
- # Dups registries from the parent Settings or global Configuration
195
- # 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).
196
70
  #
197
- # @rbs () -> void
198
- def init_registries
199
- if @parent
200
- @middlewares = @parent.middlewares.dup
201
- @callbacks = @parent.callbacks.dup
202
- @coercions = @parent.coercions.dup
203
- @validators = @parent.validators.dup
204
- @attributes = @parent.attributes.dup
205
- else
206
- 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
207
76
 
208
- @middlewares = config.middlewares.dup
209
- @callbacks = config.callbacks.dup
210
- @coercions = config.coercions.dup
211
- @validators = config.validators.dup
212
- @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
213
83
  end
214
84
  end
215
85
 
216
- # Initializes array-valued settings that need their own copy
217
- # to avoid cross-class mutation.
218
- #
219
- # @rbs () -> void
220
- def init_collections
221
- @returns = @parent&.returns&.dup || EMPTY_ARRAY
222
- @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
223
91
  end
224
92
 
225
93
  end