cmdx 1.7.0 → 1.7.2

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: 8483d78b67a6dfffa6721b39e623ac08d57d443b499287891bfb9c6e06422c89
4
- data.tar.gz: 02cb722299d6a97fb4e2d07a84880c7374a8c17b691f42bee995bd4f3ea698ab
3
+ metadata.gz: 528f3b3b1d4f23045a295cc15c05106c3bd0f9efde6400bb88c01bb2a6455637
4
+ data.tar.gz: 473ca317fd214b48e4e4988ba4358bdcff4a3fc9f9805300dba292f4401e51c1
5
5
  SHA512:
6
- metadata.gz: b6600b6d617c11a3ec3b66367a7fa309e191bd2d1f20a3fb1a538565a0c2b45e05f8093f2c3e07edbb155e308e6c90c486f069db171aac68e8658bbe85bd4b34
7
- data.tar.gz: 6ad4a1348066d3aa4d0ed2733fc88f94af293835cc970be5986f3c002cf3b3615f474bff774a2b48f7e043920ca01b5f278cd26850b5b4827a70d2a4b0e6ab50
6
+ metadata.gz: 5b48be81fa184343f76c1891a3b87c25853e0dc859764dbf92946c7b31d69fd4af85b75eee7720470e29949edb395bce668343dbc6b17916079ba9befd1fbc80
7
+ data.tar.gz: b95fe96f12cf2aeb724782808d80b4ee36e083d0f6c60876edac45a6046c7925437f329dc2f98a42432b4794996c3269f71ba7f93452f161a38f365c6d40afa6
data/CHANGELOG.md CHANGED
@@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [TODO]
8
8
 
9
+ ## [1.7.2] - 2025-09-03
10
+
11
+ ### Changes
12
+ - Correlation ID is set before continuing to further steps
13
+
14
+ ## [1.7.1] - 2025-08-26
15
+
16
+ ### Added
17
+ - Yield result if block given to `execute` and `execute!` methods
18
+
9
19
  ## [1.7.0] - 2025-08-25
10
20
 
11
21
  ### Added
data/LLM.md CHANGED
@@ -11,6 +11,31 @@ url: https://github.com/drexed/cmdx/blob/main/docs/getting_started.md
11
11
 
12
12
  CMDx is a Ruby framework for building maintainable, observable business logic through composable command objects. Design robust workflows with automatic attribute validation, structured error handling, comprehensive logging, and intelligent execution flow control.
13
13
 
14
+ **Common Challenges:**
15
+
16
+ - Inconsistent patterns across implementations
17
+ - Minimal or no logging, making debugging painful
18
+ - Fragile designs that erode developer confidence
19
+
20
+ **CMDx Solutions:**
21
+
22
+ - Establishes a consistent, standardized design
23
+ - Provides flow control and error handling
24
+ - Supports composable, reusable workflows
25
+ - Includes detailed logging for observability
26
+ - Defines input attributes with fallback defaults
27
+ - Adds validations and type coercions
28
+ - Plus many other developer-friendly tools
29
+
30
+ ## Compose, Execute, React, Observe pattern
31
+
32
+ CMDx encourages breaking business logic into composable tasks. Each task can be combined into larger workflows, executed with standardized flow control, and fully observed through logging, validations, and context.
33
+
34
+ - **Compose** → Define small, contract-driven tasks with typed attributes, validations, and natural workflow composition.
35
+ - **Execute** → Run tasks with clear outcomes, intentional halts, and pluggable behaviors via middlewares and callbacks.
36
+ - **React** → Adapt to outcomes by chaining follow-up tasks, handling faults, or shaping future flows.
37
+ - **Observe** → Capture immutable results, structured logs, and full execution chains for reliable tracing and insight.
38
+
14
39
  ## Installation
15
40
 
16
41
  Add CMDx to your Gemfile:
@@ -1251,6 +1276,22 @@ result.index #=> 0 (first task in chain)
1251
1276
  result.chain.results[result.index] == result #=> true
