cmdx 2.0.0 → 2.0.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b679253afacaaa6d2b1ef842026e3b535ee1b0378167bfa0ebfe5b8c93508e83
4
- data.tar.gz: 1751b980c3af7d2e4950d6f1447d17883f94ba1e26ff1a550d90043fbb30b956
3
+ metadata.gz: f2cf3294b8f53c29043999fe6749ffee42f6c9a52d2e4e327f1ac4589ae96c47
4
+ data.tar.gz: 8d73284f355b01e171bb75f69c3fab37240462140e834f96e07831402517b89c
5
5
  SHA512:
6
- metadata.gz: b6998f0a2d795797ca412008afb29eb8c7b8240eeda91a6f88cccae9b77c3250b1b8616d67d661bb82aab6f8d8c2b7f32b9cbc5e099fe1989eb7efd3d927d457
7
- data.tar.gz: a4240ac375f5488dc3b312f19d0f556f8f1e78ed84189288de3fea8d82a3eda1586094d2ef45722450c95426ec007f400d926c327f5c2ddb8ad25415f2246187
6
+ metadata.gz: 5c1e9c43bddefec0085027447651b20de2e1ef16c57492c060d411b08fc036435c7358bcc910fcb7b7f14a8a3d166f45d4a856b17aaa2454bdb2f5f609ca4be3
7
+ data.tar.gz: 382b8a147035f476f2b03fda8fc1573a4b729bfc336d9776bf04d6c3f5bd95f6617d9efa77b0bbd3576a3feae828537618c06cfa53df1b119d8ac2425ac63682
data/CHANGELOG.md CHANGED
@@ -4,7 +4,16 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
- ## [2.0.0] - UNRELEASED
7
+ ## [2.x.x] - UNRELEASED
8
+
9
+ ### Changed
10
+ - Link cause of a raised Fault exception
11
+ - Simplify context `method_missing` lookup order (small perf boost)
12
+ - Attempt translation of errors `full_messages`
13
+ - Emit `:task_rolled_back` telemetry event on workflow rollback
14
+ - `around_execution` callbacks now wrap only `Task#work` (and any `#rollback`); `before_validation` runs *before* the around-block and `after_execution` runs *after* it. Previously both were nested inside the around-block
15
+
16
+ ## [2.0.0] - 2026-05-05
8
17
 
9
18
  Full runtime rewrite: the v1 state-machine plus Zeitwerk architecture is replaced by an explicit signal-based runtime, immutable results, fiber-local chains, and a slimmer registry surface. See [docs/v2-migration.md](docs/v2-migration.md) for the full upgrade guide.
10
19
 
data/lib/cmdx/context.rb CHANGED
@@ -281,14 +281,14 @@ module CMDx
281
281
  # @raise [NoMethodError] when {#strict?} is true and the key is missing
282
282
  # @api private
283
283
  def method_missing(method_name, *args, **_kwargs, &)
284
- if method_name.end_with?("=")
284
+ if @table.key?(method_name)
285
+ @table[method_name]
286
+ elsif method_name.end_with?("=")
285
287
  @table[method_name[..-2].to_sym] = args.first
286
288
  elsif method_name.end_with?("?")
287
289
  !!@table[method_name[..-2].to_sym]
288
- elsif strict? && !@table.key?(method_name)
290
+ elsif strict?
289
291
  raise NoMethodError, "unknown context key #{method_name.inspect} (strict mode)"
290
- else
291
- @table[method_name]
292
292
  end
293
293
  end
294
294
 
data/lib/cmdx/errors.rb CHANGED
@@ -113,7 +113,10 @@ module CMDx
113
113
  # (e.g. `{ name: ["name is required"] }`)
114
114
  def full_messages
115
115
  messages.each_with_object({}) do |(key, set), hash|
116
- hash[key] = set.map { |message| "#{key} #{message}" }
116
+ hash[key] = set.map do |message|
117
+ i18n_message = I18nProxy.t(message, default: message)
118
+ "#{key} #{i18n_message}"
119
+ end
117
120
  end
118
121
  end
119
122
 
