cmdx 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/llms.md +3 -13
  3. data/.cursor/prompts/yardoc.md +1 -0
  4. data/CHANGELOG.md +16 -0
  5. data/LLM.md +436 -374
  6. data/README.md +7 -2
  7. data/docs/basics/setup.md +17 -0
  8. data/docs/callbacks.md +1 -1
  9. data/docs/getting_started.md +22 -2
  10. data/docs/index.md +13 -1
  11. data/docs/retries.md +121 -0
  12. data/docs/tips_and_tricks.md +2 -1
  13. data/examples/stoplight_circuit_breaker.md +36 -0
  14. data/lib/cmdx/attribute.rb +82 -1
  15. data/lib/cmdx/attribute_registry.rb +20 -0
  16. data/lib/cmdx/attribute_value.rb +25 -0
  17. data/lib/cmdx/callback_registry.rb +19 -0
  18. data/lib/cmdx/chain.rb +34 -1
  19. data/lib/cmdx/coercion_registry.rb +18 -0
  20. data/lib/cmdx/coercions/array.rb +2 -0
  21. data/lib/cmdx/coercions/big_decimal.rb +3 -0
  22. data/lib/cmdx/coercions/boolean.rb +5 -0
  23. data/lib/cmdx/coercions/complex.rb +2 -0
  24. data/lib/cmdx/coercions/date.rb +4 -0
  25. data/lib/cmdx/coercions/date_time.rb +5 -0
  26. data/lib/cmdx/coercions/float.rb +2 -0
  27. data/lib/cmdx/coercions/hash.rb +2 -0
  28. data/lib/cmdx/coercions/integer.rb +2 -0
  29. data/lib/cmdx/coercions/rational.rb +2 -0
  30. data/lib/cmdx/coercions/string.rb +2 -0
  31. data/lib/cmdx/coercions/symbol.rb +2 -0
  32. data/lib/cmdx/coercions/time.rb +5 -0
  33. data/lib/cmdx/configuration.rb +126 -3
  34. data/lib/cmdx/context.rb +36 -0
  35. data/lib/cmdx/deprecator.rb +3 -0
  36. data/lib/cmdx/errors.rb +22 -0
  37. data/lib/cmdx/executor.rb +71 -11
  38. data/lib/cmdx/faults.rb +14 -0
  39. data/lib/cmdx/identifier.rb +2 -0
  40. data/lib/cmdx/locale.rb +3 -0
  41. data/lib/cmdx/log_formatters/json.rb +2 -0
  42. data/lib/cmdx/log_formatters/key_value.rb +2 -0
  43. data/lib/cmdx/log_formatters/line.rb +2 -0
  44. data/lib/cmdx/log_formatters/logstash.rb +2 -0
  45. data/lib/cmdx/log_formatters/raw.rb +2 -0
  46. data/lib/cmdx/middleware_registry.rb +20 -0
  47. data/lib/cmdx/middlewares/correlate.rb +11 -0
  48. data/lib/cmdx/middlewares/runtime.rb +4 -0
  49. data/lib/cmdx/middlewares/timeout.rb +4 -0
  50. data/lib/cmdx/pipeline.rb +20 -1
  51. data/lib/cmdx/railtie.rb +4 -0
  52. data/lib/cmdx/result.rb +123 -1
  53. data/lib/cmdx/task.rb +91 -1
  54. data/lib/cmdx/utils/call.rb +2 -0
  55. data/lib/cmdx/utils/condition.rb +3 -0
  56. data/lib/cmdx/utils/format.rb +5 -0
  57. data/lib/cmdx/validator_registry.rb +18 -0
  58. data/lib/cmdx/validators/exclusion.rb +2 -0
  59. data/lib/cmdx/validators/format.rb +2 -0
  60. data/lib/cmdx/validators/inclusion.rb +2 -0
  61. data/lib/cmdx/validators/length.rb +14 -0
  62. data/lib/cmdx/validators/numeric.rb +14 -0
  63. data/lib/cmdx/validators/presence.rb +2 -0
  64. data/lib/cmdx/version.rb +4 -1
  65. data/lib/cmdx/workflow.rb +10 -0
  66. data/lib/cmdx.rb +8 -0
  67. data/lib/generators/cmdx/locale_generator.rb +0 -1
  68. data/mkdocs.yml +3 -1
  69. metadata +3 -1
