cmdx 1.13.0 → 1.15.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 (154) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +92 -76
  3. data/LICENSE.txt +3 -20
  4. data/README.md +16 -8
  5. data/lib/cmdx/attribute.rb +42 -5
  6. data/lib/cmdx/context.rb +16 -0
  7. data/lib/cmdx/executor.rb +9 -9
  8. data/lib/cmdx/result.rb +27 -7
  9. data/lib/cmdx/task.rb +19 -0
  10. data/lib/cmdx/validator_registry.rb +1 -0
  11. data/lib/cmdx/validators/absence.rb +61 -0
  12. data/lib/cmdx/version.rb +1 -1
  13. data/lib/locales/af.yml +1 -0
  14. data/lib/locales/ar.yml +1 -0
  15. data/lib/locales/az.yml +1 -0
  16. data/lib/locales/be.yml +1 -0
  17. data/lib/locales/bg.yml +1 -0
  18. data/lib/locales/bn.yml +1 -0
  19. data/lib/locales/bs.yml +1 -0
  20. data/lib/locales/ca.yml +1 -0
  21. data/lib/locales/cnr.yml +1 -0
  22. data/lib/locales/cs.yml +1 -0
  23. data/lib/locales/cy.yml +1 -0
  24. data/lib/locales/da.yml +1 -0
  25. data/lib/locales/de.yml +1 -0
  26. data/lib/locales/dz.yml +1 -0
  27. data/lib/locales/el.yml +1 -0
  28. data/lib/locales/en.yml +1 -0
  29. data/lib/locales/eo.yml +1 -0
  30. data/lib/locales/es.yml +1 -0
  31. data/lib/locales/et.yml +1 -0
  32. data/lib/locales/eu.yml +1 -0
  33. data/lib/locales/fa.yml +1 -0
  34. data/lib/locales/fi.yml +1 -0
  35. data/lib/locales/fr.yml +1 -0
  36. data/lib/locales/fy.yml +1 -0
  37. data/lib/locales/gd.yml +1 -0
  38. data/lib/locales/gl.yml +1 -0
  39. data/lib/locales/he.yml +1 -0
  40. data/lib/locales/hi.yml +1 -0
  41. data/lib/locales/hr.yml +1 -0
  42. data/lib/locales/hu.yml +1 -0
  43. data/lib/locales/hy.yml +1 -0
  44. data/lib/locales/id.yml +1 -0
  45. data/lib/locales/is.yml +1 -0
  46. data/lib/locales/it.yml +1 -0
  47. data/lib/locales/ja.yml +1 -0
  48. data/lib/locales/ka.yml +1 -0
  49. data/lib/locales/kk.yml +1 -0
  50. data/lib/locales/km.yml +1 -0
  51. data/lib/locales/kn.yml +1 -0
  52. data/lib/locales/ko.yml +1 -0
  53. data/lib/locales/lb.yml +1 -0
  54. data/lib/locales/lo.yml +1 -0
  55. data/lib/locales/lt.yml +1 -0
  56. data/lib/locales/lv.yml +1 -0
  57. data/lib/locales/mg.yml +1 -0
  58. data/lib/locales/mk.yml +1 -0
  59. data/lib/locales/ml.yml +1 -0
  60. data/lib/locales/mn.yml +1 -0
  61. data/lib/locales/mr-IN.yml +1 -0
  62. data/lib/locales/ms.yml +1 -0
  63. data/lib/locales/nb.yml +1 -0
  64. data/lib/locales/ne.yml +1 -0
  65. data/lib/locales/nl.yml +1 -0
  66. data/lib/locales/nn.yml +1 -0
  67. data/lib/locales/oc.yml +1 -0
  68. data/lib/locales/or.yml +1 -0
  69. data/lib/locales/pa.yml +1 -0
  70. data/lib/locales/pl.yml +1 -0
  71. data/lib/locales/pt.yml +1 -0
  72. data/lib/locales/rm.yml +1 -0
  73. data/lib/locales/ro.yml +1 -0
  74. data/lib/locales/ru.yml +1 -0
  75. data/lib/locales/sc.yml +1 -0
  76. data/lib/locales/sk.yml +1 -0
  77. data/lib/locales/sl.yml +1 -0
  78. data/lib/locales/sq.yml +1 -0
  79. data/lib/locales/sr.yml +1 -0
  80. data/lib/locales/st.yml +1 -0
  81. data/lib/locales/sv.yml +1 -0
  82. data/lib/locales/sw.yml +1 -0
  83. data/lib/locales/ta.yml +1 -0
  84. data/lib/locales/te.yml +1 -0
  85. data/lib/locales/th.yml +1 -0
  86. data/lib/locales/tl.yml +1 -0
  87. data/lib/locales/tr.yml +1 -0
  88. data/lib/locales/tt.yml +1 -0
  89. data/lib/locales/ug.yml +1 -0
  90. data/lib/locales/uk.yml +1 -0
  91. data/lib/locales/ur.yml +1 -0
  92. data/lib/locales/uz.yml +1 -0
  93. data/lib/locales/vi.yml +1 -0
  94. data/lib/locales/wo.yml +1 -0
  95. data/lib/locales/zh-CN.yml +1 -0
  96. data/lib/locales/zh-HK.yml +1 -0
  97. data/lib/locales/zh-TW.yml +1 -0
  98. data/lib/locales/zh-YUE.yml +1 -0
  99. data/mkdocs.yml +65 -36
  100. metadata +4 -57
  101. data/.cursor/prompts/docs.md +0 -12
  102. data/.cursor/prompts/llms.md +0 -8
  103. data/.cursor/prompts/rspec.md +0 -24
  104. data/.cursor/prompts/yardoc.md +0 -15
  105. data/.cursor/rules/cursor-instructions.mdc +0 -68
  106. data/.irbrc +0 -18
  107. data/.rspec +0 -4
  108. data/.rubocop.yml +0 -95
  109. data/.ruby-version +0 -1
  110. data/.yard-lint.yml +0 -174
  111. data/.yardopts +0 -7
  112. data/docs/.DS_Store +0 -0
  113. data/docs/assets/favicon.ico +0 -0
  114. data/docs/assets/favicon.svg +0 -1
  115. data/docs/attributes/coercions.md +0 -155
  116. data/docs/attributes/defaults.md +0 -77
  117. data/docs/attributes/definitions.md +0 -283
  118. data/docs/attributes/naming.md +0 -68
  119. data/docs/attributes/transformations.md +0 -63
  120. data/docs/attributes/validations.md +0 -336
  121. data/docs/basics/chain.md +0 -108
  122. data/docs/basics/context.md +0 -121
  123. data/docs/basics/execution.md +0 -152
  124. data/docs/basics/setup.md +0 -107
  125. data/docs/callbacks.md +0 -157
  126. data/docs/configuration.md +0 -314
  127. data/docs/deprecation.md +0 -143
  128. data/docs/getting_started.md +0 -137
  129. data/docs/index.md +0 -134
  130. data/docs/internationalization.md +0 -126
  131. data/docs/interruptions/exceptions.md +0 -52
  132. data/docs/interruptions/faults.md +0 -169
  133. data/docs/interruptions/halt.md +0 -216
  134. data/docs/logging.md +0 -90
  135. data/docs/middlewares.md +0 -191
  136. data/docs/outcomes/result.md +0 -197
  137. data/docs/outcomes/states.md +0 -66
  138. data/docs/outcomes/statuses.md +0 -65
  139. data/docs/retries.md +0 -121
  140. data/docs/stylesheets/extra.css +0 -42
  141. data/docs/tips_and_tricks.md +0 -157
  142. data/docs/workflows.md +0 -226
  143. data/examples/active_record_database_transaction.md +0 -27
  144. data/examples/active_record_query_tagging.md +0 -46
  145. data/examples/flipper_feature_flags.md +0 -50
  146. data/examples/paper_trail_whatdunnit.md +0 -39
  147. data/examples/redis_idempotency.md +0 -71
  148. data/examples/sentry_error_tracking.md +0 -46
  149. data/examples/sidekiq_async_execution.md +0 -29
  150. data/examples/stoplight_circuit_breaker.md +0 -36
  151. data/src/cmdx-dark-logo.png +0 -0
  152. data/src/cmdx-favicon.svg +0 -1
  153. data/src/cmdx-light-logo.png +0 -0
  154. data/src/cmdx-logo.svg +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b1cd7841d40aeac40b6eb36a7ff87a5ace03deae3496e1aef5cb10e34f8f42d
