cmdx 0.4.0 → 1.0.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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/rules/cursor-instructions.mdc +6 -0
  4. data/.rubocop.yml +16 -1
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +42 -1
  7. data/README.md +72 -25
  8. data/docs/ai_prompts.md +309 -0
  9. data/docs/basics/call.md +225 -14
  10. data/docs/basics/chain.md +271 -0
  11. data/docs/basics/context.md +232 -33
  12. data/docs/basics/setup.md +76 -12
  13. data/docs/callbacks.md +273 -0
  14. data/docs/configuration.md +158 -28
  15. data/docs/getting_started.md +134 -22
  16. data/docs/interruptions/exceptions.md +189 -11
  17. data/docs/interruptions/faults.md +187 -44
  18. data/docs/interruptions/halt.md +179 -35
  19. data/docs/logging.md +194 -53
  20. data/docs/middlewares.md +735 -0
  21. data/docs/outcomes/result.md +296 -10
  22. data/docs/outcomes/states.md +212 -19
  23. data/docs/outcomes/statuses.md +284 -18
  24. data/docs/parameters/coercions.md +402 -29
  25. data/docs/parameters/defaults.md +249 -25
  26. data/docs/parameters/definitions.md +238 -72
  27. data/docs/parameters/namespacing.md +250 -27
  28. data/docs/parameters/validations.md +193 -168
  29. data/docs/testing.md +550 -0
  30. data/docs/tips_and_tricks.md +95 -43
  31. data/docs/workflows.md +319 -0
  32. data/lib/cmdx/.DS_Store +0 -0
  33. data/lib/cmdx/callback.rb +69 -0
  34. data/lib/cmdx/callback_registry.rb +106 -0
  35. data/lib/cmdx/chain.rb +190 -0
  36. data/lib/cmdx/chain_inspector.rb +149 -0
  37. data/lib/cmdx/chain_serializer.rb +175 -0
  38. data/lib/cmdx/coercions/array.rb +37 -0
  39. data/lib/cmdx/coercions/big_decimal.rb +33 -0
  40. data/lib/cmdx/coercions/boolean.rb +41 -1
  41. data/lib/cmdx/coercions/complex.rb +31 -0
  42. data/lib/cmdx/coercions/date.rb +39 -0
  43. data/lib/cmdx/coercions/date_time.rb +39 -0
  44. data/lib/cmdx/coercions/float.rb +31 -0
  45. data/lib/cmdx/coercions/hash.rb +42 -0
  46. data/lib/cmdx/coercions/integer.rb +32 -0
  47. data/lib/cmdx/coercions/rational.rb +31 -0
  48. data/lib/cmdx/coercions/string.rb +31 -0
  49. data/lib/cmdx/coercions/time.rb +39 -0
  50. data/lib/cmdx/coercions/virtual.rb +31 -0
  51. data/lib/cmdx/configuration.rb +217 -9
  52. data/lib/cmdx/context.rb +173 -2
  53. data/lib/cmdx/core_ext/hash.rb +72 -0
  54. data/lib/cmdx/core_ext/module.rb +94 -0
  55. data/lib/cmdx/core_ext/object.rb +105 -0
  56. data/lib/cmdx/correlator.rb +217 -0
  57. data/lib/cmdx/error.rb +210 -8
  58. data/lib/cmdx/errors.rb +256 -1
  59. data/lib/cmdx/fault.rb +177 -2
  60. data/lib/cmdx/faults.rb +158 -2
  61. data/lib/cmdx/immutator.rb +121 -2
  62. data/lib/cmdx/lazy_struct.rb +261 -18
  63. data/lib/cmdx/log_formatters/json.rb +46 -0
  64. data/lib/cmdx/log_formatters/key_value.rb +46 -0
  65. data/lib/cmdx/log_formatters/line.rb +54 -0
  66. data/lib/cmdx/log_formatters/logstash.rb +64 -0
  67. data/lib/cmdx/log_formatters/pretty_json.rb +57 -0
  68. data/lib/cmdx/log_formatters/pretty_key_value.rb +51 -0
  69. data/lib/cmdx/log_formatters/pretty_line.rb +60 -0
  70. data/lib/cmdx/log_formatters/raw.rb +54 -0
  71. data/lib/cmdx/logger.rb +85 -0
  72. data/lib/cmdx/logger_ansi.rb +93 -7
  73. data/lib/cmdx/logger_serializer.rb +116 -0
  74. data/lib/cmdx/middleware.rb +74 -0
  75. data/lib/cmdx/middleware_registry.rb +106 -0
  76. data/lib/cmdx/middlewares/correlate.rb +266 -0
  77. data/lib/cmdx/middlewares/timeout.rb +232 -0
  78. data/lib/cmdx/parameter.rb +228 -1
  79. data/lib/cmdx/parameter_inspector.rb +61 -0
  80. data/lib/cmdx/parameter_registry.rb +125 -0
  81. data/lib/cmdx/parameter_serializer.rb +83 -0
  82. data/lib/cmdx/parameter_validator.rb +62 -0
  83. data/lib/cmdx/parameter_value.rb +109 -1
  84. data/lib/cmdx/parameters_inspector.rb +59 -0
  85. data/lib/cmdx/parameters_serializer.rb +102 -0
  86. data/lib/cmdx/railtie.rb +123 -3
  87. data/lib/cmdx/result.rb +399 -20
  88. data/lib/cmdx/result_ansi.rb +105 -9
  89. data/lib/cmdx/result_inspector.rb +76 -0
  90. data/lib/cmdx/result_logger.rb +90 -3
  91. data/lib/cmdx/result_serializer.rb +137 -0
  92. data/lib/cmdx/rspec/result_matchers.rb +917 -0
  93. data/lib/cmdx/rspec/task_matchers.rb +570 -0
  94. data/lib/cmdx/task.rb +409 -34
  95. data/lib/cmdx/task_serializer.rb +74 -2
  96. data/lib/cmdx/utils/ansi_color.rb +95 -0
  97. data/lib/cmdx/utils/log_timestamp.rb +48 -0
  98. data/lib/cmdx/utils/monotonic_runtime.rb +71 -4
  99. data/lib/cmdx/utils/name_affix.rb +78 -0
  100. data/lib/cmdx/validators/custom.rb +82 -0
  101. data/lib/cmdx/validators/exclusion.rb +94 -0
  102. data/lib/cmdx/validators/format.rb +102 -8
  103. data/lib/cmdx/validators/inclusion.rb +104 -0
  104. data/lib/cmdx/validators/length.rb +128 -0
  105. data/lib/cmdx/validators/numeric.rb +128 -0
  106. data/lib/cmdx/validators/presence.rb +93 -7
  107. data/lib/cmdx/version.rb +7 -1
  108. data/lib/cmdx/workflow.rb +394 -0
  109. data/lib/cmdx.rb +25 -64
  110. data/lib/generators/cmdx/install_generator.rb +37 -1
  111. data/lib/generators/cmdx/task_generator.rb +69 -1
  112. data/lib/generators/cmdx/templates/install.rb +8 -12
  113. data/lib/generators/cmdx/workflow_generator.rb +109 -0
  114. metadata +54 -15
  115. data/docs/basics/run.md +0 -34
  116. data/docs/batch.md +0 -53
  117. data/docs/example.md +0 -82
  118. data/docs/hooks.md +0 -59
  119. data/lib/cmdx/batch.rb +0 -43
  120. data/lib/cmdx/parameters.rb +0 -34
  121. data/lib/cmdx/run.rb +0 -38
  122. data/lib/cmdx/run_inspector.rb +0 -26
  123. data/lib/cmdx/run_serializer.rb +0 -16
  124. data/lib/cmdx/task_hook.rb +0 -18
  125. data/lib/generators/cmdx/batch_generator.rb +0 -30
  126. /data/lib/generators/cmdx/templates/{batch.rb.tt → workflow.rb.tt} +0 -0