@@ -62,7 +62,7 @@ module CMDx
62
62
  # @option options [Hash{Symbol => Object}] extra keys interpolated via `String#%` for bundled translations
63
63
  # @return [String, Object] the translated/interpolated message
64
64
  def translate(key, **options)
65
- return ::I18n.translate(key, **options) if defined?(::I18n) && ::I18n.respond_to?(:translate)
65
+ return ::I18n.translate(key, **options) if defined?(::I18n)
66
66
 
67
67
  message = translation_default(key) || options[:default]
68
68
 
data/lib/cmdx/pipeline.rb CHANGED
@@ -70,8 +70,6 @@ module CMDx
70
70
 
71
71
  private
72
72
 
73
- # @param group [Workflow::ExecutionGroup]
74
- # @return [Result, nil] failed result to halt on, or nil when the group succeeds
75
73
  def run_sequential(group)
76
74
  continue = group.options[:continue_on_failure]
77
75
  failures = group.tasks.each_with_object([]) do |task_class, bucket|
@@ -87,8 +85,6 @@ module CMDx
87
85
  aggregate(failures, continue:)
88
86
  end
89
87
 
90
- # @param group [Workflow::ExecutionGroup]
91
- # @return [Result, nil] failed result to halt on, or nil when the group succeeds
92
88
  def run_parallel(group)
93
89
  tasks = group.tasks
94
90
  chain = Chain.current
@@ -146,17 +142,23 @@ module CMDx
146
142
  next unless result.success?
147
143
  next unless instance.respond_to?(:rollback)
148
144
 
149
- instance.rollback
150
-
151
145
  old_opts = result.instance_variable_get(:@options)
152
146
  new_opts = old_opts.merge(rolled_back: true).freeze
153
147
  result.instance_variable_set(:@options, new_opts)
148
+
149
+ emit_telemetry(instance, :task_rolled_back)
150
+ instance.rollback
154
151
  end
155
152
  end
156
153
 
157
- # @param failures [Array<Result>]
158
- # @param continue [Boolean] when true, merges failures into the workflow's errors
159
- # @return [Result, nil] first failure (echoed upstream), or nil when `failures` is empty
154
+ def emit_telemetry(instance, name, payload = EMPTY_HASH)
155
+ telemetry = instance.class.telemetry
156
+ return unless telemetry.subscribed?(name)
157
+
158
+ event = Telemetry::Event.build(instance, name, root: false, payload:)
159
+ telemetry.emit(name, event)
160
+ end
161
+
160
162
  def aggregate(failures, continue:)
161
163
  return if failures.empty?
162
164
  return failures.first unless continue
data/lib/cmdx/runtime.rb CHANGED
@@ -90,12 +90,12 @@ module CMDx
90
90
  def run_lifecycle
91
91
  measure_duration do
92
92
  run_callbacks(:before_execution)
93
+ run_callbacks(:before_validation)
93
94
  run_around(:around_execution) do
94
- run_callbacks(:before_validation)
95
95
  perform_work
96
96
  perform_rollback if @signal.failed?
97
- run_callbacks(:after_execution)
98
97
  end
98
+ run_callbacks(:after_execution)
99
99
  run_callbacks(:"on_#{@signal.state}")
100
100
  run_callbacks(:"on_#{@signal.status}")
101
101
  run_callbacks(:on_ok) if @signal.ok?
@@ -109,7 +109,7 @@ module CMDx
109
109
  cause = @signal.cause
110
110
  raise cause if cause && !cause.is_a?(Fault)
111
111
 
112
- raise Fault, @result.caused_failure
112
+ raise Fault, @result.caused_failure, cause:
113
113
  end
114
114
 
115
115
  def finalize_result
@@ -151,8 +151,6 @@ module CMDx
151
151
  @duration = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start
152
152
  end
153
153
 
154
- # @param event [Symbol] callback event from {Callbacks::EVENTS}
155
- # @return [void]
156
154
  def run_callbacks(event)
157
155
  callbacks = @task.class.callbacks
158
156
  return if callbacks.empty?
@@ -160,9 +158,6 @@ module CMDx
160
158
  callbacks.process(event, @task)