4
- data.tar.gz: 303333d4635690575ceda40a2564be417b47f5ec4c2992af542c509710289ae3
3
+ metadata.gz: f23ba43366877a7e368b02f036bd98212c53ee1d52ae47c09e4d689ac305651e
4
+ data.tar.gz: 4b4e778baa84ae14274c8313464c979fb1be9c9d0651f45ca0483bc7f530a3c2
5
5
  SHA512:
6
- metadata.gz: 18cde5cf4b6c7fa974ffb7a3827e09d0a5c2ac5f3c2c01427abbc94de71ef1bc03aee78e25032900761f09c8d39e89e52a4ea1d5fa33e07c86f060b483fe839f
7
- data.tar.gz: 5ed05ecdb682ae160be47cf6399c76081486011b6143f95f9884a06b588474ba64e3e000efd72ba2dcd6abb343e3e12985d2691ca15ee7575873f90daff6e70f
6
+ metadata.gz: e1ea9eeebd2d84da3c8d0813dbf8739f8776adc0fb749ee9309f63bff258026b4ec9a8f53e32bca6c8a806ae135a617773709ff41d9b52fd3def07f914b2199d
7
+ data.tar.gz: 1dc6797d0e13aa08f900464c731f81cc5fd15672094c9ce3f02d0487d99395ce219b21ad7a9e95aa1d833b3dd36a9394a156fec503cd417109e3e08d33e41449
data/CHANGELOG.md CHANGED
@@ -4,175 +4,191 @@ 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
- ## [UNRELEASED]
7
+ ## [Unreleased]
8
8
 