data/lib/cmdx/context.rb CHANGED
@@ -11,6 +11,14 @@ module CMDx
11
11
 
12
12
  extend Forwardable
13
13
 
14
+ # Returns the internal hash storing context data.
15
+ #
16
+ # @return [Hash{Symbol => Object}] The internal hash table
17
+ #
18
+ # @example
19
+ # context.table # => { name: "John", age: 30 }
20
+ #
21
+ # @rbs @table: Hash[Symbol, untyped]
14
22
  attr_reader :table
15
23
  alias to_h table
16
24
 
@@ -28,6 +36,8 @@ module CMDx
28
36
  # @example
29
37
  # context = Context.new(name: "John", age: 30)
30
38
  # context[:name] # => "John"
39
+ #
40
+ # @rbs (untyped args) -> void
31
41
  def initialize(args = {})
32
42
  @table =
33
43
  if args.respond_to?(:to_hash)
@@ -50,6 +60,8 @@ module CMDx
50
60
  # existing = Context.new(name: "John")
51
61
  # built = Context.build(existing) # reuses existing context
52
62
  # built.object_id == existing.object_id # => true
63
+ #
64
+ # @rbs (untyped context) -> Context
53
65
  def self.build(context = {})
54
66
  if context.is_a?(self) && !context.frozen?
55
67
  context
@@ -70,6 +82,8 @@ module CMDx
70
82
  # context = Context.new(name: "John")
71
83
  # context[:name] # => "John"
72
84
  # context["name"] # => "John" (automatically converted to symbol)
85
+ #
86
+ # @rbs ((String | Symbol) key) -> untyped
73
87
  def [](key)
74
88
  table[key.to_sym]
75
89
  end
@@ -85,6 +99,8 @@ module CMDx
85
99
  # context = Context.new
86
100
  # context.store(:name, "John")
87
101
  # context[:name] # => "John"
102
+ #
103
+ # @rbs ((String | Symbol) key, untyped value) -> untyped
88
104
  def store(key, value)
89
105
  table[key.to_sym] = value
90
106
  end
@@ -104,6 +120,8 @@ module CMDx
104
120
  # context.fetch(:name) # => "John"
105
121
  # context.fetch(:age, 25) # => 25
106
122
  # context.fetch(:city) { |key| "Unknown #{key}" } # => "Unknown city"
123
+ #
124
+ # @rbs ((String | Symbol) key, *untyped) ?{ ((String | Symbol)) -> untyped } -> untyped
107
125
  def fetch(key, ...)
108
126
  table.fetch(key.to_sym, ...)
109
127
  end
@@ -122,6 +140,8 @@ module CMDx
122
140
  # context.fetch_or_store(:name, "Default") # => "John" (existing value)
123
141
  # context.fetch_or_store(:age, 25) # => 25 (stored and returned)
124
142
  # context.fetch_or_store(:city) { |key| "Unknown #{key}" } # => "Unknown city" (stored and returned)
143
+ #
144
+ # @rbs ((String | Symbol) key, ?untyped value) ?{ () -> untyped } -> untyped
125
145
  def fetch_or_store(key, value = nil)
126
146
  table.fetch(key.to_sym) do
127
147
  table[key.to_sym] = block_given? ? yield : value
@@ -139,6 +159,8 @@ module CMDx
139
159
  # context = Context.new(name: "John")
140
160
  # context.merge!(age: 30, city: "NYC")
141
161
  # context.to_h # => {name: "John", age: 30, city: "NYC"}
162
+ #
163
+ # @rbs (?untyped args) -> self
142
164
  def merge!(args = {})
143
165
  args.to_h.each { |key, value| self[key.to_sym] = value }
144
166
  self
@@ -156,6 +178,8 @@ module CMDx
156
178
  # context = Context.new(name: "John", age: 30)
157
179
  # context.delete!(:age) # => 30
158
180
  # context.delete!(:city) { |key| "Key #{key} not found" } # => "Key city not found"
181
+ #
182
+ # @rbs ((String | Symbol) key) ?{ ((String | Symbol)) -> untyped } -> untyped
159
183
  def delete!(key, &)
160
184
  table.delete(key.to_sym, &)
161
185
  end
@@ -170,6 +194,8 @@ module CMDx
170
194
  # context1 = Context.new(name: "John")
171
195
  # context2 = Context.new(name: "John")
