cmdx 1.0.1 → 1.1.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.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +21 -0
  4. data/.cursor/prompts/yardoc.md +13 -0
  5. data/.rubocop.yml +2 -0
  6. data/CHANGELOG.md +29 -3
  7. data/README.md +2 -1
  8. data/docs/ai_prompts.md +269 -195
  9. data/docs/basics/call.md +126 -60
  10. data/docs/basics/chain.md +190 -160
  11. data/docs/basics/context.md +242 -154
  12. data/docs/basics/setup.md +302 -32
  13. data/docs/callbacks.md +382 -119
  14. data/docs/configuration.md +211 -49
  15. data/docs/deprecation.md +245 -0
  16. data/docs/getting_started.md +161 -39
  17. data/docs/internationalization.md +590 -70
  18. data/docs/interruptions/exceptions.md +135 -118
  19. data/docs/interruptions/faults.md +152 -127
  20. data/docs/interruptions/halt.md +134 -80
  21. data/docs/logging.md +183 -120
  22. data/docs/middlewares.md +165 -392
  23. data/docs/outcomes/result.md +140 -112
  24. data/docs/outcomes/states.md +134 -99
  25. data/docs/outcomes/statuses.md +204 -146
  26. data/docs/parameters/coercions.md +251 -289
  27. data/docs/parameters/defaults.md +224 -169
  28. data/docs/parameters/definitions.md +289 -141
  29. data/docs/parameters/namespacing.md +250 -161
  30. data/docs/parameters/validations.md +247 -159
  31. data/docs/testing.md +196 -203
  32. data/docs/workflows.md +146 -101
  33. data/lib/cmdx/.DS_Store +0 -0
  34. data/lib/cmdx/callback.rb +39 -55
  35. data/lib/cmdx/callback_registry.rb +80 -73
  36. data/lib/cmdx/chain.rb +65 -122
  37. data/lib/cmdx/chain_inspector.rb +23 -116
  38. data/lib/cmdx/chain_serializer.rb +34 -146
  39. data/lib/cmdx/coercion.rb +57 -0
  40. data/lib/cmdx/coercion_registry.rb +113 -0
  41. data/lib/cmdx/coercions/array.rb +18 -36
  42. data/lib/cmdx/coercions/big_decimal.rb +21 -33
  43. data/lib/cmdx/coercions/boolean.rb +21 -40
  44. data/lib/cmdx/coercions/complex.rb +18 -31
  45. data/lib/cmdx/coercions/date.rb +20 -39
  46. data/lib/cmdx/coercions/date_time.rb +22 -39
  47. data/lib/cmdx/coercions/float.rb +19 -32
  48. data/lib/cmdx/coercions/hash.rb +22 -41
  49. data/lib/cmdx/coercions/integer.rb +20 -33
  50. data/lib/cmdx/coercions/rational.rb +20 -32
  51. data/lib/cmdx/coercions/string.rb +23 -31
  52. data/lib/cmdx/coercions/time.rb +24 -40
  53. data/lib/cmdx/coercions/virtual.rb +14 -31
  54. data/lib/cmdx/configuration.rb +101 -162
  55. data/lib/cmdx/context.rb +34 -166
  56. data/lib/cmdx/core_ext/hash.rb +42 -67
  57. data/lib/cmdx/core_ext/module.rb +35 -79
  58. data/lib/cmdx/core_ext/object.rb +63 -98
  59. data/lib/cmdx/correlator.rb +59 -154
  60. data/lib/cmdx/error.rb +37 -202
  61. data/lib/cmdx/errors.rb +153 -216
  62. data/lib/cmdx/fault.rb +68 -150
  63. data/lib/cmdx/faults.rb +26 -137
  64. data/lib/cmdx/immutator.rb +22 -110
  65. data/lib/cmdx/lazy_struct.rb +110 -186
  66. data/lib/cmdx/log_formatters/json.rb +14 -40
  67. data/lib/cmdx/log_formatters/key_value.rb +14 -40
  68. data/lib/cmdx/log_formatters/line.rb +14 -48
  69. data/lib/cmdx/log_formatters/logstash.rb +14 -57
  70. data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
  71. data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
  72. data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
  73. data/lib/cmdx/log_formatters/raw.rb +19 -49
  74. data/lib/cmdx/logger.rb +22 -79
  75. data/lib/cmdx/logger_ansi.rb +31 -72
  76. data/lib/cmdx/logger_serializer.rb +74 -103
  77. data/lib/cmdx/middleware.rb +56 -60
  78. data/lib/cmdx/middleware_registry.rb +82 -77
  79. data/lib/cmdx/middlewares/correlate.rb +41 -226
  80. data/lib/cmdx/middlewares/timeout.rb +46 -185
  81. data/lib/cmdx/parameter.rb +167 -183
  82. data/lib/cmdx/parameter_evaluator.rb +231 -0
  83. data/lib/cmdx/parameter_inspector.rb +37 -55
  84. data/lib/cmdx/parameter_registry.rb +65 -84
  85. data/lib/cmdx/parameter_serializer.rb +32 -76
  86. data/lib/cmdx/railtie.rb +24 -107
  87. data/lib/cmdx/result.rb +254 -259
  88. data/lib/cmdx/result_ansi.rb +28 -80
  89. data/lib/cmdx/result_inspector.rb +34 -70
  90. data/lib/cmdx/result_logger.rb +23 -77
  91. data/lib/cmdx/result_serializer.rb +59 -125
  92. data/lib/cmdx/rspec/matchers.rb +28 -0
  93. data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
  94. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
  95. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
  96. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
  97. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
  98. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
  99. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
  100. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
  101. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
  102. data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
  103. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
  104. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
  105. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
  106. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
  107. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
  108. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
  109. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
  110. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
  111. data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
  112. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
  113. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
  114. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
  115. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
  116. data/lib/cmdx/task.rb +336 -427
  117. data/lib/cmdx/task_deprecator.rb +52 -0
  118. data/lib/cmdx/task_processor.rb +246 -0
  119. data/lib/cmdx/task_serializer.rb +34 -69
  120. data/lib/cmdx/utils/ansi_color.rb +13 -89
  121. data/lib/cmdx/utils/log_timestamp.rb +13 -42
  122. data/lib/cmdx/utils/monotonic_runtime.rb +11 -63
  123. data/lib/cmdx/utils/name_affix.rb +21 -71
  124. data/lib/cmdx/validator.rb +57 -0
  125. data/lib/cmdx/validator_registry.rb +108 -0
  126. data/lib/cmdx/validators/exclusion.rb +55 -94
  127. data/lib/cmdx/validators/format.rb +31 -85
  128. data/lib/cmdx/validators/inclusion.rb +65 -110
  129. data/lib/cmdx/validators/length.rb +117 -133
  130. data/lib/cmdx/validators/numeric.rb +123 -130
  131. data/lib/cmdx/validators/presence.rb +38 -79
  132. data/lib/cmdx/version.rb +1 -7
  133. data/lib/cmdx/workflow.rb +58 -330
  134. data/lib/cmdx.rb +1 -1
  135. data/lib/generators/cmdx/install_generator.rb +14 -31
  136. data/lib/generators/cmdx/task_generator.rb +39 -55
  137. data/lib/generators/cmdx/templates/install.rb +24 -6
  138. data/lib/generators/cmdx/workflow_generator.rb +41 -66
  139. data/lib/locales/ar.yml +0 -1
  140. data/lib/locales/cs.yml +0 -1
  141. data/lib/locales/da.yml +0 -1
  142. data/lib/locales/de.yml +0 -1
  143. data/lib/locales/el.yml +0 -1
  144. data/lib/locales/en.yml +0 -1
  145. data/lib/locales/es.yml +0 -1
  146. data/lib/locales/fi.yml +0 -1
  147. data/lib/locales/fr.yml +0 -1
  148. data/lib/locales/he.yml +0 -1
  149. data/lib/locales/hi.yml +0 -1
  150. data/lib/locales/it.yml +0 -1
  151. data/lib/locales/ja.yml +0 -1
  152. data/lib/locales/ko.yml +0 -1
  153. data/lib/locales/nl.yml +0 -1
  154. data/lib/locales/no.yml +0 -1
  155. data/lib/locales/pl.yml +0 -1
  156. data/lib/locales/pt.yml +0 -1
  157. data/lib/locales/ru.yml +0 -1
  158. data/lib/locales/sv.yml +0 -1
  159. data/lib/locales/th.yml +0 -1
  160. data/lib/locales/tr.yml +0 -1
  161. data/lib/locales/vi.yml +0 -1
  162. data/lib/locales/zh.yml +0 -1
  163. metadata +36 -8
  164. data/lib/cmdx/parameter_validator.rb +0 -81
  165. data/lib/cmdx/parameter_value.rb +0 -244
  166. data/lib/cmdx/parameters_inspector.rb +0 -72
  167. data/lib/cmdx/parameters_serializer.rb +0 -115
  168. data/lib/cmdx/rspec/result_matchers.rb +0 -917
  169. data/lib/cmdx/rspec/task_matchers.rb +0 -570
  170. data/lib/cmdx/validators/custom.rb +0 -102