9
- ## [1.13.0] - 2025-12-23
9
+ ## [1.15.0] - 2025-01-21
10
10
 
11
11
  ### Added
12
+ - Add attribute `Absence` validator
13
+ - Add attribute `:description` option
12
14
 
13
- - Added task execution rollback tracking and logging
14
- - Added `dry_run` option to task execution with inheritance support for nested tasks
15
- - Added context `delete` alias for `delete!`
16
- - Added context `merge` alias for `merge!`
15
+ ## [1.14.0] - 2025-01-09
17
16
 
18
- ## [1.12.0] - 2025-12-18
17
+ ### Added
18
+ - Add Ruby 4.0 compatibility
19
+ - Add `Context#clear!` method to remove all context data
20
+ - Add `Task.attribute_schema` class method for attribute introspection
21
+ - Add `#to_h` method for attribute serialization
22
+
23
+ ### Changed
24
+ - **BREAKING**: Switch license from MIT to LGPLv3
25
+ - Replace `instance_eval` with `define_singleton_method` for attribute method definitions
26
+ - Move retry count from metadata to result object
27
+ - Exclude non-essential files from gem package
28
+
29
+ ### Removed
30
+ - Remove public `Result#rolled_back!` method to hide internal implementation
31
+
32
+ ## [1.13.0] - 2025-12-23
19
33
 
20
34
  ### Added
21
- - Added active record database transaction example
22
- - Added Sentry error tracking example
23
- - Added Redis idempotency example
24
- - Added Flipper feature flag example
25
-
26
- ### Updated
27
- - Remove `handle_*` methods and provide `on(*states_or_statuses)` method for more flexibility
28
- - Optimize logging ancestor lookup
29
- - Use chop instead of range for better string performance
30
- - Update boolean coercion `TRUTHY` and `FALSEY` regexp to be case insensitive
31
- - Improve YARD documentation with `yard-lint`
35
+ - Add rollback tracking and logging for task execution
36
+ - Add `dry_run` execution option with inheritance support for nested tasks
37
+ - Add `Context#delete` alias for `Context#delete!`
38
+ - Add `Context#merge` alias for `Context#merge!`
39
+
40
+ ## [1.12.0] - 2025-12-18
41
+
42
+ ### Changed
43
+ - Optimize logging ancestor chain lookup performance
44
+ - Use `String#chop` instead of range indexing for improved string performance
45
+ - Make boolean coercion `TRUTHY` and `FALSEY` patterns case-insensitive
46
+ - Enhance YARD documentation using `yard-lint` validation
47
+
48
+ ### Removed
49
+ - Remove `handle_*` callback methods in favor of `on(*states_or_statuses)` for flexible state handling
32
50
 
33
51
  ## [1.11.0] - 2025-11-08
34
52
 
35
- ### Updated
36
- - Added conditionally required attributes options
37
- - Update specs to use new `cmdx-rspec` matcher names
53
+ ### Changed
54
+ - Add conditional requirement support for attribute validation
55
+ - Update specs to use new `cmdx-rspec` matcher naming conventions
38
56
 
39
57
  ## [1.10.1] - 2025-11-06
40
58
 
41
59
  ### Added
42
- - Added Sidekiq async integration example
43
- - Added YARDoc documentation to docs site
60
+ - Add YARDoc documentation to documentation site
44
61
 