172
196
  # context1 == context2 # => true
197
+ #
198
+ # @rbs (untyped other) -> bool
173
199
  def eql?(other)
174
200
  other.is_a?(self.class) && (to_h == other.to_h)
175
201
  end
@@ -185,6 +211,8 @@ module CMDx
185
211
  # context = Context.new(name: "John")
186
212
  # context.key?(:name) # => true
187
213
  # context.key?(:age) # => false
214
+ #
215
+ # @rbs ((String | Symbol) key) -> bool
188
216
  def key?(key)
189
217
  table.key?(key.to_sym)
190
218
  end
@@ -200,6 +228,8 @@ module CMDx
200
228
  # context = Context.new(user: {profile: {name: "John"}})
201
229
  # context.dig(:user, :profile, :name) # => "John"
202
230
  # context.dig(:user, :profile, :age) # => nil
231
+ #
232
+ # @rbs ((String | Symbol) key, *(String | Symbol) keys) -> untyped
203
233
  def dig(key, *keys)
204
234
  table.dig(key.to_sym, *keys)
205
235
  end
@@ -211,6 +241,8 @@ module CMDx
211
241
  # @example
212
242
  # context = Context.new(name: "John", age: 30)
213
243
  # context.to_s # => "name: John, age: 30"
244
+ #
245
+ # @rbs () -> String
214
246
  def to_s
215
247
  Utils::Format.to_str(to_h)
216
248
  end
@@ -227,6 +259,8 @@ module CMDx
227
259
  # @yield [Object] optional block
228
260
  #
229
261
  # @return [Object] the result of the method call
262
+ #
263
+ # @rbs (Symbol method_name, *untyped args, **untyped _kwargs) ?{ () -> untyped } -> untyped
230
264
  def method_missing(method_name, *args, **_kwargs, &)
231
265
  fetch(method_name) do
232
266
  store(method_name[0..-2], args.first) if method_name.end_with?("=")
@@ -244,6 +278,8 @@ module CMDx
244
278
  # context = Context.new(name: "John")
245
279
  # context.respond_to?(:name) # => true
246
280
  # context.respond_to?(:age) # => false
281
+ #
282
+ # @rbs (Symbol method_name, ?bool include_private) -> bool
247
283
  def respond_to_missing?(method_name, include_private = false)
248
284
  key?(method_name) || super
249
285
  end
@@ -10,6 +10,7 @@ module CMDx
10
10
 
11
11
  extend self
12
12
 
13
+ # @rbs EVAL: Proc
13
14
  EVAL = proc do |target, callable|
14
15
  case callable
15
16
  when /raise|log|warn/ then callable
@@ -45,6 +46,8 @@ module CMDx
45
46
  # end
46
47
  #
47
48
  # MyTask.new # => [MyTask] DEPRECATED: migrate to a replacement or discontinue use
49
+ #
50
+ # @rbs (Task task) -> void
48
51
  def restrict(task)
49
52
  type = EVAL.call(task, task.class.settings[:deprecate])
50
53
 
data/lib/cmdx/errors.rb CHANGED
@@ -8,11 +8,21 @@ module CMDx
8
8
 
9
9
  extend Forwardable
10
10
 
11
+ # Returns the internal hash of error messages by attribute.
12
+ #
13
+ # @return [Hash{Symbol => Set<String>}] Hash mapping attribute names to error message sets
14
+ #
15
+ # @example
16
+ # errors.messages # => { email: #<Set: ["must be valid", "is required"]> }
17
+ #
18
+ # @rbs @messages: Hash[Symbol, Set[String]]
11
19
  attr_reader :messages
12
20
 
13
21
  def_delegators :messages, :empty?
14
22
 
15
23
  # Initialize a new error collection.
24
+ #
25
+ # @rbs () -> void
16
26
  def initialize
17
27
  @messages = {}
18
28
  end
@@ -26,6 +36,8 @@ module CMDx
26
36
  # errors = CMDx::Errors.new
27
37
  # errors.add(:email, "must be valid format")
28
38
  # errors.add(:email, "cannot be blank")
39
+ #
40
+ # @rbs (Symbol attribute, String message) -> void
29
41
  def add(attribute, message)
30
42
  return if message.empty?
31
43
 
@@ -42,6 +54,8 @@ module CMDx
42
54
  # @example