161
159
  end
162
160
 
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
161
  def run_around(event, &)
167
162
  callbacks = @task.class.callbacks
168
163
  return yield if callbacks.empty?
@@ -225,25 +220,11 @@ module CMDx
225
220
  throw(Signal::TAG, Signal.failed(@task.errors.to_s, metadata: @task.metadata))
226
221
  end
227
222
 
228
- # @param name [Symbol] telemetry channel from {Telemetry::EVENTS}
229
- # @param payload [Hash{Symbol => Object}] forwarded onto {Telemetry::Event#payload}
230
- # @return [void]
231
223
  def emit_telemetry(name, payload = EMPTY_HASH)
232
224
  telemetry = @task.class.telemetry
233
225
  return unless telemetry.subscribed?(name)
234
226
 
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
-
227
+ event = Telemetry::Event.build(@task, name, root: @root, payload:)
247
228
  telemetry.emit(name, event)
248
229
  end
249
230
 
@@ -7,7 +7,21 @@ module CMDx
7
7
  class Telemetry
8
8
 
9
9
  # Immutable event payload passed to subscribers.
10
- Event = Data.define(:xid, :cid, :root, :type, :task, :tid, :name, :payload, :timestamp)
10
+ Event = Data.define(:xid, :cid, :root, :type, :task, :tid, :name, :payload, :timestamp) do
11
+ def self.build(task, name, root: false, payload: EMPTY_HASH)
12
+ new(
13
+ xid: Chain.current.xid,
14
+ cid: Chain.current.id,
15
+ root:,
16
+ type: task.class.type,
17
+ task: task.class,
18
+ tid: task.tid,
19
+ name:,
20
+ payload:,
21
+ timestamp: Time.now.utc
22
+ )
23
+ end
24
+ end
11
25
 
12
26
  # Lifecycle event names Runtime emits.
13
27
  EVENTS = %i[
data/lib/cmdx/version.rb CHANGED
@@ -3,6 +3,6 @@
3
3
  module CMDx
4
4
 
5
5
  # Gem version. Bumped on release; mirrored in the gemspec.
6
- VERSION = "2.0.0"
6
+ VERSION = "2.0.1"
7
7
 
8
8
  end
@@ -108,6 +108,28 @@ CMDx.configure do |config|
108
108
  # end
109
109
  # end)
110
110
 
111
+ # ===========================================================================
112
+ # Retriers
113
+ # ===========================================================================
114
+ # Registered retriers compute the sleep duration between `retry_on` attempts.
115
+ # Built-ins: `:exponential` (default), `:linear`, `:fibonacci`, `:half_random`,
116
+ # `:full_random`, `:bounded_random`, `:decorrelated_jitter`. A callable
117
+ # receives `call(attempt, delay, prev_delay)` and returns seconds.
118
+ #
119
+ # config.retriers.register(:capped_exponential, proc do |attempt, delay, _prev|
120
+ # [delay * (2**(attempt - 1)), 30.0].min
121
+ # end)
122
+
123
+ # ===========================================================================
124
+ # Deprecators
125
+ # ===========================================================================
126
+ # Registered deprecators dispatch a task class's `deprecation` declaration.
127
+ # Built-ins: `:log`, `:warn`, `:error`. A callable receives `call(task)`.
128
+ #
129
+ # config.deprecators.register(:notify, proc do |task|
130
+ # Bugsnag.notify("Deprecated task invoked: #{task.class.name}")
131
+ # end)
132
+
111
133
  # ===========================================================================
112
134
  # Executors
113
135
  # ===========================================================================
data/mkdocs.yml CHANGED
@@ -195,6 +195,7 @@ nav:
195
195
  - llms-full.txt: https://drexed.github.io/cmdx/llms-full.txt
196
196
  - AI Skills: https://github.com/drexed/cmdx/blob/main/skills
197
197
  - Ecosystem:
198
+ - cmdx-i18n: https://github.com/drexed/cmdx-i18n
198
199
  - cmdx-rspec: https://github.com/drexed/cmdx-rspec
199
200
  - Blog: blog/index.md
200
201
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cmdx
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juan Gomez