@@ -1,57 +1,280 @@
1
1
  # Parameters - Namespacing
2
2
 
3
- Parameters can have the delegated method prefixed and/or suffixed to
4
- prevent clashing where the source object have methods with the same name.
3
+ Parameter namespacing provides method name customization to prevent conflicts
4
+ and enable flexible parameter access patterns. When parameters share names with
5
+ existing methods or when multiple parameters from different sources have the
6
+ same name, namespacing ensures clean method resolution within tasks.
5
7
 
6
- `:prefix` and `:suffix` can be used independently or both at the same time.
8
+ ## Table of Contents
7
9
 
8
- ## With fixed value
10
+ - [Namespacing Fundamentals](#namespacing-fundamentals)
11
+ - [Fixed Value Namespacing](#fixed-value-namespacing)
12
+ - [Dynamic Source-Based Namespacing](#dynamic-source-based-namespacing)
13
+ - [Conflict Resolution](#conflict-resolution)
14
+ - [Advanced Namespacing Patterns](#advanced-namespacing-patterns)
15
+ - [Error Handling with Namespacing](#error-handling-with-namespacing)
16
+
17
+ ## Namespacing Fundamentals
18
+
19
+ > [!IMPORTANT]
20
+ > The `:prefix` and `:suffix` options modify only the generated accessor method names while preserving the original parameter names for call arguments.
21
+
22
+ This separation allows for flexible method naming without affecting the task interface.
23
+
24
+ ### Fixed Value Namespacing
25
+
26
+ Use string or symbol values to add consistent prefixes or suffixes to parameter
27
+ method names:
9
28
 
10
29
  ```ruby
11
- class DetermineBoxSizeTask < CMDx::Task
30
+ class CreateOrderTask < CMDx::Task
31
+
32
+ # Fixed prefix for shipping dimensions
33
+ required :width, prefix: "shipping_"
34
+ required :height, prefix: "shipping_"
12
35
 
13
- required :width, prefix: :before_
14
- required :height, suffix: "_after"
36
+ # Fixed suffix for user contact info
37
+ required :email, suffix: "_contact"
38
+ required :phone, suffix: "_contact"
39
+
40
+ # Combined prefix and suffix
41
+ required :weight, prefix: "item_", suffix: "_kg"
15
42
 
16
43
  def call
17
- before_width #=> 1
18
- height_after #=> 2
44
+ # Generated method names with namespacing
45
+ shipping_width #=> accesses width parameter
46
+ shipping_height #=> accesses height parameter
47
+ email_contact #=> accesses email parameter
48
+ phone_contact #=> accesses phone parameter
49
+ item_weight_kg #=> accesses weight parameter
19
50
  end
20
51
 
21
52
  end
22
53
 
23
- # Call arguments match the parameter names
24
- DetermineBoxSizeTask.call(width: 1, height: 2)
54
+ # Call arguments use original parameter names
55
+ CreateOrderTask.call(
56
+ width: 10,
57
+ height: 20,
58
+ email: "customer@example.com",
59
+ phone: "555-1234",
60
+ weight: 2.5
61
+ )
25
62
  ```
26
63
 
27
- ## With source name
64
+ ### Dynamic Source-Based Namespacing
65
+
66
+ Use `true` value to automatically generate prefixes or suffixes based on the
67
+ parameter source name:
28
68
 
29
69
  ```ruby
30
- class DetermineBoxSizeTask < CMDx::Task
70
+ class ProcessUserRegistrationTask < CMDx::Task
71
+
72
+ # Automatic prefix from default source (:context)
73
+ required :user_id, prefix: true # Generates: context_user_id
31
74
 
32
- # Default (:context) as source
33
- optional :height, prefix: true
75
+ # Automatic suffix from custom source
76
+ required :name, source: :profile, suffix: true # Generates: name_profile
34
77
 
35
- # Custom source
36
- optional :width, source: :account, suffix: true
78
+ # Combined automatic namespacing
79
+ required :email, source: :account, prefix: true, suffix: true # Generates: account_email_account
37
80
 
38
81
  def call
39
- context_height #=> 1
40
- width_account #=> 2
82
+ context_user_id #=> accesses context.user_id
83
+ name_profile #=> accesses profile.name
84
+ account_email_account #=> accesses account.email
41
85
  end
42
86
 
43
- end
87
+ private
88
+
89
+ def profile
90
+ @profile ||= User.find(context.user_id).profile
91
+ end
44
92
 
45
- # Call arguments match the parameter names
46
- account = Account.new(width: 2)
47
- DetermineBoxSizeTask.call(height: 1, account: account)
93
+ def account
94
+ @account ||= User.find(context.user_id).account
95
+ end
96
+
97
+ end
48
98
  ```
49
99
 
50
100
  > [!NOTE]
51
- > `:prefix` or `:suffix` with a custom source and a fixed value
52
- > will always return the fixed value without the source.
101
+ > Call arguments always use original parameter names regardless of namespacing configuration.
102
+
103
+ ## Conflict Resolution
104
+
105
+ Namespacing is essential when dealing with method name conflicts or when
106
+ accessing multiple objects with similar attribute names:
107
+
108
+ ### Method Name Conflicts
109
+
110
+ ```ruby
111
+ class UpdateUserProfileTask < CMDx::Task
112
+
113
+ # Avoid conflict with Ruby's built-in 'name' method
114
+ required :name, prefix: "user_"
115
+
116
+ # Avoid conflict with custom private methods
117
+ required :status, suffix: "_param"
118
+
119
+ def call
120
+ user_name #=> parameter value, not Ruby's Object#name
121
+ status_param #=> parameter value, not custom status method
122
+ end
123
+
124
+ private
125
+
126
+ def status
127
+ "processing" # Custom method that would conflict without suffix
128
+ end
129
+
130
+ end
131
+ ```
132
+
133
+ ### Multiple Source Disambiguation
134
+
135
+ ```ruby
136
+ class GenerateInvoiceTask < CMDx::Task
137
+
138
+ # Customer information
139
+ required :name, source: :customer, prefix: "customer_"
140
+ required :email, source: :customer, prefix: "customer_"
141
+
142
+ # Company information
143
+ required :name, source: :company, prefix: "company_"
144
+ required :email, source: :company, prefix: "company_"
145
+
146
+ # Order information
147
+ required :total, source: :order, suffix: "_amount"
148
+ required :status, source: :order, suffix: "_state"
149
+
150
+ def call
151
+ # Clear disambiguation of same-named attributes
152
+ customer_name #=> customer.name
153
+ company_name #=> company.name
154
+ customer_email #=> customer.email
155
+ company_email #=> company.email
156
+ total_amount #=> order.total
157
+ status_state #=> order.status
158
+ end
159
+
160
+ private
161
+
162
+ def customer
163
+ @customer ||= Customer.find(context.customer_id)
164
+ end
165
+
166
+ def company
167
+ @company ||= Company.find(context.company_id)
168
+ end
169
+
170
+ def order
171
+ @order ||= Order.find(context.order_id)
172
+ end
173
+
174
+ end
175
+ ```
176
+
177
+ ## Advanced Namespacing Patterns
178
+
179
+ ### Hierarchical Namespacing
180
+
181
+ Combine namespacing with nested parameters for complex data structures:
182
+
183
+ ```ruby
184
+ class CreateShipmentTask < CMDx::Task
185
+
186
+ # Origin address with prefix
187
+ required :origin_address, source: :shipment, prefix: "from_" do
188
+ required :street, :city, :state, :zip
189
+ end
190
+
191
+ # Destination address with suffix
192
+ required :destination_address, source: :shipment, suffix: "_to" do
193
+ required :street, :city, :state, :zip
194
+ end
195
+
196
+ def call
197
+ from_origin_address #=> shipment.origin_address
198
+ destination_address_to #=> shipment.destination_address
199
+
200
+ # Nested parameters access depends on current context
201
+ street #=> current address context street
202
+ city #=> current address context city
203
+ end
204
+
205
+ private
206
+
207
+ def shipment
208
+ @shipment ||= Shipment.find(context.shipment_id)
209
+ end
210
+
211
+ end
212
+ ```
213
+
214
+ ### Conditional Namespacing
215
+
216
+ Apply namespacing based on runtime conditions:
217
+
218
+ ```ruby
219
+ class ProcessPaymentTask < CMDx::Task
220
+
221
+ # Different namespacing based on payment type
222
+ required :reference_id,
223
+ prefix: -> { context.payment_type == "credit_card" ? "card_" : "bank_" }
224
+
225
+ def call
226
+ # Method names determined at runtime
227
+ if context.payment_type == "credit_card"
228
+ card_reference_id #=> accesses reference_id parameter
229
+ else
230
+ bank_reference_id #=> accesses reference_id parameter
231
+ end
232
+ end
233
+
234
+ end
235
+ ```
236
+
237
+ ## Error Handling with Namespacing
238
+
239
+ ```ruby
240
+ class ValidateUserDataTask < CMDx::Task
241
+
242
+ required :email,
243
+ prefix: "user_",
244
+ type: :string,
245
+ format: { with: /@/ }
246
+
247
+ required :age,
248
+ suffix: "_years",
249
+ type: :integer,
250
+ numeric: { min: 18 }
251
+
252
+ def call
253
+ # Access via namespaced methods
254
+ user_email #=> validated email
255
+ age_years #=> validated age
256
+ end
257
+
258
+ end
259
+
260
+ # Invalid parameters
261
+ result = ValidateUserDataTask.call(
262
+ email: "invalid-email",
263
+ age: "not-a-number"
264
+ )
265
+
266
+ result.failed? #=> true
267
+ result.metadata
268
+ #=> {
269
+ # reason: "email format is not valid. age could not coerce into an integer.",
270
+ # messages: {
271
+ # user_email: ["format is not valid"],
272
+ # age_years: ["could not coerce into an integer"]
273
+ # }
274
+ # }
275
+ ```
53
276
 
54
277
  ---
55
278
 
56
- - **Prev:** [Definitions](https://github.com/drexed/cmdx/blob/main/docs/parameters/definitions.md)
57
- - **Next:** [Coercions](https://github.com/drexed/cmdx/blob/main/docs/parameters/coercions.md)
279
+ - **Prev:** [Parameters - Definitions](definitions.md)
280
+ - **Next:** [Parameters - Coercions](coercions.md)