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.
- checksums.yaml +4 -4
- data/.cursor/prompts/docs.md +9 -0
- data/.cursor/prompts/rspec.md +21 -0
- data/.cursor/prompts/yardoc.md +13 -0
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +29 -3
- data/README.md +2 -1
- data/docs/ai_prompts.md +269 -195
- data/docs/basics/call.md +126 -60
- data/docs/basics/chain.md +190 -160
- data/docs/basics/context.md +242 -154
- data/docs/basics/setup.md +302 -32
- data/docs/callbacks.md +382 -119
- data/docs/configuration.md +211 -49
- data/docs/deprecation.md +245 -0
- data/docs/getting_started.md +161 -39
- data/docs/internationalization.md +590 -70
- data/docs/interruptions/exceptions.md +135 -118
- data/docs/interruptions/faults.md +152 -127
- data/docs/interruptions/halt.md +134 -80
- data/docs/logging.md +183 -120
- data/docs/middlewares.md +165 -392
- data/docs/outcomes/result.md +140 -112
- data/docs/outcomes/states.md +134 -99
- data/docs/outcomes/statuses.md +204 -146
- data/docs/parameters/coercions.md +251 -289
- data/docs/parameters/defaults.md +224 -169
- data/docs/parameters/definitions.md +289 -141
- data/docs/parameters/namespacing.md +250 -161
- data/docs/parameters/validations.md +247 -159
- data/docs/testing.md +196 -203
- data/docs/workflows.md +146 -101
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +39 -55
- data/lib/cmdx/callback_registry.rb +80 -73
- data/lib/cmdx/chain.rb +65 -122
- data/lib/cmdx/chain_inspector.rb +23 -116
- data/lib/cmdx/chain_serializer.rb +34 -146
- data/lib/cmdx/coercion.rb +57 -0
- data/lib/cmdx/coercion_registry.rb +113 -0
- data/lib/cmdx/coercions/array.rb +18 -36
- data/lib/cmdx/coercions/big_decimal.rb +21 -33
- data/lib/cmdx/coercions/boolean.rb +21 -40
- data/lib/cmdx/coercions/complex.rb +18 -31
- data/lib/cmdx/coercions/date.rb +20 -39
- data/lib/cmdx/coercions/date_time.rb +22 -39
- data/lib/cmdx/coercions/float.rb +19 -32
- data/lib/cmdx/coercions/hash.rb +22 -41
- data/lib/cmdx/coercions/integer.rb +20 -33
- data/lib/cmdx/coercions/rational.rb +20 -32
- data/lib/cmdx/coercions/string.rb +23 -31
- data/lib/cmdx/coercions/time.rb +24 -40
- data/lib/cmdx/coercions/virtual.rb +14 -31
- data/lib/cmdx/configuration.rb +101 -162
- data/lib/cmdx/context.rb +34 -166
- data/lib/cmdx/core_ext/hash.rb +42 -67
- data/lib/cmdx/core_ext/module.rb +35 -79
- data/lib/cmdx/core_ext/object.rb +63 -98
- data/lib/cmdx/correlator.rb +59 -154
- data/lib/cmdx/error.rb +37 -202
- data/lib/cmdx/errors.rb +153 -216
- data/lib/cmdx/fault.rb +68 -150
- data/lib/cmdx/faults.rb +26 -137
- data/lib/cmdx/immutator.rb +22 -110
- data/lib/cmdx/lazy_struct.rb +110 -186
- data/lib/cmdx/log_formatters/json.rb +14 -40
- data/lib/cmdx/log_formatters/key_value.rb +14 -40
- data/lib/cmdx/log_formatters/line.rb +14 -48
- data/lib/cmdx/log_formatters/logstash.rb +14 -57
- data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
- data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
- data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
- data/lib/cmdx/log_formatters/raw.rb +19 -49
- data/lib/cmdx/logger.rb +22 -79
- data/lib/cmdx/logger_ansi.rb +31 -72
- data/lib/cmdx/logger_serializer.rb +74 -103
- data/lib/cmdx/middleware.rb +56 -60
- data/lib/cmdx/middleware_registry.rb +82 -77
- data/lib/cmdx/middlewares/correlate.rb +41 -226
- data/lib/cmdx/middlewares/timeout.rb +46 -185
- data/lib/cmdx/parameter.rb +167 -183
- data/lib/cmdx/parameter_evaluator.rb +231 -0
- data/lib/cmdx/parameter_inspector.rb +37 -55
- data/lib/cmdx/parameter_registry.rb +65 -84
- data/lib/cmdx/parameter_serializer.rb +32 -76
- data/lib/cmdx/railtie.rb +24 -107
- data/lib/cmdx/result.rb +254 -259
- data/lib/cmdx/result_ansi.rb +28 -80
- data/lib/cmdx/result_inspector.rb +34 -70
- data/lib/cmdx/result_logger.rb +23 -77
- data/lib/cmdx/result_serializer.rb +59 -125
- data/lib/cmdx/rspec/matchers.rb +28 -0
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
- data/lib/cmdx/task.rb +336 -427
- data/lib/cmdx/task_deprecator.rb +52 -0
- data/lib/cmdx/task_processor.rb +246 -0
- data/lib/cmdx/task_serializer.rb +34 -69
- data/lib/cmdx/utils/ansi_color.rb +13 -89
- data/lib/cmdx/utils/log_timestamp.rb +13 -42
- data/lib/cmdx/utils/monotonic_runtime.rb +11 -63
- data/lib/cmdx/utils/name_affix.rb +21 -71
- data/lib/cmdx/validator.rb +57 -0
- data/lib/cmdx/validator_registry.rb +108 -0
- data/lib/cmdx/validators/exclusion.rb +55 -94
- data/lib/cmdx/validators/format.rb +31 -85
- data/lib/cmdx/validators/inclusion.rb +65 -110
- data/lib/cmdx/validators/length.rb +117 -133
- data/lib/cmdx/validators/numeric.rb +123 -130
- data/lib/cmdx/validators/presence.rb +38 -79
- data/lib/cmdx/version.rb +1 -7
- data/lib/cmdx/workflow.rb +58 -330
- data/lib/cmdx.rb +1 -1
- data/lib/generators/cmdx/install_generator.rb +14 -31
- data/lib/generators/cmdx/task_generator.rb +39 -55
- data/lib/generators/cmdx/templates/install.rb +24 -6
- data/lib/generators/cmdx/workflow_generator.rb +41 -66
- data/lib/locales/ar.yml +0 -1
- data/lib/locales/cs.yml +0 -1
- data/lib/locales/da.yml +0 -1
- data/lib/locales/de.yml +0 -1
- data/lib/locales/el.yml +0 -1
- data/lib/locales/en.yml +0 -1
- data/lib/locales/es.yml +0 -1
- data/lib/locales/fi.yml +0 -1
- data/lib/locales/fr.yml +0 -1
- data/lib/locales/he.yml +0 -1
- data/lib/locales/hi.yml +0 -1
- data/lib/locales/it.yml +0 -1
- data/lib/locales/ja.yml +0 -1
- data/lib/locales/ko.yml +0 -1
- data/lib/locales/nl.yml +0 -1
- data/lib/locales/no.yml +0 -1
- data/lib/locales/pl.yml +0 -1
- data/lib/locales/pt.yml +0 -1
- data/lib/locales/ru.yml +0 -1
- data/lib/locales/sv.yml +0 -1
- data/lib/locales/th.yml +0 -1
- data/lib/locales/tr.yml +0 -1
- data/lib/locales/vi.yml +0 -1
- data/lib/locales/zh.yml +0 -1
- metadata +36 -8
- data/lib/cmdx/parameter_validator.rb +0 -81
- data/lib/cmdx/parameter_value.rb +0 -244
- data/lib/cmdx/parameters_inspector.rb +0 -72
- data/lib/cmdx/parameters_serializer.rb +0 -115
- data/lib/cmdx/rspec/result_matchers.rb +0 -917
- data/lib/cmdx/rspec/task_matchers.rb +0 -570
- data/lib/cmdx/validators/custom.rb +0 -102
@@ -2,220 +2,81 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
|
5
|
-
|
6
|
-
#
|
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 :
|
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]
|
143
|
-
# @option options [Integer, Float, Symbol, Proc] :seconds
|
144
|
-
#
|
145
|
-
#
|
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
|
-
# @
|
152
|
-
# CMDx::Middlewares::Timeout.new(seconds: 30)
|
29
|
+
# @return [Timeout] new instance of the middleware
|
153
30
|
#
|
154
|
-
# @example
|
155
|
-
# CMDx::Middlewares::Timeout.new(seconds:
|
31
|
+
# @example Register with a middleware instance
|
32
|
+
# use :middleware, CMDx::Middlewares::Timeout.new(seconds: 30)
|
156
33
|
#
|
157
|
-
# @example
|
158
|
-
# CMDx::Middlewares::Timeout
|
34
|
+
# @example Register with fixed timeout
|
35
|
+
# use :middleware, CMDx::Middlewares::Timeout, seconds: 30
|
159
36
|
#
|
160
|
-
# @example
|
161
|
-
# CMDx::Middlewares::Timeout.
|
37
|
+
# @example Register with dynamic timeout
|
38
|
+
# use :middleware, CMDx::Middlewares::Timeout, seconds: -> { Rails.env.test? ? 1 : 10 }
|
162
39
|
#
|
163
|
-
# @example
|
164
|
-
# CMDx::Middlewares::Timeout
|
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
|
-
#
|
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
|
-
#
|
181
|
-
#
|
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
|
-
# @
|
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
|
-
# @
|
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
|
197
|
-
#
|
198
|
-
#
|
58
|
+
# @example Task using timeout middleware
|
59
|
+
# class ProcessFileTask < CMDx::Task
|
60
|
+
# use :middleware, CMDx::Middlewares::Timeout, seconds: 10
|
199
61
|
#
|
200
|
-
#
|
201
|
-
#
|
202
|
-
#
|
203
|
-
#
|
62
|
+
# def call
|
63
|
+
# # Task execution is automatically wrapped with timeout protection
|
64
|
+
# end
|
65
|
+
# end
|
204
66
|
#
|
205
|
-
# @example
|
206
|
-
#
|
207
|
-
#
|
208
|
-
#
|
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.
|
73
|
+
return callable.call(task) unless task.cmdx_eval(conditional)
|
216
74
|
|
217
75
|
# Get seconds using yield for dynamic generation
|
218
|
-
limit = task.
|
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
|