43
55
  # errors.for?(:email) # => true
44
56
  # errors.for?(:name) # => false
57
+ #
58
+ # @rbs (Symbol attribute) -> bool
45
59
  def for?(attribute)
46
60
  return false unless messages.key?(attribute)
47
61
 
@@ -54,6 +68,8 @@ module CMDx
54
68
  #
55
69
  # @example
56
70
  # errors.full_messages # => { email: ["email must be valid format", "email cannot be blank"] }
71
+ #
72
+ # @rbs () -> Hash[Symbol, Array[String]]
57
73
  def full_messages
58
74
  messages.each_with_object({}) do |(attribute, messages), hash|
59
75
  hash[attribute] = messages.map { |message| "#{attribute} #{message}" }
@@ -66,6 +82,8 @@ module CMDx
66
82
  #
67
83
  # @example
68
84
  # errors.to_h # => { email: ["must be valid format", "cannot be blank"] }
85
+ #
86
+ # @rbs () -> Hash[Symbol, Array[String]]
69
87
  def to_h
70
88
  messages.transform_values(&:to_a)
71
89
  end
@@ -78,6 +96,8 @@ module CMDx
78
96
  # @example
79
97
  # errors.to_hash # => { email: ["must be valid format", "cannot be blank"] }
80
98
  # errors.to_hash(true) # => { email: ["email must be valid format", "email cannot be blank"] }
99
+ #
100
+ # @rbs (?bool full) -> Hash[Symbol, Array[String]]
81
101
  def to_hash(full = false)
82
102
  full ? full_messages : to_h
83
103
  end
@@ -88,6 +108,8 @@ module CMDx
88
108
  #
89
109
  # @example
90
110
  # errors.to_s # => "email must be valid format. email cannot be blank"
111
+ #
112
+ # @rbs () -> String
91
113
  def to_s
92
114
  full_messages.values.flatten.join(". ")
93
115
  end
data/lib/cmdx/executor.rb CHANGED
@@ -8,6 +8,14 @@ module CMDx
8
8
  # and proper error handling for different types of failures.
9
9
  class Executor
10
10
 
11
+ # Returns the task being executed.
12
+ #
13
+ # @return [Task] The task instance
14
+ #
15
+ # @example
16
+ # executor.task.id # => "abc123"
17
+ #
18
+ # @rbs @task: Task
11
19
  attr_reader :task
12
20
 
13
21
  # @param task [CMDx::Task] The task to execute
@@ -16,6 +24,8 @@ module CMDx
16
24
  #
17
25
  # @example
18
26
  # executor = CMDx::Executor.new(my_task)
27
+ #
28
+ # @rbs (Task task) -> void
19
29
  def initialize(task)
20
30
  @task = task
21
31
  end
@@ -32,6 +42,8 @@ module CMDx
32
42
  # @example
33
43
  # CMDx::Executor.execute(my_task)
34
44
  # CMDx::Executor.execute(my_task, raise: true)
45
+ #
46
+ # @rbs (Task task, raise: bool) -> Result
35
47
  def self.execute(task, raise: false)
36
48
  instance = new(task)
37
49
  raise ? instance.execute! : instance.execute
@@ -44,6 +56,8 @@ module CMDx
44
56
  # @example
45
57
  # executor = CMDx::Executor.new(my_task)
46
58
  # result = executor.execute
59
+ #
60
+ # @rbs () -> Result
47
61
  def execute
48
62
  task.class.settings[:middlewares].call!(task) do
49
63
  pre_execution! unless @pre_execution
@@ -73,6 +87,8 @@ module CMDx
73
87
  # @example
74
88
  # executor = CMDx::Executor.new(my_task)
75
89
  # result = executor.execute!
90
+ #
91
+ # @rbs () -> Result
76
92
  def execute!
77
93
  task.class.settings[:middlewares].call!(task) do
78
94
  pre_execution! unless @pre_execution
@@ -102,13 +118,12 @@ module CMDx
102
118
  #
103
119
  # @return [Boolean] Whether execution should halt
104
120
  #
105
- # @example
106
- # halt_execution?(fault_exception)
121
+ # @rbs (Exception exception) -> bool
107
122
  def halt_execution?(exception)