45
62
  ### Removed
46
- - Removed `Executor#repeator` method (never used)
63
+ - Remove unused `Executor#repeator` method
47
64
 
48
65
  ## [1.10.0] - 2025-10-26
49
66
 
50
67
  ### Added
51
- - Added `rollback` ability to undo on matching status
52
- - Added documentation for retries
53
- - Added Stoplight circuit breaker integration example
68
+ - Add `rollback` capability to undo operations based on status
69
+ - Add retry mechanism documentation
54
70
 
55
- ### Updated
56
- - Updated `retry_jitter` option to support symbol, proc, and callables on top of fixed values
71
+ ### Changed
72
+ - Extend `retry_jitter` option to accept symbols, procs, and callable objects
57
73
 
58
74
  ## [1.9.1] - 2025-10-22
59
75
 
60
76
  ### Added
61
- - Added RBS inlines type signatures
62
- - Added YARDocs for `attr_reader` and `attr_accessor` methods
77
+ - Add RBS inline type signatures
78
+ - Add YARDocs for `attr_reader` and `attr_accessor` methods
63
79
 
64
80
  ## [1.9.0] - 2025-10-21
65
81
 
66
82
  ### Added
67
- - Added `transform` option to attributes
68
- - Added option to output failure backtraces
69
- - Added exception handling for non-bang methods
70
- - Added durability with automatic retries to execution
71
- - Added `to_h` hash coercion support
72
- - Added comprehensive MkDocs configuration with material theme
83
+ - Add `transform` option for attribute value transformations
84
+ - Add optional failure backtrace output
85
+ - Add exception handling for non-bang execution methods
86
+ - Add automatic retry mechanism for execution durability
87
+ - Add `to_h` hash coercion support
88
+ - Add MkDocs configuration with Material theme
73
89
 
74
90
  ### Changed
75
- - Improved performance of task settings setup
76
- - Improved error messages for raised exceptions
77
- - Improved inheritance of parent settings
78
- - Cleaned halt backtrace frames for better readability
91
+ - Improve task settings initialization performance
92
+ - Improve exception error message clarity
93
+ - Improve parent settings inheritance behavior
94
+ - Clean halt backtrace frames for better readability
79
95
 
80
96
  ### Removed
81
- - Removed `Freezer` module and moved logic into executor `freeze_execution!` method
82
- - Removed task parameter from callback signature
83
- - Removed task and workflow arguments from conditional checks
84
- - Removed chain persistence after execution in specs
97
+ - Remove `Freezer` module; consolidate logic into `Executor#freeze_execution!`
98
+ - Remove `task` parameter from callback method signatures
99
+ - Remove `task` and `workflow` arguments from conditional checks
100
+ - Remove chain persistence after execution in specs
85
101
 
86
102
  ## [1.8.0] - 2025-09-22
87
103
 
88
104
  ### Changed
89
- - Generalized locale values for fault `invalid` and `unspecified`
90
- - Nested attribute error messages under `error` key within metadata
91
- - Reordered logstash formatter keys for consistency
92
- - Improved error message for already defined items
93
- - Changed hash coercion for `nil` to return `{}`
105
+ - Generalize locale values for `invalid` and `unspecified` faults
106
+ - Nest attribute error messages under `error` key in metadata
107
+ - Reorder Logstash formatter keys for consistency
108
+ - Improve error messaging for duplicate item definitions
109
+ - Return empty hash `{}` for `nil` hash coercion
94
110
 
95
111
  ## [1.7.5] - 2025-09-10
96
112
 
97
113
  ### Added
98
- - Added `fetch_or_store` method to context
99
- - Added `ctx` alias for context in result
100
- - Added `ok?` alias for `good?` in result
101
- - Added deconstruction values in result
114
+ - Add `Context#fetch_or_store` method for atomic get-or-set operations
115
+ - Add `Result#ctx` alias for `Result#context`
116
+ - Add `Result#ok?` alias for `Result#good?`
117
+ - Add result deconstruction support for pattern matching
102
118
 
103
119
  ## [1.7.4] - 2025-09-03
104
120
 
105
121
  ### Added
106
- - Added errors delegation from result object
107
- - Added `full_messages` and `to_hash` methods to errors
122
+ - Add errors delegation from result object
123
+ - Add `Errors#full_messages` and `Errors#to_hash` methods
108
124
 
109
125
  ## [1.7.3] - 2025-09-03
110
126
 