@@ -2,220 +2,81 @@
2
2
 
3
3
  module CMDx
4
4
 
5
- ##
6
- # Timeout middleware that enforces execution time limits on tasks.
7
- #
8
- # This middleware wraps task execution with timeout protection, automatically
9
- # failing tasks that exceed their configured timeout duration. The timeout
10
- # value can be static, dynamic, or method-based. If no timeout is specified,
11
- # it defaults to 3 seconds. Optionally supports conditional timeout application
12
- # based on task or context state.
13
- #
14
- # ## Timeout Value Types
15
- #
16
- # The middleware supports multiple ways to specify timeout values:
17
- # - **Static values** (Integer/Float): Fixed timeout duration
18
- # - **Method symbols**: Calls the specified method on the task for dynamic calculation
19
- # - **Procs/Lambdas**: Executed in task context for runtime timeout determination
20
- #
21
- # ## Conditional Execution
22
- #
23
- # The middleware supports conditional timeout application using `:if` and `:unless` options:
24
- # - `:if` - Only applies timeout when the condition evaluates to true
25
- # - `:unless` - Only applies timeout when the condition evaluates to false
26
- # - Conditions can be Procs, method symbols, or boolean values
27
- #
28
- # @example Static timeout configuration
29
- # class ProcessOrderTask < CMDx::Task
30
- # use CMDx::Middlewares::Timeout, seconds: 30 # 30 seconds
31
- #
32
- # def call
33
- # # Task logic that might take too long
34
- # end
35
- # end
36
- #
37
- # @example Dynamic timeout using proc
38
- # class ProcessOrderTask < CMDx::Task
39
- # use CMDx::Middlewares::Timeout, seconds: -> { complex_order? ? 60 : 30 }
40
- #
41
- # def call
42
- # # Task logic with dynamic timeout based on order complexity
43
- # end
44
- #
45
- # private
46
- #
47
- # def complex_order?
48
- # context.order_items.count > 10
49
- # end
50
- # end
51
- #
52
- # @example Method-based timeout
53
- # class ProcessOrderTask < CMDx::Task
54
- # use CMDx::Middlewares::Timeout, seconds: :calculate_timeout
55
- #
56
- # def call
57
- # # Task logic with method-calculated timeout
58
- # end
59
- #
60
- # private
61
- #
62
- # def calculate_timeout
63
- # base_timeout = 30
64
- # base_timeout += (context.order_items.count * 2)
65
- # base_timeout
66
- # end
67
- # end
68
- #
69
- # @example Using default timeout (3 seconds)
70
- # class QuickTask < CMDx::Task
71
- # use CMDx::Middlewares::Timeout # 3 seconds default
72
- #
73
- # def call
74
- # # Task logic with default timeout
75
- # end
76
- # end
77
- #
78
- # @example Conditional timeout based on task context
79
- # class ProcessOrderTask < CMDx::Task
80
- # use CMDx::Middlewares::Timeout,
81
- # seconds: 30,
82
- # if: proc { context.enable_timeout? }
83
- #
84
- # def call
85
- # # Task logic with conditional timeout
86
- # end
87
- # end
88
- #
89
- # @example Conditional timeout with method reference
90
- # class ProcessOrderTask < CMDx::Task
91
- # use CMDx::Middlewares::Timeout,
92
- # seconds: 60,
93
- # unless: :skip_timeout?
94
- #
95
- # def call
96
- # # Task logic
97
- # end
98
- #
99
- # private
100
- #
101
- # def skip_timeout?
102
- # Rails.env.development?
103
- # end
104
- # end
105
- #
106
- # @example Global timeout middleware
107
- # class ApplicationTask < CMDx::Task
108
- # use CMDx::Middlewares::Timeout, seconds: 60 # Default 60 seconds
109
- # end
110
- #
111
- # @see CMDx::Middleware Base middleware class
112
- # @see CMDx::Task Task settings configuration
113
- # @see CMDx::Workflow Workflow execution context
114
-
115
- ##
116
- # Custom timeout error class that inherits from Interrupt.
117
- #
118
- # This error is raised when task execution exceeds the configured timeout
119
- # duration. It provides a clean way to distinguish timeout errors from
120
- # other types of interruptions or exceptions.
121
- #
122
- # @example Catching timeout errors
123
- # begin
124
- # task.call
125
- # rescue CMDx::TimeoutError => e
126
- # puts "Task timed out: #{e.message}"
127
- # end
128
- #
129
- # @see CMDx::Middlewares::Timeout The middleware that raises this error
5
+ # Custom exception raised when task execution exceeds the configured timeout limit.
6
+ # Inherits from Interrupt to provide consistent error handling for timeout scenarios
7
+ # and allow proper interruption of long-running tasks.
130
8
  TimeoutError = Class.new(Interrupt)