108
- breakpoints = task.class.settings[:breakpoints] || task.class.settings[:task_breakpoints]
109
- breakpoints = Array(breakpoints).map(&:to_s).uniq
123
+ statuses = task.class.settings[:breakpoints] || task.class.settings[:task_breakpoints]
124
+ statuses = Array(statuses).map(&:to_s).uniq
110
125
 
111
- breakpoints.include?(exception.result.status)
126
+ statuses.include?(exception.result.status)
112
127
  end
113
128
 
114
129
  # Determines if execution should be retried based on retry configuration.
@@ -117,8 +132,7 @@ module CMDx
117
132
  #
118
133
  # @return [Boolean] Whether execution should be retried
119
134
  #
120
- # @example
121
- # retry_execution?(standard_error)
135
+ # @rbs (Exception exception) -> bool
122
136
  def retry_execution?(exception)
123
137
  available_retries = (task.class.settings[:retries] || 0).to_i
124
138
  return false unless available_retries.positive?
@@ -137,7 +151,18 @@ module CMDx
137
151
  task.to_h.merge!(reason:, remaining_retries:)
138
152
  end
139
153
 
140
- jitter = task.class.settings[:retry_jitter].to_f * current_retries
154
+ jitter = task.class.settings[:retry_jitter]
155
+ jitter =
156
+ if jitter.is_a?(Symbol)
157
+ task.send(jitter, current_retries)
158
+ elsif jitter.is_a?(Proc)
159
+ task.instance_exec(current_retries, &jitter)
160
+ elsif jitter.respond_to?(:call)
161
+ jitter.call(task, current_retries)
162
+ else
163
+ jitter.to_f * current_retries
164
+ end
165
+
141
166
  sleep(jitter) if jitter.positive?
142
167
 
143
168
  true
@@ -149,8 +174,7 @@ module CMDx
149
174
  #
150
175
  # @raise [Exception] The provided exception
151
176
  #
152
- # @example
153
- # raise_exception(standard_error)
177
+ # @rbs (Exception exception) -> void
154
178
  def raise_exception(exception)
155
179
  Chain.clear
156
180
 
@@ -165,6 +189,8 @@ module CMDx
165
189
  #
166
190
  # @example
167
191
  # invoke_callbacks(:before_execution)
192
+ #
193
+ # @rbs (Symbol type) -> void
168
194
  def invoke_callbacks(type)
169
195
  task.class.settings[:callbacks].invoke(type, task)
170
196
  end
@@ -172,11 +198,15 @@ module CMDx
172
198
  private
173
199
 
174
200
  # Lazy loaded repeator instance to handle retries.
201
+ #
202
+ # @rbs () -> untyped
175
203
  def repeator
176
204
  @repeator ||= Repeator.new(task)
177
205
  end
178
206
 
179
207
  # Performs pre-execution tasks including validation and attribute verification.
208
+ #
209
+ # @rbs () -> void
180
210
  def pre_execution!
181
211
  @pre_execution = true
182
212
 
@@ -195,6 +225,8 @@ module CMDx
195
225
  end
196
226
 
197
227
  # Executes the main task logic.
228
+ #
229
+ # @rbs () -> void
198
230
  def execution!
199
231
  invoke_callbacks(:before_execution)
200
232
 
@@ -203,6 +235,8 @@ module CMDx
203
235
  end
204
236
 
205
237
  # Performs post-execution tasks including callback invocation.
238
+ #
239
+ # @rbs () -> void
206
240
  def post_execution!
207
241
  invoke_callbacks(:"on_#{task.result.state}")
208
242
  invoke_callbacks(:on_executed) if task.result.executed?
@@ -212,21 +246,29 @@ module CMDx
212
246
  invoke_callbacks(:on_bad) if task.result.bad?
213
247
  end
214
248
 
215
- # Finalizes execution by freezing the task and logging results.
249
+ # Finalizes execution by freezing the task, logging results, and rolling back work.
250
+ #
251
+ # @rbs () -> Result
216
252
  def finalize_execution!
217
253
  log_execution!
218
254
  log_backtrace! if task.class.settings[:backtrace]
219
255
 
220
256
  freeze_execution!
221
257
  clear_chain!
258
+
259
+ rollback_execution!
222
260
  end
223
261
 
224
262
  # Logs the execution result at the configured log level.
263
+ #
264
+ # @rbs () -> void
225
265
  def log_execution!
226
266
  task.logger.info { task.result.to_h }
227
267
  end
228
268
 