111
127
  ### Changed
112
- - Changed validation reasons to use generic values
113
- - Moved validation full message string to `:full_message` key within metadata
128
+ - Use generic validation reason values
129
+ - Move validation full message to `:full_message` key in metadata
114
130
 
115
131
  ## [1.7.2] - 2025-09-03
116
132
 
117
133
  ### Changed
118
- - Changed correlation ID to be set before continuing to further steps
134
+ - Set correlation ID before proceeding to subsequent execution steps
119
135
 
120
136
  ## [1.7.1] - 2025-08-26
121
137
 
122
138
  ### Added
123
- - Added result yielding when block is given to `execute` and `execute!` methods
139
+ - Add block yielding support to `execute` and `execute!` methods
124
140
 
125
141
  ## [1.7.0] - 2025-08-25
126
142
 
127
143
  ### Added
128
- - Added workflow generator
144
+ - Add workflow generator
129
145
 
130
146
  ### Changed
131
- - Ported `cmdx-parallel` changes into core
132
- - Ported `cmdx-i18n` changes into core
147
+ - Integrate `cmdx-parallel` functionality into core
148
+ - Integrate `cmdx-i18n` functionality into core
133
149
 
134
150
  ## [1.6.2] - 2025-08-24
135
151
 
136
152
  ### Changed
137
- - Prefixed railtie I18n with `::` for compatibility with `CMDx::I18n`
138
- - Changed to use `cmdx-rspec` for matchers support
153
+ - Prefix railtie I18n with `::` for `CMDx::I18n` compatibility
154
+ - Switch to `cmdx-rspec` for matcher support
139
155
 
140
156
  ## [1.6.1] - 2025-08-23
141
157
 
142
158
  ### Changed
143
- - Changed task results to be logged before freezing
144
- - Renamed `execute_tasks_sequentially` to `execute_tasks_in_sequence`
159
+ - Log task results before freezing execution state
160
+ - Rename `execute_tasks_sequentially` to `execute_tasks_in_sequence`
145
161
 
146
162
  ## [1.6.0] - 2025-08-22
147
163
 
148
164
  ### Added
149
- - Added workflow task `:breakpoints` support
165
+ - Add workflow task `:breakpoints` support
150
166
 
151
167
  ### Changed
152
- - Renamed `Worker` class to `Executor`
153
- - Moved workflow `work` logic into `Pipeline`
168
+ - Rename `Worker` class to `Executor`
169
+ - Extract workflow execution logic into `Pipeline` class
154
170
 
155
171
  ## [1.5.2] - 2025-08-22
156
172
 
157
173
  ### Changed
158
- - Renamed workflow `execution_groups` attribute to `pipeline`
174
+ - Rename workflow `execution_groups` attribute to `pipeline`
159
175
 
160
176
  ## [1.5.1] - 2025-08-21
161
177
 
162
178
  ### Changed
163
- - Prefixed locale I18n with `::` for compatibility with `CMDx::I18n`
164
- - Added safe navigation to length and numeric validators
165
- - Updated railtie file path to point to correct directory
179
+ - Prefix locale I18n with `::` for `CMDx::I18n` compatibility
180
+ - Add safe navigation to length and numeric validators
181
+ - Fix railtie file path to reference correct directory
166
182
 
167
183
  ## [1.5.0] - 2025-08-21
168
184
 
169
185
  ### Changed
170
- - **BREAKING**: Revamped CMDx for improved clarity, transparency, and higher performance
186
+ - **BREAKING**: Complete architecture redesign for improved clarity, transparency, and performance
171
187
 
172
188
  ## [1.1.2] - 2025-07-20
173
189
 
174
190
  ### Changed
175
- - All changes between versions `0.1.0` and `1.1.2` should be reviewed within their respective git tags
191
+ - See git tags for changes between versions 0.1.0 and 1.1.2
176
192
 
177
193
  ## [0.1.0] - 2025-03-07
178
194
 
data/LICENSE.txt CHANGED
@@ -1,21 +1,4 @@
1
- The MIT License (MIT)
1
+ Copyright (c) Drexed
2
2
 
3
- Copyright (c) 2025 Juan Gomez
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
3
+ CMDx is an Open Source project licensed under the terms of the LGPLv3 license.
4
+ Please see <http://www.gnu.org/licenses/lgpl-3.0.html> for license text.
data/README.md CHANGED
@@ -1,16 +1,23 @@
1
1
  <div align="center">