131
9
 
132
10
  module Middlewares
11
+ # Middleware that provides execution timeout protection for tasks.
12
+ # Automatically interrupts task execution if it exceeds the specified time limit,
13
+ # preventing runaway processes and ensuring system responsiveness.
133
14
  class Timeout < CMDx::Middleware
134
15
 
135
16
  # @return [Integer, Float, Symbol, Proc] The timeout value in seconds
17
+ attr_reader :seconds
18
+
136
19
  # @return [Hash] The conditional options for timeout application
137
- attr_reader :seconds, :conditional
20
+ attr_reader :conditional
138
21
 
139
- ##
140
- # Initializes the timeout middleware.
22
+ # Initializes the timeout middleware with optional configuration.
141
23
  #
142
- # @param options [Hash] Configuration options for the timeout middleware
143
- # @option options [Integer, Float, Symbol, Proc] :seconds Timeout value in seconds.
144
- # - Integer/Float: Used as-is for static timeout
145
- # - Symbol: Called as method on task if it exists, otherwise used as numeric value
146
- # - Proc/Lambda: Executed in task context for dynamic timeout calculation
147
- # Defaults to 3 seconds if not provided.
148
- # @option options [Symbol, Proc] :if Condition that must be truthy for timeout to be applied
149
- # @option options [Symbol, Proc] :unless Condition that must be falsy for timeout to be applied
24
+ # @param options [Hash] configuration options for the middleware
25
+ # @option options [Integer, Float, Symbol, Proc] :seconds timeout duration in seconds (default: 3)
26
+ # @option options [Symbol, Proc] :if condition that must be truthy to apply timeout
27
+ # @option options [Symbol, Proc] :unless condition that must be falsy to apply timeout
150
28
  #
