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
@@ -1,283 +0,0 @@
1
- # Attributes - Definitions
2
-
3
- Attributes define your task's interface with automatic validation, type coercion, and accessor generation. They're the contract between callers and your business logic.
4
-
5
- ## Declarations
6
-
7
- !!! warning "Important"
8
-
9
- Attributes are order-dependent, so if you need to reference them as a source or use them in conditions, make sure they’re defined in the correct order.
10
-
11
- !!! tip
12
-
13
- Prefer using the `required` and `optional` alias for `attributes` for brevity and to clearly signal intent.
14
-
15
- ### Optional
16
-
17
- Optional attributes return `nil` when not provided.
18
-
19
- ```ruby
20
- class ScheduleEvent < CMDx::Task
21
- attribute :title
22
- attributes :duration, :location
23
-
24
- # Alias for attributes (preferred)
25
- optional :description
26
- optional :visibility, :attendees
27
-
28
- def work
29
- title #=> "Team Standup"
30
- duration #=> 30
31
- location #=> nil
32
- description #=> nil
33
- visibility #=> nil
34
- attendees #=> ["alice@company.com", "bob@company.com"]
35
- end
36
- end
37
-
38
- # Attributes passed as keyword arguments
39
- ScheduleEvent.execute(
40
- title: "Team Standup",
41
- duration: 30,
42
- attendees: ["alice@company.com", "bob@company.com"]
43
- )
44
- ```
45
-
46
- ### Required
47
-
48
- Required attributes must be provided in call arguments or task execution will fail.
49
-
50
- ```ruby
51
- class PublishArticle < CMDx::Task
52
- attribute :title, required: true
53
- attributes :content, :author_id, required: true
54
-
55
- # Alias for attributes => required: true (preferred)
56
- required :category
57
- required :status, :tags
58
-
59
- # Conditionally required
60
- required :publisher, if: :magazine?
61
- attribute :approver, required: true, unless: proc { status == :published }
62
-
63
- def work
64
- title #=> "Getting Started with Ruby"
65
- content #=> "This is a comprehensive guide..."
66
- author_id #=> 42
67
- category #=> "programming"
68
- status #=> :published
69
- tags #=> ["ruby", "beginner"]
70
- publisher #=> "Eastbay"
71
- approver #=> #<Editor ...>
72
- end
73
-
74
- private
75
-
76
- def magazine?
77
- context.title.ends_with?("[M]")
78
- end
79
- end
80
- ```
81
-
82
- !!! note
83
-
84
- When a required attribute's condition evaluates to `false`, the attribute behaves as optional. All other attribute features such as coercions, validations, defaults, and transformations still apply normally.
85
-
86
- ## Sources
87
-
88
- Attributes read from any accessible object—not just context. Use sources to pull data from models, services, or any callable:
89
-
90
- ### Context
91
-
92
- ```ruby
93
- class BackupDatabase < CMDx::Task
94
- # Default source is :context
95
- required :database_name
96
- optional :compression_level
97
-
98
- # Explicitly specify context source
99
- attribute :backup_path, source: :context
100
-
101
- def work
102
- database_name #=> context.database_name
103
- backup_path #=> context.backup_path
104
- compression_level #=> context.compression_level
105
- end
106
- end
107
- ```
108
-
109
- ### Symbol References
110
-
111
- Reference instance methods by symbol for dynamic source values:
112
-
113
- ```ruby
114
- class BackupDatabase < CMDx::Task
115
- attributes :host, :credentials, source: :database_config
116
-
117
- # Access from declared attributes
118
- attribute :connection_string, source: :credentials
119
-
120
- def work
121
- # Your logic here...
122
- end
123
-
124
- private
125
-
126
- def database_config
127
- @database_config ||= DatabaseConfig.find(context.database_name)
128
- end
129
- end
130
- ```
131
-
132
- ### Proc or Lambda
133
-
134
- Use anonymous functions for dynamic source values:
135
-
136
- ```ruby
137
- class BackupDatabase < CMDx::Task
138
- # Proc
139
- attribute :timestamp, source: proc { Time.current }
140
-
141
- # Lambda
142
- attribute :server, source: -> { Current.server }
143
- end
144
- ```
145
-
146
- ### Class or Module
147
-
148
- For complex source logic, use classes or modules:
149
-
150
- ```ruby
151
- class DatabaseResolver
152
- def self.call(task)
153
- Database.find(task.context.database_name)
154
- end
155
- end
156
-
157
- class BackupDatabase < CMDx::Task
158
- # Class or Module
159
- attribute :schema, source: DatabaseResolver
160
-
161
- # Instance
162
- attribute :metadata, source: DatabaseResolver.new
163
- end
164
- ```
165
-
166
- ## Nesting
167
-
168
- Build complex structures with nested attributes. Children inherit their parent as source and support all attribute options:
169
-
170
- !!! note
171
-
172
- Nested attributes support all features: naming, coercions, validations, defaults, and more.
173
-
174
- ```ruby
175
- class ConfigureServer < CMDx::Task
176
- # Required parent with required children
177
- required :network_config do
178
- required :hostname, :port, :protocol, :subnet
179
- optional :load_balancer
180
- attribute :firewall_rules
181
- end
182
-
183
- # Optional parent with conditional children
184
- optional :ssl_config do
185
- required :certificate_path, :private_key # Only required if ssl_config provided
186
- optional :enable_http2, prefix: true
187
- end
188
-
189
- # Multi-level nesting
190
- attribute :monitoring do
191
- required :provider
192
-
193
- optional :alerting do
194
- required :threshold_percentage
195
- optional :notification_channel
196
- end
197
- end
198
-
199
- def work
200
- network_config #=> { hostname: "api.company.com" ... }
201
- hostname #=> "api.company.com"
202
- load_balancer #=> nil
203
- end
204
- end
205
-
206
- ConfigureServer.execute(
207
- server_id: "srv-001",
208
- network_config: {
209
- hostname: "api.company.com",
210
- port: 443,
211
- protocol: "https",
212
- subnet: "10.0.1.0/24",
213
- firewall_rules: "allow_web_traffic"
214
- },
215
- monitoring: {
216
- provider: "datadog",
217
- alerting: {
218
- threshold_percentage: 85.0,
219
- notification_channel: "slack"
220
- }
221
- }
222
- )
223
- ```
224
-
225
- !!! warning "Important"
226
-
227
- Child requirements only apply when the parent is provided—perfect for optional structures.
228
-
229
- ## Error Handling
230
-
231
- Validation failures provide detailed, structured error messages:
232
-
233
- !!! note
234
-
235
- Nested attributes are only validated when their parent is present and valid.
236
-
237
- ```ruby
238
- class ConfigureServer < CMDx::Task
239
- required :server_id, :environment
240
- required :network_config do
241
- required :hostname, :port
242
- end
243
-
244
- def work
245
- # Your logic here...
246
- end
247
- end
248
-
249
- # Missing required top-level attributes
250
- result = ConfigureServer.execute(server_id: "srv-001")
251
-
252
- result.state #=> "interrupted"
253
- result.status #=> "failed"
254
- result.reason #=> "Invalid"
255
- result.metadata #=> {
256
- # errors: {
257
- # full_message: "environment is required. network_config is required.",
258
- # messages: {
259
- # environment: ["is required"],
260
- # network_config: ["is required"]
261
- # }
262
- # }
263
- # }
264
-
265
- # Missing required nested attributes
266
- result = ConfigureServer.execute(
267
- server_id: "srv-001",
268
- environment: "production",
269
- network_config: { hostname: "api.company.com" } # Missing port
270
- )
271
-
272
- result.state #=> "interrupted"
273
- result.status #=> "failed"
274
- result.reason #=> "Invalid"
275
- result.metadata #=> {
276
- # errors: {
277
- # full_message: "port is required.",
278
- # messages: {
279
- # port: ["is required"]
280
- # }
281
- # }
282
- # }
283
- ```
@@ -1,68 +0,0 @@
1
- # Attributes - Naming
2
-
3
- Customize accessor method names to avoid conflicts and improve clarity. Affixing changes only the generated methods—not the original attribute names.
4
-
5
- !!! note
6
-
7
- Use naming when attributes conflict with existing methods or need better clarity in your code.
8
-
9
- ## Prefix
10
-
11
- Adds a prefix to the generated accessor method name.
12
-
13
- ```ruby
14
- class GenerateReport < CMDx::Task
15
- # Dynamic from attribute source
16
- attribute :template, prefix: true
17
-
18
- # Static
19
- attribute :format, prefix: "report_"
20
-
21
- def work
22
- context_template #=> "monthly_sales"
23
- report_format #=> "pdf"
24
- end
25
- end
26
-
27
- # Attributes passed as original attribute names
28
- GenerateReport.execute(template: "monthly_sales", format: "pdf")
29
- ```
30
-
31
- ## Suffix
32
-
33
- Adds a suffix to the generated accessor method name.
34
-
35
- ```ruby
36
- class DeployApplication < CMDx::Task
37
- # Dynamic from attribute source
38
- attribute :branch, suffix: true
39
-
40
- # Static
41
- attribute :version, suffix: "_tag"
42
-
43
- def work
44
- branch_context #=> "main"
45
- version_tag #=> "v1.2.3"
46
- end
47
- end
48
-
49
- # Attributes passed as original attribute names
50
- DeployApplication.execute(branch: "main", version: "v1.2.3")
51
- ```
52
-
53
- ## As
54
-
55
- Completely renames the generated accessor method.
56
-
57
- ```ruby
58
- class ScheduleMaintenance < CMDx::Task
59
- attribute :scheduled_at, as: :when
60
-
61
- def work
62
- when #=> <DateTime>
63
- end
64
- end
65
-
66
- # Attributes passed as original attribute names
67
- ScheduleMaintenance.execute(scheduled_at: DateTime.new(2024, 12, 15, 2, 0, 0))
68
- ```
@@ -1,63 +0,0 @@
1
- # Attributes - Transformations
2
-
3
- Modify attribute values after coercion but before validation. Perfect for normalization, formatting, and data cleanup.
4
-
5
- ## Declarations
6
-
7
- ### Symbol References
8
-
9
- Reference instance methods by symbol for dynamic value transformations:
10
-
11
- ```ruby
12
- class ProcessAnalytics < CMDx::Task
13
- attribute :options, transform: :compact_blank
14
- end
15
- ```
16
-
17
- ### Proc or Lambda
18
-
19
- Use anonymous functions for dynamic value transformations:
20
-
21
- ```ruby
22
- class CacheContent < CMDx::Task
23
- # Proc
24
- attribute :expire_hours, transform: proc { |v| v * 2 }
25
-
26
- # Lambda
27
- attribute :compression, transform: ->(v) { v.to_s.upcase.strip[0..2] }
28
- end
29
- ```
30
-
31
- ### Class or Module
32
-
33
- Use any object that responds to `call` for reusable transformation logic:
34
-
35
- ```ruby
36
- class EmailNormalizer
37
- def call(value)
38
- value.to_s.downcase.strip
39
- end
40
- end
41
-
42
- class ProcessContacts < CMDx::Task
43
- # Class or Module
44
- attribute :email, transform: EmailNormalizer
45
-
46
- # Instance
47
- attribute :email, transform: EmailNormalizer.new
48
- end
49
- ```
50
-
51
- ## Validations
52
-
53
- Validations run on transformed values, ensuring data consistency:
54
-
55
- ```ruby
56
- class ScheduleBackup < CMDx::Task
57
- # Coercions
58
- attribute :retention_days, type: :integer, transform: proc { |v| v.clamp(1, 5) }
59
-
60
- # Validations
61
- optional :frequency, transform: :downcase, inclusion: { in: %w[hourly daily weekly monthly] }
62
- end
63
- ```