2
- <img src="./src/cmdx-light-logo.png#gh-light-mode-only" width="200" alt="CMDx Logo">
3
- <img src="./src/cmdx-dark-logo.png#gh-dark-mode-only" width="200" alt="CMDx Logo">
2
+ <img src="./src/cmdx-light-logo.png#gh-light-mode-only" width="200" alt="CMDx Light Logo">
3
+ <img src="./src/cmdx-dark-logo.png#gh-dark-mode-only" width="200" alt="CMDx Dark Logo">
4
4
 
5
5
  ---
6
6
 
7
7
  Build business logic that’s powerful, predictable, and maintainable.
8
8
 
9
- [Documentation](https://drexed.github.io/cmdx) · [Changelog](./CHANGELOG.md) · [Report Bug](https://github.com/drexed/cmdx/issues) · [Request Feature](https://github.com/drexed/cmdx/issues) · [LLM.md](https://raw.githubusercontent.com/drexed/cmdx/refs/heads/main/LLM.md)
9
+ [Home](https://drexed.github.io/cmdx) ·
10
+ [Documentation](https://drexed.github.io/cmdx/getting_started) ·
11
+ [Blog](https://drexed.github.io/cmdx/blog) ·
12
+ [Changelog](./CHANGELOG.md) ·
13
+ [Report Bug](https://github.com/drexed/cmdx/issues) ·
14
+ [Request Feature](https://github.com/drexed/cmdx/issues) ·
15
+ [llms.txt](https://drexed.github.io/cmdx/llms.txt) ·
16
+ [llms-full.txt](https://drexed.github.io/cmdx/llms-full.txt)
10
17
 
11
18
  <img alt="Version" src="https://img.shields.io/gem/v/cmdx">
12
19
  <img alt="Build" src="https://github.com/drexed/cmdx/actions/workflows/ci.yml/badge.svg">
13
- <img alt="License" src="https://img.shields.io/github/license/drexed/cmdx">
20
+ <img alt="License" src="https://img.shields.io/badge/license-LGPL%20v3-blue.svg">
14
21
  </div>
15
22
 
16
23
  # CMDx
@@ -18,13 +25,14 @@
18
25
  Say goodbye to messy service objects. CMDx helps you design business logic with clarity and consistency—build faster, debug easier, and ship with confidence.
19
26
 
20
27
  > [!NOTE]
21
- > Documentation reflects the latest code on `main`. For version-specific documentation, please refer to the `docs/` directory within that version's tag.
28
+ > [Documentation](https://drexed.github.io/cmdx/getting_started/) reflects the latest code on `main`. For version-specific documentation, please refer to the `docs/` directory within that version's tag.
22
29
 
23
30
  ## Requirements
24
31
 
25
- - Ruby: MRI 3.1+ or JRuby 9.4+.
32
+ - Ruby: MRI 3.1+ or JRuby 9.4+
33
+ - Dependencies: None
26
34
 
27
- CMDx works with any Ruby framework. Rails support is built-in, but it's framework-agnostic at its core.
35
+ Rails support is built-in, but it's framework-agnostic at its core.
28
36
 
29
37
  ## Installation
30
38
 
@@ -125,4 +133,4 @@ Bug reports and pull requests are welcome at <https://github.com/drexed/cmdx>. W
125
133
 
126
134
  ## License
127
135
 
128
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
136
+ The gem is available as open source under the terms of the [LGPLv3 License](https://www.gnu.org/licenses/lgpl-3.0.html).
@@ -72,6 +72,16 @@ module CMDx
72
72
  # @rbs @types: Array[Class]
73
73
  attr_reader :types
74
74
 
75
+ # Returns the description of the attribute.
76
+ #
77
+ # @return [String] The description of the attribute
78
+ #
79
+ # @example
80
+ # attribute.description # => "The user's name"
81
+ #
82
+ # @rbs @description: String
83
+ attr_reader :description
84
+
75
85
  # Creates a new attribute with the specified name and configuration.
76
86
  #
77
87
  # @param name [Symbol, String] The name of the attribute
@@ -79,6 +89,7 @@ module CMDx
79
89
  # @option options [Attribute] :parent The parent attribute for nested structures
80
90
  # @option options [Boolean] :required Whether the attribute is required (default: false)
81
91
  # @option options [Array<Class>, Class] :types The expected type(s) for the attribute value
92
+ # @option options [String] :description The description of the attribute
82
93
  # @option options [Symbol, String, Proc] :source The source of the attribute value
83
94
  # @option options [Symbol, String] :as The method name to use for this attribute
84
95
  # @option options [Symbol, String, Boolean] :prefix The prefix to add to the method name
@@ -98,6 +109,7 @@ module CMDx
98
109
  @parent = options.delete(:parent)
99
110
  @required = options.delete(:required) || false
100
111
  @types = Array(options.delete(:types) || options.delete(:type))
112
+ @description = options.delete(:description) || options.delete(:desc)
101
113
 
102
114
  @name = name.to_sym
103
115
  @options = options
@@ -235,6 +247,32 @@ module CMDx
235
247
  end
236
248
  end
237
249
 
250
+ # @return [Hash] A hash representation of the attribute
251
+ #
252
+ # @example
253
+ # attribute.to_h # => {
254
+ # name: :user_id,
255
+ # method_name: :current_user_id,
256
+ # description: "The user's name",
257
+ # required: true,
258
+ # types: [:integer],
259
+ # options: {},
260
+ # children: []
261
+ # }
262
+ #
263
+ # @rbs () -> Hash[Symbol, untyped]
264
+ def to_h
265
+ {
266
+ name: name,
267
+ method_name: method_name,
268
+ description: description,
269
+ required: required?,
270
+ types: types,
271
+ options: options.except(:if, :unless),
272
+ children: children.map(&:to_h)
273
+ }
274
+ end
275
+
238
276
  private
239
277
 
240
278
  # Creates nested attributes as children of this attribute.
@@ -312,11 +350,10 @@ module CMDx
312
350
  attribute_value.generate
313
351
  attribute_value.validate
314
352
 
315
- task.instance_eval(<<~RUBY, __FILE__, __LINE__ + 1)
316
- def #{method_name}
317
- attributes[:#{method_name}]
318
- end
319
- RUBY
353
+ name_of_method = method_name
354
+ task.define_singleton_method(name_of_method) do
355
+ attributes[name_of_method]
356
+ end
320
357
  end
321
358
 
322
359
  end
data/lib/cmdx/context.rb CHANGED
@@ -185,6 +185,22 @@ module CMDx
185
185
  end
186
186
  alias delete delete!
187
187
 
188
+ # Clears all key-value pairs from the context.
189
+ #
190
+ # @return [Context] self for method chaining
191
+ #
192
+ # @example
193
+ # context = Context.new(name: "John")
194
+ # context.clear!
195
+ # context.to_h # => {}
196
+ #
197
+ # @rbs () -> self
198
+ def clear!
199
+ table.clear
200
+ self
201
+ end
202
+ alias clear clear!
203
+
188
204
  # Compares this context with another object for equality.
189
205
  #
190
206
  # @param other [Object] the object to compare with
data/lib/cmdx/executor.rb CHANGED
@@ -138,17 +138,17 @@ module CMDx
138
138
  #
139
139
  # @rbs (Exception exception) -> bool
140
140
  def retry_execution?(exception)
141
- available_retries = (task.class.settings[:retries] || 0).to_i
141
+ available_retries = Integer(task.class.settings[:retries] || 0)
142
142
  return false unless available_retries.positive?
143
143
 
144
- current_retries = (result.metadata[:retries] ||= 0).to_i
145
- remaining_retries = available_retries - current_retries
144
+ current_retry = result.retries
145
+ remaining_retries = available_retries - current_retry
146
146
  return false unless remaining_retries.positive?
147
147
 
148
148
  exceptions = Array(task.class.settings[:retry_on] || StandardError)
149
149
  return false unless exceptions.any? { |e| exception.class <= e }
150
150
 
151
- result.metadata[:retries] += 1
151
+ result.retries += 1
152
152
 
153
153
  task.logger.warn do
154
154
  reason = "[#{exception.class}] #{exception.message}"
@@ -158,13 +158,13 @@ module CMDx
158
158
  jitter = task.class.settings[:retry_jitter]
159
159
  jitter =
160
160
  if jitter.is_a?(Symbol)
161
- task.send(jitter, current_retries)
161
+ task.send(jitter, current_retry)
162
162
  elsif jitter.is_a?(Proc)
163
- task.instance_exec(current_retries, &jitter)
163
+ task.instance_exec(current_retry, &jitter)
164
164
  elsif jitter.respond_to?(:call)
165
- jitter.call(task, current_retries)
165
+ jitter.call(task, current_retry)
166
166
  else
167
- jitter.to_f * current_retries
167
+ jitter.to_f * current_retry
168
168
  end
169
169
 
170
170
  sleep(jitter) if jitter.positive?
@@ -320,7 +320,7 @@ module CMDx
320
320
  statuses = Array(statuses).map(&:to_s).uniq
321
321
  return unless statuses.include?(result.status)
322
322
 
323
- result.rolled_back!
323
+ result.rolled_back = true
324
324
  task.rollback
325
325
  end
326
326
 
data/lib/cmdx/result.rb CHANGED
@@ -70,7 +70,7 @@ module CMDx
70
70
  # @return [Hash{Symbol => Object}] Metadata hash
71
71
  #
72
72
  # @example
73
- # result.metadata # => { duration: 1.5, retries: 2 }
73
+ # result.metadata # => { duration: 1.5, code: 200, message: "Success" }
74
74
  #
75
75
  # @rbs @metadata: Hash[Symbol, untyped]
76
76
  attr_reader :metadata
@@ -95,6 +95,26 @@ module CMDx
95
95
  # @rbs @cause: (Exception | nil)
96
96
  attr_reader :cause
97
97
 
98
+ # Returns the number of retries attempted.
99
+ #
100
+ # @return [Integer] The number of retries attempted
101
+ #
102
+ # @example
103
+ # result.retries # => 2
104
+ #
105
+ # @rbs @retries: Integer
106
+ attr_accessor :retries
107
+
108
+ # Returns whether the result has been rolled back.
109
+ #
110
+ # @return [Boolean] Whether the result has been rolled back
111
+ #
112
+ # @example
113
+ # result.rolled_back? # => true
114
+ #
115
+ # @rbs @rolled_back: bool
116
+ attr_accessor :rolled_back
117
+
98
118
  def_delegators :task, :context, :chain, :errors, :dry_run?
99
119
  alias ctx context
100
120
 
@@ -118,6 +138,7 @@ module CMDx
118
138
  @metadata = {}
119
139
  @reason = nil
120
140
  @cause = nil
141
+ @retries = 0
121
142
  @rolled_back = false
122
143
  end
123
144
 
@@ -420,15 +441,14 @@ module CMDx
420
441
  failed? && !caused_failure?
421
442
  end
422
443
 
423
- # @return [void]
444
+ # @return [Boolean] Whether the result has been retried
424
445
  #
425
446
  # @example
426
- # result.rolled_back!
427
- # result.rolled_back? # => true
447
+ # result.retried? # => true
428
448
  #
429
- # @rbs () -> void
430
- def rolled_back!
431
- @rolled_back = true
449
+ # @rbs () -> bool
450
+ def retried?
451
+ retries.positive?
432
452
  end
433
453
 
434
454
  # @return [Boolean] Whether the result has been rolled back
data/lib/cmdx/task.rb CHANGED
@@ -217,6 +217,25 @@ module CMDx
217
217
  end
218
218
  alias remove_attribute remove_attributes
219
219
 
220
+ # @return [Hash] Hash of attribute names to their configurations
221
+ #
222
+ # @example
223
+ # MyTask.attributes_schema #=> {
224
+ # user_id: { name: :user_id, method_name: :user_id, required: true, types: [:integer], options: {}, children: [] },
225
+ # email: { name: :email, method_name: :email, required: false, types: [:string], options: { default: nil }, children: [] },
226
+ # profile: { name: :profile, method_name: :profile, required: false, types: [:hash], options: {}, children: [
227
+ # { name: :bio, method_name: :bio, required: false, types: [:string], options: {}, children: [] },
228
+ # { name: :name, method_name: :name, required: true, types: [:string], options: {}, children: [] }
229
+ # ] }
230
+ # }
231
+ #
232
+ # @rbs () -> Hash[Symbol, Hash[Symbol, untyped]]
233
+ def attributes_schema
234
+ Array(settings[:attributes]).each_with_object({}) do |attr, schema|
235
+ schema[attr.method_name] = attr.to_h
236
+ end
237
+ end
238
+
220
239
  CallbackRegistry::TYPES.each do |callback|
221
240
  # @param callables [Array] Callable objects to register as callbacks
222
241
  # @param options [Hash] Options for the callback registration
@@ -29,6 +29,7 @@ module CMDx
29
29
  # @rbs (?Hash[Symbol, Class]? registry) -> void
30
30
  def initialize(registry = nil)
31
31
  @registry = registry || {
32
+ absence: Validators::Absence,
32
33
  exclusion: Validators::Exclusion,
33
34
  format: Validators::Format,
34
35
  inclusion: Validators::Inclusion,