229
269
  # Logs the backtrace of the exception if the task failed.
270
+ #
271
+ # @rbs () -> void
230
272
  def log_backtrace!
231
273
  return unless task.result.failed?
232
274
 
@@ -244,6 +286,8 @@ module CMDx
244
286
  end
245
287
 
246
288
  # Freezes the task and its associated objects to prevent modifications.
289
+ #
290
+ # @rbs () -> void
247
291
  def freeze_execution!
248
292
  # Stubbing on frozen objects is not allowed in most test environments.
249
293
  skip_freezing = ENV.fetch("SKIP_CMDX_FREEZING", false)
@@ -260,11 +304,27 @@ module CMDx
260
304
  task.chain.freeze
261
305
  end
262
306
 
307
+ # Clears the chain if the task is the outermost (top-level) task.
308
+ #
309
+ # @rbs () -> void
263
310
  def clear_chain!
264
311
  return unless task.result.index.zero?
265
312
 
266
313
  Chain.clear
267
314
  end
268
315
 
316
+ # Rolls back the work of a task.
317
+ #
318
+ # @rbs () -> void
319
+ def rollback_execution!
320
+ return unless task.respond_to?(:rollback)
321
+
322
+ statuses = task.class.settings[:rollback_on]
323
+ statuses = Array(statuses).map(&:to_s).uniq
324
+ return unless statuses.include?(task.result.status)
325
+
326
+ task.rollback
327
+ end
328
+
269
329
  end
270
330
  end
data/lib/cmdx/faults.rb CHANGED
@@ -11,6 +11,14 @@ module CMDx
11
11
 
12
12
  extend Forwardable
13
13
 
14
+ # Returns the result that caused this fault.
15
+ #
16
+ # @return [Result] The result instance
17
+ #
18
+ # @example
19
+ # fault.result.reason # => "Validation failed"
20
+ #
21
+ # @rbs @result: Result
14
22
  attr_reader :result
15
23
 
16
24
  def_delegators :result, :task, :context, :chain
@@ -24,6 +32,8 @@ module CMDx
24
32
  # @example
25
33
  # fault = Fault.new(task_result)
26
34
  # fault.result.reason # => "Task validation failed"
35
+ #
36
+ # @rbs (Result result) -> void
27
37
  def initialize(result)
28
38
  @result = result
29
39
 
@@ -41,6 +51,8 @@ module CMDx
41
51
  # @example
42
52
  # Fault.for?(UserTask, AdminUserTask)
43
53
  # # => true if fault.task is a UserTask or AdminUserTask
54
+ #
55
+ # @rbs (*Class tasks) -> Class
44
56
  def for?(*tasks)
45
57
  temp_fault = Class.new(self) do
46
58
  def self.===(other)
@@ -62,6 +74,8 @@ module CMDx
62
74
  # @example
63
75
  # Fault.matches? { |fault| fault.result.metadata[:critical] }
64
76
  # # => true if fault has critical metadata
77
+ #
78
+ # @rbs () { (Fault) -> bool } -> Class
65
79
  def matches?(&block)
66
80
  raise ArgumentError, "block required" unless block_given?
67
81
 
@@ -18,6 +18,8 @@ module CMDx
18
18
  # @example Generate a unique identifier
19
19
  # CMDx::Identifier.generate
20
20
  # # => "01890b2c-1234-5678-9abc-def123456789"
21
+ #
22
+ # @rbs () -> String
21
23
  def generate
22
24
  if SecureRandom.respond_to?(:uuid_v7)
23
25
  SecureRandom.uuid_v7
data/lib/cmdx/locale.rb CHANGED
@@ -8,6 +8,7 @@ module CMDx
8
8
 
9
9
  extend self
10
10
 
11
+ # @rbs EN: Hash[String, untyped]
11
12
  EN = YAML.load_file(CMDx.gem_path.join("lib/locales/en.yml")).freeze
12
13
  private_constant :EN
13
14
 
@@ -34,6 +35,8 @@ module CMDx
34
35
  # @example With fallback
35
36
  # Locale.translate("missing.key", default: "Custom fallback message")
36
37
  # # => "Custom fallback message"
38
+ #
39
+ # @rbs ((String | Symbol) key, **untyped options) -> String
37
40
  def translate(key, **options)
38
41
  options[:default] ||= EN.dig("en", *key.to_s.split("."))