1252
1277
  ```
1253
1278
 
1279
+ ## Block Yield
1280
+
1281
+ Implement conditional logic using a block expression that yields a result for complete encapsulation.
1282
+
1283
+ ```ruby
1284
+ BuildApplication.execute(version: "1.2.3") do |result|
1285
+ if result.success?
1286
+ notify_deployment_ready(result)
1287
+ elsif result.failed?
1288
+ handle_build_failure(result)
1289
+ else
1290
+ log_skip_reason(result)
1291
+ end
1292
+ end
1293
+ ```
1294
+
1254
1295
  ## Handlers
1255
1296
 
1256
1297
  Use result handlers for clean, functional-style conditional logic. Handlers return the result object, enabling method chaining and fluent interfaces.
@@ -2727,21 +2768,19 @@ Sample output:
2727
2768
  ```log
2728
2769
  <!-- Success (INFO level) -->
2729
2770
  I, [2022-07-17T18:43:15.000000 #3784] INFO -- GenerateInvoice:
2730
- index=0 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task"
2731
- class="GenerateInvoice" state="complete" status="success" metadata={runtime: 187}
2771
+ index=0 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="GenerateInvoice" state="complete" status="success" metadata={runtime: 187}
2732
2772
 
2733
2773
  <!-- Skipped (WARN level) -->
2734
2774
  W, [2022-07-17T18:43:15.000000 #3784] WARN -- ValidateCustomer:
2735
- index=1 state="interrupted" status="skipped" reason="Customer already validated"
2775
+ index=1 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="ValidateCustomer" state="interrupted" status="skipped" reason="Customer already validated"
2736
2776
 
2737
2777
  <!-- Failed (ERROR level) -->
2738
2778
  E, [2022-07-17T18:43:15.000000 #3784] ERROR -- CalculateTax:
2739
- index=2 state="interrupted" status="failed" metadata={error_code: "TAX_SERVICE_UNAVAILABLE"}
2779
+ index=2 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="CalculateTax" state="interrupted" status="failed" metadata={error_code: "TAX_SERVICE_UNAVAILABLE"}
2740
2780
 
2741
2781
  <!-- Failed Chain -->
2742
2782
  E, [2022-07-17T18:43:15.000000 #3784] ERROR -- BillingWorkflow:
2743
- caused_failure={index: 2, class: "CalculateTax", status: "failed"}
2744
- threw_failure={index: 1, class: "ValidateCustomer", status: "failed"}
2783
+ index=3 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="BillingWorkflow" state="interrupted" status="failed" caused_failure={index: 2, class: "CalculateTax", status: "failed"} threw_failure={index: 1, class: "ValidateCustomer", status: "failed"}
2745
2784
  ```
2746
2785
 
2747
2786
  > [!TIP]
data/README.md CHANGED
@@ -8,16 +8,24 @@
8
8
  <img alt="License" src="https://img.shields.io/github/license/drexed/cmdx">
9
9
  </p>
10
10
 
11
- # CMDx
11
+ # 🚀 CMDx — Business logic without the chaos
12
12
 
13
- CMDx is a framework for building maintainable business processes. It simplifies building task objects by offering integrated:
13
+ Stop wrestling with messy service objects. CMDx gives you a clean, consistent way to design business processes:
14
14
 
15
- - Flow controls
16
- - Composable workflows
17
- - Comprehensive logging
18
- - Attribute definition
19
- - Validations and coercions
20
- - And much more...
15
+ - Start small with a single `work` method
16
+ - Scale to complex tasks and multi-step workflows
17
+ - Get built-in flow control, logging, validations, and more...
18
+
19
+ *Build faster. Debug easier. Stay sane.*
20
+
21
+ ## Compose, Execute, React, Observe pattern
22
+
23
+ CMDx encourages breaking business logic into composable tasks. Each task can be combined into larger workflows, executed with standardized flow control, and fully observed through logging, validations, and context.
24
+
25
+ - **Compose** → Define small, contract-driven tasks with typed attributes, validations, and natural workflow composition.
26
+ - **Execute** → Run tasks with clear outcomes, intentional halts, and pluggable behaviors via middlewares and callbacks.
27
+ - **React** → Adapt to outcomes by chaining follow-up tasks, handling faults, or shaping future flows.
28
+ - **Observe** → Capture immutable results, structured logs, and full execution chains for reliable tracing and insight.
21
29
 
22
30
  ## Installation
23
31
 
@@ -37,11 +45,24 @@ Or install it yourself as:
37
45
 
38
46
  ## Quick Example
39
47
 
40
- Here's how a quick 3 step process can open up a world of possibilities:
48
+ Here's how a quick 4 step process can open up a world of possibilities:
49
+
50
+ ### 1. Compose
51
+
52
+ #### Minimum Viable Task
53
+
54
+ ```ruby
55
+ class SendAnalyzedEmail < CMDx::Task
56
+ def work
57
+ user = User.find(context.user_id)
58
+ MetricsMailer.analyzed(user).deliver_now
59
+ end
60
+ end
61
+ ```
62
+
63
+ #### Fully Featured Task
41
64
 
42
65
  ```ruby
43
- # 1. Setup task
44
- # ---------------------------------
45
66
  class AnalyzeMetrics < CMDx::Task
46
67
  register :middleware, CMDx::Middlewares::Correlate, id: -> { Current.request_id }
47
68
 
@@ -56,8 +77,10 @@ class AnalyzeMetrics < CMDx::Task
56
77
  elsif dataset.unprocessed?
57
78
  skip!("Dataset not ready for analysis")
58
79
  else
59
- context.result = PValueAnalyzer.analyze(dataset, analysis_type)
80
+ context.result = PValueAnalyzer.execute(dataset:, analysis_type:)
60
81
  context.analyzed_at = Time.now
82
+
83
+ SendAnalyzedEmail.execute(user_id: Current.account.manager_id)
61
84
  end
62
85
  end
63
86
 
@@ -71,16 +94,20 @@ class AnalyzeMetrics < CMDx::Task
71
94
  dataset.update!(analysis_result_id: context.result.id)
72
95
  end
73
96
  end
97
+ ```
74
98
 
75
- # 2. Execute task
76
- # ---------------------------------
99
+ ### 2. Execute
100
+
101
+ ```ruby
77
102
  result = AnalyzeMetrics.execute(
78
103
  dataset_id: 123,
79
104
  "analysis_type" => "advanced"
80
105
  )
106
+ ```
81
107
 
82
- # 3. Handle result
83
- # ---------------------------------
108
+ ### 3. React
109
+
110
+ ```ruby
84
111
  if result.success?
85
112
  puts "Metrics analyzed at #{result.context.analyzed_at}"
86
113
  elsif result.skipped?
@@ -90,6 +117,16 @@ elsif result.failed?
90
117
  end
91
118
  ```
92
119
 
120
+ ### 4. Observe
121
+
122
+ ```log
123
+ I, [2022-07-17T18:42:37.000000 #3784] INFO -- CMDx:
124
+ index=1 chain_id="018c2b95-23j4-2kj3-32kj-3n4jk3n4jknf" type="Task" class="SendAnalyzedEmail" state="complete" status="success" metadata={runtime: 347}
125
+
126
+ I, [2022-07-17T18:43:15.000000 #3784] INFO -- CMDx:
127
+ index=0 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="AnalyzeMetrics" state="complete" status="success" metadata={runtime: 187}
128
+ ```
129
+
93
130
  ## Table of contents
94
131
 
95
132
  - [Getting Started](docs/getting_started.md)
@@ -122,12 +159,12 @@ end
122
159
 
123
160
  ## Ecosystem
124
161
 
125
- - [cmdx-i18n](https://github.com/drexed/cmdx-i18n) - 85+ translations
126
- - [cmdx-parallel](https://github.com/drexed/cmdx-parallel) - Parallel workflow tasks
162
+ - [cmdx-rspec](https://github.com/drexed/cmdx-rspec) - RSpec test matchers
127
163
 
128
- The following gems are currently under development:
164
+ For backwards compatibility of certain functionality:
129
165
 
130
- - `cmdx-testing` - RSpec and Minitest matchers
166
+ - [cmdx-i18n](https://github.com/drexed/cmdx-i18n) - 85+ translations, `v1.5.0` - `v1.6.2`
167
+ - [cmdx-parallel](https://github.com/drexed/cmdx-parallel) - Parallel workflow tasks, `v1.6.1` - `v1.6.2`
131
168
 
132
169
  ## Development
133
170
 
@@ -2,8 +2,25 @@
2
2
 
3
3
  CMDx is a Ruby framework for building maintainable, observable business logic through composable command objects. Design robust workflows with automatic attribute validation, structured error handling, comprehensive logging, and intelligent execution flow control.
4
4
 
5
+ **Common Challenges:**
6
+
7
+ - Inconsistent patterns across implementations
8
+ - Minimal or no logging, making debugging painful
9
+ - Fragile designs that erode developer confidence
10
+
11
+ **CMDx Solutions:**
12
+
13
+ - Establishes a consistent, standardized design
14
+ - Provides flow control and error handling
15
+ - Supports composable, reusable workflows
16
+ - Includes detailed logging for observability
17
+ - Defines input attributes with fallback defaults
18
+ - Adds validations and type coercions
19
+ - Plus many other developer-friendly tools
20
+
5
21
  ## Table of Contents
6
22
 
23
+ - [Compose, Execute, React, Observe pattern](#compose-execute-react-observe-pattern)
7
24
  - [Installation](#installation)
8
25
  - [Configuration Hierarchy](#configuration-hierarchy)
9
26
  - [Global Configuration](#global-configuration)
@@ -21,6 +38,15 @@ CMDx is a Ruby framework for building maintainable, observable business logic th
21
38
  - [Resetting](#resetting)
22
39
  - [Task Generator](#task-generator)
23
40
 
41
+ ## Compose, Execute, React, Observe pattern
42
+
43
+ CMDx encourages breaking business logic into composable tasks. Each task can be combined into larger workflows, executed with standardized flow control, and fully observed through logging, validations, and context.
44
+
45
+ - *Compose* → Define small, contract-driven tasks with typed attributes, validations, and natural workflow composition.
46
+ - *Execute* → Run tasks with clear outcomes, intentional halts, and pluggable behaviors via middlewares and callbacks.
47
+ - *React* → Adapt to outcomes by chaining follow-up tasks, handling faults, or shaping future flows.
48
+ - *Observe* → Capture immutable results, structured logs, and full execution chains for reliable tracing and insight.
49
+
24
50
  ## Installation
25
51
 
26
52
  Add CMDx to your Gemfile:
data/docs/logging.md CHANGED
@@ -25,21 +25,19 @@ Sample output:
25
25
  ```log
26
26
  <!-- Success (INFO level) -->
27
27
  I, [2022-07-17T18:43:15.000000 #3784] INFO -- GenerateInvoice:
28
- index=0 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task"
29
- class="GenerateInvoice" state="complete" status="success" metadata={runtime: 187}
28
+ index=0 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="GenerateInvoice" state="complete" status="success" metadata={runtime: 187}
30
29
 
31
30
  <!-- Skipped (WARN level) -->
32
31
  W, [2022-07-17T18:43:15.000000 #3784] WARN -- ValidateCustomer:
33
- index=1 state="interrupted" status="skipped" reason="Customer already validated"
32
+ index=1 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="ValidateCustomer" state="interrupted" status="skipped" reason="Customer already validated"
34
33
 
35
34
  <!-- Failed (ERROR level) -->
36
35
  E, [2022-07-17T18:43:15.000000 #3784] ERROR -- CalculateTax:
37
- index=2 state="interrupted" status="failed" metadata={error_code: "TAX_SERVICE_UNAVAILABLE"}
36
+ index=2 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="CalculateTax" state="interrupted" status="failed" metadata={error_code: "TAX_SERVICE_UNAVAILABLE"}
38
37
 
39
38
  <!-- Failed Chain -->
40
39
  E, [2022-07-17T18:43:15.000000 #3784] ERROR -- BillingWorkflow:
41
- caused_failure={index: 2, class: "CalculateTax", status: "failed"}
42
- threw_failure={index: 1, class: "ValidateCustomer", status: "failed"}
40
+ index=3 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="BillingWorkflow" state="interrupted" status="failed" caused_failure={index: 2, class: "CalculateTax", status: "failed"} threw_failure={index: 1, class: "ValidateCustomer", status: "failed"}
43
41
  ```
44
42
 
45
43
  > [!TIP]
@@ -9,6 +9,7 @@ The result object is the comprehensive return value of task execution, providing
9
9
  - [Outcome Analysis](#outcome-analysis)
10
10
  - [Chain Analysis](#chain-analysis)
11
11
  - [Index and Position](#index-and-position)
12
+ - [Block Yield](#block-yield)
12
13
  - [Handlers](#handlers)
13
14
  - [Pattern Matching](#pattern-matching)
14
15
  - [Array Pattern](#array-pattern)
@@ -113,6 +114,22 @@ result.index #=> 0 (first task in chain)
113
114
  result.chain.results[result.index] == result #=> true
114
115
  ```
115
116
 
117
+ ## Block Yield
118
+
119
+ Implement conditional logic using a block expression that yields a result for complete encapsulation.
120
+
121
+ ```ruby
122
+ BuildApplication.execute(version: "1.2.3") do |result|
123
+ if result.success?
124
+ notify_deployment_ready(result)
125
+ elsif result.failed?
126
+ handle_build_failure(result)
127
+ else
128
+ log_skip_reason(result)
129
+ end
130
+ end
131
+ ```
132
+
116
133
  ## Handlers
117
134
 
118
135
  Use result handlers for clean, functional-style conditional logic. Handlers return the result object, enabling method chaining and fluent interfaces.
@@ -95,7 +95,8 @@ module CMDx
95
95
  def call(task, **options, &)
96
96
  return yield unless Utils::Condition.evaluate(task, options)
97
97
 
98
- correlation_id =
98
+ correlation_id = task.result.metadata[:correlation_id] ||=
99
+ id ||
99
100
  case callable = options[:id]
100
101
  when Symbol then task.send(callable)
101
102
  when Proc then task.instance_eval(&callable)
@@ -107,9 +108,7 @@ module CMDx
107
108
  end
108
109
  end
109
110
 
110
- result = use(correlation_id, &)
111
- task.result.metadata[:correlation_id] = correlation_id
112
- result
111
+ use(correlation_id, &)
113
112
  end
114
113
 
115
114
  end
data/lib/cmdx/task.rb CHANGED
@@ -169,10 +169,10 @@ module CMDx
169
169
  # if result.success?
170
170
  # puts "Task completed successfully"
171
171
  # end
172
- def execute(...)
173
- task = new(...)
172
+ def execute(*args, **kwargs)
173
+ task = new(*args, **kwargs)
174
174
  task.execute(raise: false)
175
- task.result
175
+ block_given? ? yield(task.result) : task.result
176
176
  end
177
177
 
178
178
  # @param args [Array] Arguments to pass to the task constructor
@@ -184,10 +184,10 @@ module CMDx
184
184
  # @example
185
185
  # result = MyTask.execute!(name: "example")
186
186
  # # Will raise an exception if execution fails
187
- def execute!(...)
188
- task = new(...)
187
+ def execute!(*args, **kwargs)
188
+ task = new(*args, **kwargs)
189
189
  task.execute(raise: true)
190
- task.result
190
+ block_given? ? yield(task.result) : task.result
191
191
  end
192
192
 
193
193
  end
@@ -201,6 +201,7 @@ module CMDx
201
201
  # result = task.execute(raise: true)
202
202
  def execute(raise: false)
203
203
  Executor.execute(self, raise:)
204
+ block_given? ? yield(result) : result
204
205
  end
205
206
 
206
207
  # @raise [UndefinedMethodError] Always raised as this method must be overridden
data/lib/cmdx/version.rb CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  module CMDx
4
4
 
5
- VERSION = "1.7.0"
5
+ VERSION = "1.7.2"
6
6
 
7
7
  end
@@ -10,7 +10,7 @@ module Cmdx
10
10
 
11
11
  source_root File.expand_path("../../locales", __dir__)
12
12
 
13
- desc "Copies the locale with the given alpha-2 code"
13
+ desc "Copies the locale with the given ISO 639 code"
14
14
 
15
15
  argument :locale, type: :string, default: "en", banner: "locale: en, es, fr, etc"
16
16
 
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: 1.7.0
4
+ version: 1.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juan Gomez