151
- # @example Static timeout configuration
152
- # CMDx::Middlewares::Timeout.new(seconds: 30)
29
+ # @return [Timeout] new instance of the middleware
153
30
  #
154
- # @example Dynamic timeout with proc
155
- # CMDx::Middlewares::Timeout.new(seconds: -> { heavy_operation? ? 120 : 30 })
31
+ # @example Register with a middleware instance
32
+ # use :middleware, CMDx::Middlewares::Timeout.new(seconds: 30)
156
33
  #
157
- # @example Method-based timeout
158
- # CMDx::Middlewares::Timeout.new(seconds: :calculate_timeout_limit)
34
+ # @example Register with fixed timeout
35
+ # use :middleware, CMDx::Middlewares::Timeout, seconds: 30
159
36
  #
160
- # @example Using default timeout (3 seconds)
161
- # CMDx::Middlewares::Timeout.new
37
+ # @example Register with dynamic timeout
38
+ # use :middleware, CMDx::Middlewares::Timeout, seconds: -> { Rails.env.test? ? 1 : 10 }
162
39
  #
163
- # @example Conditional timeout
164
- # CMDx::Middlewares::Timeout.new(seconds: 30, if: :production_mode?)
165
- # CMDx::Middlewares::Timeout.new(seconds: 60, unless: proc { Rails.env.test? })
40
+ # @example Register with conditions
41
+ # use :middleware, CMDx::Middlewares::Timeout, seconds: 5, if: :long_running?, unless: :skip_timeout?
166
42
  def initialize(options = {})