39
42
  return ::I18n.t(key, **options) if defined?(::I18n)
@@ -21,6 +21,8 @@ module CMDx
21
21
  # @example Basic usage
22
22
  # logger_formatter.call("INFO", Time.now, "MyApp", "User logged in")
23
23
  # # => '{"severity":"INFO","timestamp":"2024-01-15T10:30:45.123456Z","progname":"MyApp","pid":12345,"message":"User logged in"}\n'
24
+ #
25
+ # @rbs (String severity, Time time, String? progname, String message) -> String
24
26
  def call(severity, time, progname, message)
25
27
  hash = {
26
28
  severity:,
@@ -21,6 +21,8 @@ module CMDx
21
21
  # @example Basic usage
22
22
  # logger_formatter.call("INFO", Time.now, "MyApp", "User logged in")
23
23
  # # => "severity=INFO timestamp=2024-01-15T10:30:45.123456Z progname=MyApp pid=12345 message=User logged in\n"
24
+ #
25
+ # @rbs (String severity, Time time, String? progname, String message) -> String
24
26
  def call(severity, time, progname, message)
25
27
  hash = {
26
28
  severity:,
@@ -21,6 +21,8 @@ module CMDx
21
21
  # @example Basic usage
22
22
  # logger_formatter.call("INFO", Time.now, "MyApp", "User logged in")
23
23
  # # => "I, [2024-01-15T10:30:45.123456Z #12345] INFO -- MyApp: User logged in\n"
24
+ #
25
+ # @rbs (String severity, Time time, String? progname, String message) -> String
24
26
  def call(severity, time, progname, message)
25
27
  "#{severity[0]}, [#{time.utc.iso8601(6)} ##{Process.pid}] #{severity} -- #{progname}: #{message}\n"
26
28
  end
@@ -22,6 +22,8 @@ module CMDx
22
22
  # @example Basic usage
23
23
  # logger_formatter.call("INFO", Time.now, "MyApp", "User logged in")
24
24
  # # => '{"severity":"INFO","progname":"MyApp","pid":12345,"message":"User logged in","@version":"1","@timestamp":"2024-01-15T10:30:45.123456Z"}\n'
25
+ #
26
+ # @rbs (String severity, Time time, String? progname, String message) -> String
25
27
  def call(severity, time, progname, message)
26
28
  hash = {
27
29
  severity:,
@@ -22,6 +22,8 @@ module CMDx
22
22
  # @example Basic usage
23
23
  # logger_formatter.call("INFO", Time.now, "MyApp", "User logged in")
24
24
  # # => "User logged in\n"
25
+ #
26
+ # @rbs (String severity, Time time, String? progname, String message) -> String
25
27
  def call(severity, time, progname, message)
26
28
  "#{message}\n"
27
29
  end
@@ -9,6 +9,14 @@ module CMDx
9
9
  # they were registered.
10
10
  class MiddlewareRegistry
11
11
 
12
+ # Returns the ordered collection of middleware entries.
13
+ #
14
+ # @return [Array<Array>] Array of middleware-options pairs
15
+ #
16
+ # @example
17
+ # registry.registry # => [[LoggingMiddleware, {level: :debug}], [AuthMiddleware, {}]]
18
+ #
19
+ # @rbs @registry: Array[Array[untyped]]
12
20
  attr_reader :registry
13
21
  alias to_a registry
14
22
 
@@ -19,6 +27,8 @@ module CMDx
19
27
  # @example
20
28
  # registry = MiddlewareRegistry.new
21
29
  # registry = MiddlewareRegistry.new([[MyMiddleware, {option: 'value'}]])
30
+ #
31
+ # @rbs (?Array[Array[untyped]] registry) -> void
22
32
  def initialize(registry = [])
23
33
  @registry = registry
24
34
  end
@@ -29,6 +39,8 @@ module CMDx
29
39
  #
30
40
  # @example
31
41
  # new_registry = registry.dup
42
+ #
43
+ # @rbs () -> MiddlewareRegistry
32
44
  def dup
33
45
  self.class.new(registry.map(&:dup))
34
46
  end
@@ -46,6 +58,8 @@ module CMDx
46
58
  # @example
47
59
  # registry.register(LoggingMiddleware, at: 0, log_level: :debug)
48
60
  # registry.register(AuthMiddleware, at: -1, timeout: 30)
61
+ #
62
+ # @rbs (untyped middleware, ?at: Integer, **untyped options) -> self
49
63
  def register(middleware, at: -1, **options)
50
64
  registry.insert(at, [middleware, options])
51
65
  self
@@ -59,6 +73,8 @@ module CMDx
59
73
  #
60
74
  # @example
61
75
  # registry.deregister(LoggingMiddleware)
76
+ #
77
+ # @rbs (untyped middleware) -> self
62
78
  def deregister(middleware)
63
79
  registry.reject! { |mw, _opts| mw == middleware }
64
80
  self
@@ -79,6 +95,8 @@ module CMDx
79
95
  # result = registry.call!(my_task) do |processed_task|
80
96
  # processed_task.execute
81
97
  # end
98
+ #
99
+ # @rbs (untyped task) { (untyped) -> untyped } -> untyped
82
100
  def call!(task, &)
83
101
  raise ArgumentError, "block required" unless block_given?
84
102
 
@@ -96,6 +114,8 @@ module CMDx
96
114
  # @yieldparam task [Object] The processed task object
97
115
  #
98
116
  # @return [Object] Result of the block execution or next middleware call
117
+ #
118
+ # @rbs (Integer index, untyped task) { (untyped) -> untyped } -> untyped
99
119
  def recursively_call_middleware(index, task, &block)
100
120
  return yield(task) if index >= registry.size
101
121
 
@@ -12,6 +12,7 @@ module CMDx
12
12
 
13
13
  extend self
14
14
 
15
+ # @rbs THREAD_KEY: Symbol
15
16
  THREAD_KEY = :cmdx_correlate
16
17
 
17
18
  # Retrieves the current correlation ID from thread-local storage.
@@ -20,6 +21,8 @@ module CMDx
20
21
  #
21
22
  # @example Get current correlation ID
22
23
  # Correlate.id # => "550e8400-e29b-41d4-a716-446655440000"
24
+ #
25
+ # @rbs () -> String?
23
26
  def id
24
27
  Thread.current[THREAD_KEY]
25
28
  end
@@ -31,6 +34,8 @@ module CMDx
31
34
  #
32
35
  # @example Set correlation ID
33
36
  # Correlate.id = "abc-123-def"
37
+ #
38
+ # @rbs (String id) -> String
34
39
  def id=(id)
35
40
  Thread.current[THREAD_KEY] = id
36
41
  end
@@ -41,6 +46,8 @@ module CMDx
41
46
  #
42
47
  # @example Clear correlation ID
43
48
  # Correlate.clear
49
+ #
50
+ # @rbs () -> nil
44
51
  def clear
45
52
  Thread.current[THREAD_KEY] = nil
46
53
  end
@@ -58,6 +65,8 @@ module CMDx
58
65
  # perform_operation
59
66
  # end
60
67
  # # Previous ID is restored
68
+ #
69
+ # @rbs (String new_id) { () -> untyped } -> untyped
61
70
  def use(new_id)
62
71
  old_id = id
63
72
  self.id = new_id
@@ -92,6 +101,8 @@ module CMDx
92
101
  # Correlate.call(task, id: -> { "dynamic-#{Time.now.to_i}" }, &block)
93
102
  # @example Conditional correlation
94
103
  # Correlate.call(task, if: :enable_correlation, &block)
104
+ #
105
+ # @rbs (Task task, **untyped options) { () -> untyped } -> untyped
95
106
  def call(task, **options, &)
96
107
  return yield unless Utils::Condition.evaluate(task, options)
97
108
 
@@ -32,6 +32,8 @@ module CMDx
32
32
  # Runtime.call(task, if: :enable_profiling, &block)
33
33
  # @example Disable runtime measurement
34
34
  # Runtime.call(task, unless: :skip_profiling, &block)
35
+ #
36
+ # @rbs (Task task, **untyped options) { () -> untyped } -> untyped
35
37
  def call(task, **options)
36
38
  return yield unless Utils::Condition.evaluate(task, options)
37
39
 
@@ -49,6 +51,8 @@ module CMDx
49
51
  # timing measurements that are not affected by system clock changes.
50
52
  #
51
53
  # @return [Integer] Current monotonic time in milliseconds
54
+ #
55
+ # @rbs () -> Integer
52
56
  def monotonic_time
53
57
  Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
54
58
  end