167
43
  @seconds = options[:seconds] || 3
168
44
  @conditional = options.slice(:if, :unless)
169
45
  end
170
46
 
171
- ##
172
- # Executes the task with conditional timeout protection.
173
- #
174
- # Evaluates the conditional options to determine if timeout should be applied.
175
- # If conditions are met, resolves the timeout value using and wraps the task
176
- # execution with a timeout mechanism that will interrupt execution if it exceeds
177
- # the configured time limit. If conditions are not met, executes the task
178
- # without timeout protection.
47
+ # Executes the middleware, wrapping task execution with timeout protection.
48
+ # Evaluates conditions, determines timeout duration, and executes the task within
49
+ # the timeout boundary to prevent runaway execution.
179
50
  #
180
- # The timeout value determination follows this precedence:
181
- # 1. Explicit timeout value (provided during middleware initialization)
182
- # - Integer/Float: Used as-is for static timeout
183
- # - Symbol: Called as method on task if it exists, otherwise used as numeric value
184
- # - Proc/Lambda: Executed in task context for dynamic timeout calculation
185
- # 2. Default value of 3 seconds if no timeout is specified
51
+ # @param task [CMDx::Task] the task being executed
52
+ # @param callable [Proc] the callable that executes the task
186
53
  #
187
- # @param task [CMDx::Task] The task instance to execute
188
- # @param callable [#call] The next middleware or task execution callable
189
- # @return [CMDx::Result] The task execution result
190
- # @raise [TimeoutError] If execution exceeds the configured timeout and conditions are met
54
+ # @return [Object] the result of the task execution
191
55
  #
192
- # @example Static timeout - successful execution
193
- # # Task completes in 5 seconds, timeout is 30 seconds, condition is true
194
- # result = task.call # => success
56
+ # @raise [TimeoutError] when task execution exceeds the timeout limit
195
57
  #
196
- # @example Static timeout - timeout exceeded
197
- # # Task would take 60 seconds, timeout is 30 seconds, condition is true
198
- # result = task.call # => failed with timeout error
58
+ # @example Task using timeout middleware
59
+ # class ProcessFileTask < CMDx::Task
60
+ # use :middleware, CMDx::Middlewares::Timeout, seconds: 10
199
61
  #
200
- # @example Dynamic timeout with proc
201
- # # Task uses proc to calculate 120 seconds for complex operation
202
- # # Task completes in 90 seconds
203
- # result = task.call # => success
62
+ # def call
63
+ # # Task execution is automatically wrapped with timeout protection
64
+ # end
65
+ # end
204
66
  #
205
- # @example Method-based timeout
206
- # # Task calls :timeout_limit method which returns 45 seconds
207
- # # Task completes in 30 seconds
208
- # result = task.call # => success
209
- #
210
- # @example Condition not met
211
- # # Task takes 60 seconds, timeout is 30 seconds, but condition is false
212
- # result = task.call # => success (no timeout applied)
67
+ # @example Global configuration with conditional timeout
68
+ # CMDx.configure do |config|
69
+ # config.middlewares.register CMDx::Middlewares::Timeout, seconds: 30, if: :large_dataset?
70
+ # end
213
71
  def call(task, callable)
214
72
  # Check if timeout should be applied based on conditions
215
- return callable.call(task) unless task.__cmdx_eval(conditional)
73
+ return callable.call(task) unless task.cmdx_eval(conditional)
216
74
 
217
75
  # Get seconds using yield for dynamic generation
218
- limit = task.__cmdx_yield(seconds) || 3
76
+ limit = task.cmdx_yield(seconds) || 3
77
+
78
+ # Ensure limit is numeric, fallback to default if not
79
+ limit = 3 unless limit.is_a?(Numeric)
219
80
 
220
81
  # Apply timeout protection
221
82
  ::Timeout.timeout(limit, TimeoutError, "execution exceeded #{limit} seconds") do