cmdx 1.1.2 → 1.5.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 (192) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/prompts/docs.md +4 -1
  4. data/.cursor/prompts/llms.md +20 -0
  5. data/.cursor/prompts/rspec.md +4 -1
  6. data/.cursor/prompts/yardoc.md +3 -2
  7. data/.cursor/rules/cursor-instructions.mdc +56 -1
  8. data/.irbrc +6 -0
  9. data/.rubocop.yml +29 -18
  10. data/CHANGELOG.md +5 -133
  11. data/LLM.md +3317 -0
  12. data/README.md +68 -44
  13. data/docs/attributes/coercions.md +162 -0
  14. data/docs/attributes/defaults.md +90 -0
  15. data/docs/attributes/definitions.md +281 -0
  16. data/docs/attributes/naming.md +78 -0
  17. data/docs/attributes/validations.md +309 -0
  18. data/docs/basics/chain.md +56 -249
  19. data/docs/basics/context.md +56 -289
  20. data/docs/basics/execution.md +114 -0
  21. data/docs/basics/setup.md +37 -334
  22. data/docs/callbacks.md +89 -467
  23. data/docs/deprecation.md +91 -174
  24. data/docs/getting_started.md +212 -202
  25. data/docs/internationalization.md +11 -647
  26. data/docs/interruptions/exceptions.md +23 -198
  27. data/docs/interruptions/faults.md +71 -151
  28. data/docs/interruptions/halt.md +109 -186
  29. data/docs/logging.md +44 -256
  30. data/docs/middlewares.md +113 -426
  31. data/docs/outcomes/result.md +81 -228
  32. data/docs/outcomes/states.md +33 -221
  33. data/docs/outcomes/statuses.md +21 -311
  34. data/docs/tips_and_tricks.md +120 -70
  35. data/docs/workflows.md +99 -283
  36. data/lib/cmdx/.DS_Store +0 -0
  37. data/lib/cmdx/attribute.rb +229 -0
  38. data/lib/cmdx/attribute_registry.rb +94 -0
  39. data/lib/cmdx/attribute_value.rb +193 -0
  40. data/lib/cmdx/callback_registry.rb +69 -77
  41. data/lib/cmdx/chain.rb +56 -73
  42. data/lib/cmdx/coercion_registry.rb +52 -68
  43. data/lib/cmdx/coercions/array.rb +19 -18
  44. data/lib/cmdx/coercions/big_decimal.rb +20 -24
  45. data/lib/cmdx/coercions/boolean.rb +26 -25
  46. data/lib/cmdx/coercions/complex.rb +21 -22
  47. data/lib/cmdx/coercions/date.rb +25 -23
  48. data/lib/cmdx/coercions/date_time.rb +24 -25
  49. data/lib/cmdx/coercions/float.rb +25 -22
  50. data/lib/cmdx/coercions/hash.rb +31 -32
  51. data/lib/cmdx/coercions/integer.rb +30 -24
  52. data/lib/cmdx/coercions/rational.rb +29 -24
  53. data/lib/cmdx/coercions/string.rb +19 -22
  54. data/lib/cmdx/coercions/symbol.rb +37 -0
  55. data/lib/cmdx/coercions/time.rb +26 -25
  56. data/lib/cmdx/configuration.rb +49 -108
  57. data/lib/cmdx/context.rb +222 -44
  58. data/lib/cmdx/deprecator.rb +61 -0
  59. data/lib/cmdx/errors.rb +42 -252
  60. data/lib/cmdx/exceptions.rb +39 -0
  61. data/lib/cmdx/faults.rb +78 -39
  62. data/lib/cmdx/freezer.rb +51 -0
  63. data/lib/cmdx/identifier.rb +30 -0
  64. data/lib/cmdx/locale.rb +52 -0
  65. data/lib/cmdx/log_formatters/json.rb +21 -22
  66. data/lib/cmdx/log_formatters/key_value.rb +20 -22
  67. data/lib/cmdx/log_formatters/line.rb +15 -22
  68. data/lib/cmdx/log_formatters/logstash.rb +22 -23
  69. data/lib/cmdx/log_formatters/raw.rb +16 -22
  70. data/lib/cmdx/middleware_registry.rb +70 -74
  71. data/lib/cmdx/middlewares/correlate.rb +90 -54
  72. data/lib/cmdx/middlewares/runtime.rb +58 -0
  73. data/lib/cmdx/middlewares/timeout.rb +48 -68
  74. data/lib/cmdx/railtie.rb +12 -45
  75. data/lib/cmdx/result.rb +229 -314
  76. data/lib/cmdx/task.rb +194 -366
  77. data/lib/cmdx/utils/call.rb +49 -0
  78. data/lib/cmdx/utils/condition.rb +71 -0
  79. data/lib/cmdx/utils/format.rb +61 -0
  80. data/lib/cmdx/validator_registry.rb +63 -72
  81. data/lib/cmdx/validators/exclusion.rb +38 -67
  82. data/lib/cmdx/validators/format.rb +48 -49
  83. data/lib/cmdx/validators/inclusion.rb +43 -74
  84. data/lib/cmdx/validators/length.rb +91 -154
  85. data/lib/cmdx/validators/numeric.rb +87 -162
  86. data/lib/cmdx/validators/presence.rb +37 -50
  87. data/lib/cmdx/version.rb +1 -1
  88. data/lib/cmdx/worker.rb +178 -0
  89. data/lib/cmdx/workflow.rb +85 -81
  90. data/lib/cmdx.rb +19 -13
  91. data/lib/generators/cmdx/install_generator.rb +14 -13
  92. data/lib/generators/cmdx/task_generator.rb +25 -50
  93. data/lib/generators/cmdx/templates/install.rb +11 -46
  94. data/lib/generators/cmdx/templates/task.rb.tt +3 -2
  95. data/lib/locales/en.yml +18 -4
  96. data/src/cmdx-logo.png +0 -0
  97. metadata +32 -116
  98. data/docs/ai_prompts.md +0 -393
  99. data/docs/basics/call.md +0 -317
  100. data/docs/configuration.md +0 -344
  101. data/docs/parameters/coercions.md +0 -396
  102. data/docs/parameters/defaults.md +0 -335
  103. data/docs/parameters/definitions.md +0 -446
  104. data/docs/parameters/namespacing.md +0 -378
  105. data/docs/parameters/validations.md +0 -405
  106. data/docs/testing.md +0 -553
  107. data/lib/cmdx/callback.rb +0 -53
  108. data/lib/cmdx/chain_inspector.rb +0 -56
  109. data/lib/cmdx/chain_serializer.rb +0 -63
  110. data/lib/cmdx/coercion.rb +0 -57
  111. data/lib/cmdx/coercions/virtual.rb +0 -29
  112. data/lib/cmdx/core_ext/hash.rb +0 -83
  113. data/lib/cmdx/core_ext/module.rb +0 -98
  114. data/lib/cmdx/core_ext/object.rb +0 -125
  115. data/lib/cmdx/correlator.rb +0 -122
  116. data/lib/cmdx/error.rb +0 -67
  117. data/lib/cmdx/fault.rb +0 -140
  118. data/lib/cmdx/immutator.rb +0 -52
  119. data/lib/cmdx/lazy_struct.rb +0 -246
  120. data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
  121. data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
  122. data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
  123. data/lib/cmdx/logger.rb +0 -49
  124. data/lib/cmdx/logger_ansi.rb +0 -68
  125. data/lib/cmdx/logger_serializer.rb +0 -116
  126. data/lib/cmdx/middleware.rb +0 -70
  127. data/lib/cmdx/parameter.rb +0 -312
  128. data/lib/cmdx/parameter_evaluator.rb +0 -231
  129. data/lib/cmdx/parameter_inspector.rb +0 -66
  130. data/lib/cmdx/parameter_registry.rb +0 -106
  131. data/lib/cmdx/parameter_serializer.rb +0 -59
  132. data/lib/cmdx/result_ansi.rb +0 -71
  133. data/lib/cmdx/result_inspector.rb +0 -71
  134. data/lib/cmdx/result_logger.rb +0 -59
  135. data/lib/cmdx/result_serializer.rb +0 -104
  136. data/lib/cmdx/rspec/matchers.rb +0 -28
  137. data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
  138. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
  139. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
  140. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
  141. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
  142. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
  143. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
  144. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
  145. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
  146. data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
  147. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
  148. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
  149. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
  150. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
  151. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
  152. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
  153. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
  154. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
  155. data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
  156. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
  157. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
  158. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
  159. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
  160. data/lib/cmdx/task_deprecator.rb +0 -58
  161. data/lib/cmdx/task_processor.rb +0 -246
  162. data/lib/cmdx/task_serializer.rb +0 -57
  163. data/lib/cmdx/utils/ansi_color.rb +0 -73
  164. data/lib/cmdx/utils/log_timestamp.rb +0 -36
  165. data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
  166. data/lib/cmdx/utils/name_affix.rb +0 -52
  167. data/lib/cmdx/validator.rb +0 -57
  168. data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
  169. data/lib/generators/cmdx/workflow_generator.rb +0 -84
  170. data/lib/locales/ar.yml +0 -35
  171. data/lib/locales/cs.yml +0 -35
  172. data/lib/locales/da.yml +0 -35
  173. data/lib/locales/de.yml +0 -35
  174. data/lib/locales/el.yml +0 -35
  175. data/lib/locales/es.yml +0 -35
  176. data/lib/locales/fi.yml +0 -35
  177. data/lib/locales/fr.yml +0 -35
  178. data/lib/locales/he.yml +0 -35
  179. data/lib/locales/hi.yml +0 -35
  180. data/lib/locales/it.yml +0 -35
  181. data/lib/locales/ja.yml +0 -35
  182. data/lib/locales/ko.yml +0 -35
  183. data/lib/locales/nl.yml +0 -35
  184. data/lib/locales/no.yml +0 -35
  185. data/lib/locales/pl.yml +0 -35
  186. data/lib/locales/pt.yml +0 -35
  187. data/lib/locales/ru.yml +0 -35
  188. data/lib/locales/sv.yml +0 -35
  189. data/lib/locales/th.yml +0 -35
  190. data/lib/locales/tr.yml +0 -35
  191. data/lib/locales/vi.yml +0 -35
  192. data/lib/locales/zh.yml +0 -35
data/lib/cmdx/task.rb CHANGED
@@ -1,438 +1,266 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Core task implementation providing executable units of work with parameter management.
5
- #
6
- # Task is the fundamental building block of the CMDx framework, providing a structured
7
- # approach to implementing business logic with built-in parameter validation, middleware
8
- # support, callback handling, and comprehensive result tracking. Tasks encapsulate
9
- # discrete units of work that can be chained together into workflows or executed
10
- # independently with rich execution context and error handling.
4
+ # Represents a task that can be executed within the CMDx framework.
5
+ # Tasks define attributes, callbacks, and execution logic that can be
6
+ # chained together to form workflows.
11
7
  class Task
12
8
 
13
- cmdx_attr_setting :cmd_settings,
14
- default: -> { CMDx.configuration.to_h.slice(:logger, :task_halt, :workflow_halt).merge(tags: []) }
15
- cmdx_attr_setting :cmd_middlewares,
16
- default: -> { MiddlewareRegistry.new(CMDx.configuration.middlewares) }
17
- cmdx_attr_setting :cmd_callbacks,
18
- default: -> { CallbackRegistry.new(CMDx.configuration.callbacks) }
19
- cmdx_attr_setting :cmd_parameters,
20
- default: -> { ParameterRegistry.new }
9
+ extend Forwardable
21
10
 
22
- cmdx_attr_delegator :cmd_middlewares, :cmd_callbacks, :cmd_parameters,
23
- :cmd_settings, :cmd_setting, :cmd_setting?,
24
- to: :class
25
- cmdx_attr_delegator :skip!, :fail!, :throw!,
26
- to: :result
27
-
28
- # @return [Context] parameter context for this task execution
29
- attr_reader :context
30
-
31
- # @return [Errors] collection of validation and execution errors
32
- attr_reader :errors
33
-
34
- # @return [String] unique identifier for this task instance
35
- attr_reader :id
36
-
37
- # @return [Result] execution result tracking state and status
38
- attr_reader :result
39
-
40
- # @return [Chain] execution chain containing this task and related executions
41
- attr_reader :chain
42
-
43
- # @return [Context] alias for context
11
+ attr_reader :attributes, :errors, :id, :context, :result, :chain
44
12
  alias ctx context
45
-
46
- # @return [Result] alias for result
47
13
  alias res result
48
14
 
49
- # Creates a new task instance with the given execution context.
50
- #
51
- # Initializes all internal state including context, errors, unique identifier,
52
- # result tracking, and execution chain. The context parameter supports various
53
- # input formats and will be normalized into a Context instance.
54
- #
55
- # @param context [Hash, Context, Object] initial execution context and parameters
15
+ def_delegators :result, :skip!, :fail!, :throw!
16
+
17
+ # @param context [Hash, Context] The initial context for the task
56
18
  #
57
- # @return [Task] the newly created task instance
19
+ # @option context [Object] :* Any key-value pairs to initialize the context
58
20
  #
59
- # @example Create task with hash context
60
- # task = MyTask.new(user_id: 123, action: "process")
61
- # task.context.user_id #=> 123
21
+ # @return [Task] A new task instance
62
22
  #
63
- # @example Create task with existing context
64
- # existing_context = OtherTask.call(status: "active")
65
- # task = MyTask.new(existing_context)
66
- # task.context.status #=> "active"
23
+ # @raise [DeprecationError] If the task class is deprecated
67
24
  #
68
- # @example Create task with empty context
69
- # task = MyTask.new
70
- # task.context #=> empty Context instance
25
+ # @example
26
+ # task = MyTask.new(name: "example", priority: :high)
27
+ # task = MyTask.new(Context.build(name: "example"))
71
28
  def initialize(context = {})
72
- context = context.context if context.respond_to?(:context)
29
+ Deprecator.restrict(self)
73
30
 
74
- @context = Context.build(context)
75
- @errors = Errors.new
76
- @id = CMDx::Correlator.generate
77
- @result = Result.new(self)
78
- @chain = Chain.build(@result)
31
+ @attributes = {}
32
+ @errors = Errors.new
79
33
 
80
- TaskDeprecator.call(self)
34
+ @id = Identifier.generate
35
+ @context = Context.build(context)
36
+ @result = Result.new(self)
37
+ @chain = Chain.build(@result)
81
38
  end
82
39
 
83
40
  class << self
84
41
 
85
- CallbackRegistry::TYPES.each do |callback|
86
- # Registers a callback for the specified lifecycle event.
87
- #
88
- # This method is dynamically defined for each callback type supported by
89
- # CallbackRegistry, allowing tasks to register callbacks for various
90
- # execution lifecycle events.
91
- #
92
- # @param callables [Array<Object>] callback objects or procs to register
93
- # @param options [Hash] options for callback registration
94
- # @param block [Proc] optional block to use as callback
95
- #
96
- # @return [void]
97
- #
98
- # @example Register before_execution callback with symbol
99
- # class MyTask < CMDx::Task
100
- # before_execution :setup_database
101
- # end
102
- #
103
- # @example Register before_execution callback with proc
104
- # class MyTask < CMDx::Task
105
- # before_execution -> { puts "Starting task execution" }
106
- # end
107
- #
108
- # @example Register before_execution callback with class
109
- # class MyTask < CMDx::Task
110
- # before_execution SetupCallback
111
- # end
112
- #
113
- # @example Register before_execution callback with block
114
- # class MyTask < CMDx::Task
115
- # before_execution { |task| task.context.started_at = Time.now }
116
- # end
117
- #
118
- # @example Register on_success callback with conditional options
119
- # class MyTask < CMDx::Task
120
- # on_success :send_notification, if: -> { Rails.env.production? }
121
- # end
122
- #
123
- # @example Register on_success callback with multiple callables
124
- # class MyTask < CMDx::Task
125
- # on_success :log_success, :send_email, :update_metrics
126
- # end
127
- define_method(callback) do |*callables, **options, &block|
128
- cmd_callbacks.register(callback, *callables, **options, &block)
129
- end
130
- end
131
-
132
- # Retrieves a configuration setting value by key.
133
- #
134
- # Provides access to task-specific configuration settings that control
135
- # various aspects of task execution including logging, halt conditions,
136
- # and custom settings.
137
- #
138
- # @param key [Symbol, String] the configuration setting key to retrieve
42
+ # @param options [Hash] Configuration options to merge with existing settings
43
+ # @option options [AttributeRegistry] :attributes Registry for task attributes
44
+ # @option options [Boolean] :deprecate Whether the task is deprecated
45
+ # @option options [Array<Symbol>] :tags Tags associated with the task
139
46
  #
140
- # @return [Object] the configuration value, or nil if key doesn't exist
47
+ # @return [Hash] The merged settings hash
141
48
  #
142
- # @example Get logger setting
143
- # MyTask.cmd_setting(:logger) #=> Logger instance
144
- #
145
- # @example Get custom setting
146
- # MyTask.cmd_settings!(timeout: 30)
147
- # MyTask.cmd_setting(:timeout) #=> 30
148
- def cmd_setting(key)
149
- cmdx_yield(cmd_settings[key])
150
- end
49
+ # @example
50
+ # class MyTask < Task
51
+ # settings deprecate: true, tags: [:experimental]
52
+ # end
53
+ def settings(**options)
54
+ @settings ||= begin
55
+ hash =
56
+ if superclass.respond_to?(:settings)
57
+ superclass.settings
58
+ else
59
+ CMDx.configuration.to_h.except(:logger)
60
+ end.transform_values(&:dup)
151
61
 
152
- # Checks if a configuration setting exists.
153
- #
154
- # @param key [Symbol, String] the configuration setting key to check
155
- #
156
- # @return [Boolean] true if the setting key exists, false otherwise
157
- #
158
- # @example Check for existing setting
159
- # MyTask.cmd_setting?(:logger) #=> true
160
- #
161
- # @example Check for non-existing setting
162
- # MyTask.cmd_setting?(:nonexistent) #=> false
163
- def cmd_setting?(key)
164
- cmd_settings.key?(key)
62
+ hash[:attributes] ||= AttributeRegistry.new
63
+ hash[:deprecate] ||= false
64
+ hash[:tags] ||= []
65
+
66
+ hash.merge!(options)
67
+ end
165
68
  end
166
69
 
167
- # Updates task configuration settings with the provided options.
70
+ # @param type [Symbol] The type of registry to register with
71
+ # @param object [Object] The object to register
72
+ # @param args [Array] Additional arguments for registration
168
73
  #
169
- # Merges the given options into the existing configuration settings,
170
- # allowing tasks to customize their execution behavior.
74
+ # @raise [RuntimeError] If the registry type is unknown
171
75
  #
172
- # @param options [Hash] configuration options to merge
173
- #
174
- # @return [Hash] the updated settings hash
175
- #
176
- # @example Set custom timeout
177
- # MyTask.cmd_settings!(timeout: 60, retries: 3)
178
- #
179
- # @example Override halt condition
180
- # MyTask.cmd_settings!(task_halt: ["failed", "error"])
181
- def cmd_settings!(**options)
182
- cmd_settings.merge!(options)
76
+ # @example
77
+ # register(:attribute, MyAttribute.new)
78
+ # register(:callback, :before, -> { puts "before" })
79
+ def register(type, object, ...)
80
+ case type
81
+ when :attribute then settings[:attributes].register(object, ...)
82
+ when :callback then settings[:callbacks].register(object, ...)
83
+ when :coercion then settings[:coercions].register(object, ...)
84
+ when :middleware then settings[:middlewares].register(object, ...)
85
+ when :validator then settings[:validators].register(object, ...)
86
+ else raise "unknown registry type #{type.inspect}"
87
+ end
183
88
  end
184
89
 
185
- # Registers middleware, callbacks, validators, or coercions with the task.
186
- #
187
- # Provides a unified interface for registering various types of task
188
- # extensions that modify or enhance task execution behavior.
189
- #
190
- # @param type [Symbol] the type of extension to register (:middleware, :callback, :validator, :coercion)
191
- # @param object [Object] the extension object to register
192
- # @param args [Array] additional arguments for registration
193
- #
194
- # @return [void]
195
- #
196
- # @raise [ArgumentError] if an unsupported type is provided
197
- #
198
- # @example Register coercion
199
- # class MyTask < CMDx::Task
200
- # use :coercion, TemperatureCoercion
201
- # end
202
- #
203
- # @example Register validator
204
- # class MyTask < CMDx::Task
205
- # use :validator, ZipcodeValidator, country: "US"
206
- # end
90
+ # @param type [Symbol] The type of registry to deregister from
91
+ # @param object [Object] The object to deregister
92
+ # @param args [Array] Additional arguments for deregistration
207
93
  #
208
- # @example Register middleware
209
- # class MyTask < CMDx::Task
210
- # use :middleware, CMDx::Middlewares::Timeout.new(seconds: 30)
211
- # end
94
+ # @raise [RuntimeError] If the registry type is unknown
212
95
  #
213
- # @example Register callback
214
- # class MyTask < CMDx::Task
215
- # use :callback, :before, LogCallback.new
216
- # end
217
- def use(type, object, ...)
96
+ # @example
97
+ # deregister(:attribute, :name)
98
+ # deregister(:callback, :before, MyCallback)
99
+ def deregister(type, object, ...)
218
100
  case type
219
- when :middleware
220
- cmd_middlewares.register(object, ...)
221
- when :callback
222
- cmd_callbacks.register(type, object, ...)
223
- when :validator
224
- cmd_validators.register(type, object, ...)
225
- when :coercion
226
- cmd_coercions.register(type, object, ...)
101
+ when :attribute then settings[:attributes].deregister(object, ...)
102
+ when :callback then settings[:callbacks].deregister(object, ...)
103
+ when :coercion then settings[:coercions].deregister(object, ...)
104
+ when :middleware then settings[:middlewares].deregister(object, ...)
105
+ when :validator then settings[:validators].deregister(object, ...)
106
+ else raise "unknown registry type #{type.inspect}"
227
107
  end
228
108
  end
229
109
 
230
- # Defines optional parameters for the task with validation and coercion.
231
- #
232
- # Creates parameter definitions that are not required for task execution
233
- # but will be validated and coerced if provided. Supports nested parameter
234
- # structures through block syntax.
110
+ # @param args [Array] Arguments to build the attribute with
235
111
  #
236
- # @param attributes [Array<Symbol>] parameter names to define as optional
237
- # @param options [Hash] parameter configuration options
238
- # @option options [Symbol, Array<Symbol>] :type parameter type(s) for coercion
239
- # @option options [Object] :default default value if parameter not provided
240
- # @option options [Hash] :validates validation rules to apply
241
- # @param block [Proc] optional block for defining nested parameters
242
- #
243
- # @return [Array<Parameter>] the created parameter definitions
244
- #
245
- # @example Define simple optional parameters
246
- # class MyTask < CMDx::Task
247
- # optional :name, :email, type: :string
248
- # optional :age, type: :integer, default: 0
249
- # end
250
- #
251
- # @example Define optional parameter with validation
252
- # class MyTask < CMDx::Task
253
- # optional :score, type: :integer, validates: { numeric: { greater_than: 0 } }
254
- # end
255
- #
256
- # @example Define nested optional parameters
257
- # class MyTask < CMDx::Task
258
- # optional :user, type: :hash do
259
- # required :name, type: :string
260
- # optional :age, type: :integer
261
- # end
262
- # end
263
- def optional(*attributes, **options, &)
264
- parameters = Parameter.optional(*attributes, **options.merge(klass: self), &)
265
- cmd_parameters.registry.concat(parameters)
112
+ # @example
113
+ # attributes :name, :email
114
+ # attributes :age, type: Integer, default: 18
115
+ def attributes(...)
116
+ register(:attribute, Attribute.build(...))
266
117
  end
118
+ alias attribute attributes
267
119
 
268
- # Defines required parameters for the task with validation and coercion.
269
- #
270
- # Creates parameter definitions that must be provided for successful task
271
- # execution. Missing required parameters will cause task validation to fail.
272
- # Supports nested parameter structures through block syntax.
273
- #
274
- # @param attributes [Array<Symbol>] parameter names to define as required
275
- # @param options [Hash] parameter configuration options
276
- # @option options [Symbol, Array<Symbol>] :type parameter type(s) for coercion
277
- # @option options [Object] :default default value if parameter not provided
278
- # @option options [Hash] :validates validation rules to apply
279
- # @param block [Proc] optional block for defining nested parameters
120
+ # @param args [Array] Arguments to build the optional attribute with
280
121
  #
281
- # @return [Array<Parameter>] the created parameter definitions
282
- #
283
- # @example Define simple required parameters
284
- # class MyTask < CMDx::Task
285
- # required :user_id, type: :integer
286
- # required :action, type: :string
287
- # end
288
- #
289
- # @example Define required parameter with validation
290
- # class MyTask < CMDx::Task
291
- # required :email, type: :string, validates: { format: /@/ }
292
- # end
293
- #
294
- # @example Define nested required parameters
295
- # class MyTask < CMDx::Task
296
- # required :payment, type: :hash do
297
- # required :amount, type: :big_decimal
298
- # required :currency, type: :string
299
- # optional :description, type: :string
300
- # end
301
- # end
302
- def required(*attributes, **options, &)
303
- parameters = Parameter.required(*attributes, **options.merge(klass: self), &)
304
- cmd_parameters.registry.concat(parameters)
122
+ # @example
123
+ # optional :description, :notes
124
+ # optional :priority, type: Symbol, default: :normal
125
+ def optional(...)
126
+ register(:attribute, Attribute.optional(...))
305
127
  end
306
128
 
307
- # Executes a task instance and returns the result without raising exceptions.
129
+ # @param args [Array] Arguments to build the required attribute with
308
130
  #
309
- # Creates a new task instance with the provided context, processes it through
310
- # the complete execution pipeline, and returns the result. This method will
311
- # not raise exceptions for task failures but will capture them in the result.
312
- #
313
- # @param args [Array] arguments passed to task constructor
314
- #
315
- # @return [Result] the execution result containing state, status, and metadata
131
+ # @example
132
+ # required :name, :email
133
+ # required :age, type: Integer, min: 0
134
+ def required(...)
135
+ register(:attribute, Attribute.required(...))
136
+ end
137
+
138
+ # @param names [Array<Symbol>] Names of attributes to remove
316
139
  #
317
- # @example Execute task
318
- # result = MyTask.call(user_id: 123, action: "process")
319
- # puts result.status #=> "success" or "failed" or "skipped"
320
- def call(...)
321
- instance = new(...)
322
- instance.process
323
- instance.result
140
+ # @example
141
+ # remove_attributes :old_field, :deprecated_field
142
+ def remove_attributes(*names)
143
+ deregister(:attribute, names)
324
144
  end
145
+ alias remove_attribute remove_attributes
325
146
 
326
- # Executes a task instance and returns the result, raising exceptions on failure.
147
+ CallbackRegistry::TYPES.each do |callback|
148
+ # @param callables [Array] Callable objects to register as callbacks
149
+ # @param options [Hash] Options for the callback registration
150
+ # @option options [Symbol] :priority Priority of the callback
151
+ # @option options [Boolean] :async Whether the callback should run asynchronously
152
+ # @param block [Proc] Block to register as a callback
153
+ #
154
+ # @example
155
+ # before { puts "before execution" }
156
+ # after :cleanup, priority: :high
157
+ # around ->(task) { task.logger.info("starting") }
158
+ define_method(callback) do |*callables, **options, &block|
159
+ register(:callback, callback, *callables, **options, &block)
160
+ end
161
+ end
162
+
163
+ # @param args [Array] Arguments to pass to the task constructor
327
164
  #
328
- # Creates a new task instance with the provided context, processes it through
329
- # the complete execution pipeline, and returns the result. This method will
330
- # raise appropriate fault exceptions if the task fails or is skipped.
165
+ # @return [Result] The execution result
331
166
  #
332
- # @param args [Array] arguments passed to task constructor
167
+ # @example
168
+ # result = MyTask.execute(name: "example")
169
+ # if result.success?
170
+ # puts "Task completed successfully"
171
+ # end
172
+ def execute(...)
173
+ task = new(...)
174
+ task.execute(raise: false)
175
+ task.result
176
+ end
177
+
178
+ # @param args [Array] Arguments to pass to the task constructor
333
179
  #
334
- # @return [Result] the execution result containing state, status, and metadata
180
+ # @return [Result] The execution result
335
181
  #
336
- # @raise [Failed] when task execution fails
337
- # @raise [Skipped] when task execution is skipped
182
+ # @raise [ExecutionError] If the task execution fails
338
183
  #
339
- # @example Execute task
340
- # begin
341
- # result = MyTask.call!(user_id: 123)
342
- # puts "Success: #{result.status}"
343
- # rescue CMDx::Failed => e
344
- # puts "Task failed: #{e.message}"
345
- # end
346
- def call!(...)
347
- instance = new(...)
348
- instance.process!
349
- instance.result
184
+ # @example
185
+ # result = MyTask.execute!(name: "example")
186
+ # # Will raise an exception if execution fails
187
+ def execute!(...)
188
+ task = new(...)
189
+ task.execute(raise: true)
190
+ task.result
350
191
  end
351
192
 
352
193
  end
353
194
 
354
- # Abstract method that must be implemented by task subclasses.
355
- #
356
- # This method contains the actual business logic for the task. Subclasses
357
- # must override this method to provide their specific implementation.
358
- # The method has access to the task's context, can modify it, and can
359
- # use skip!, fail!, or throw! to control execution flow.
195
+ # @param raise [Boolean] Whether to raise exceptions on failure
360
196
  #
361
- # @return [void]
197
+ # @return [Result] The execution result
362
198
  #
363
- # @raise [UndefinedCallError] always raised in the base Task class
364
- #
365
- # @example Implement in a subclass
366
- # class ProcessUserTask < CMDx::Task
367
- # required :user_id, type: :integer
368
- #
369
- # def call
370
- # user = User.find(context.user_id)
371
- # skip!(reason: "User already processed") if user.processed?
199
+ # @example
200
+ # result = task.execute
201
+ # result = task.execute(raise: true)
202
+ def execute(raise: false)
203
+ Worker.execute(self, raise:)
204
+ end
205
+
206
+ # @raise [UndefinedMethodError] Always raised as this method must be overridden
372
207
  #
373
- # user.process!
374
- # context.processed_at = Time.now
208
+ # @example
209
+ # class MyTask < Task
210
+ # def work
211
+ # # Custom work logic here
212
+ # puts "Performing work..."
375
213
  # end
376
214
  # end
377
- def call
378
- raise UndefinedCallError, "call method not defined in #{self.class.name}"
215
+ def work
216
+ raise UndefinedMethodError, "undefined method #{self.class.name}#work"
379
217
  end
380
218
 
381
- # Executes the task through the middleware pipeline without raising exceptions.
382
- #
383
- # Processes the task by running it through all registered middleware and
384
- # the TaskProcessor. This method captures exceptions and converts them
385
- # into result states rather than propagating them.
386
- #
387
- # @return [void]
219
+ # @return [Logger] The logger instance for this task
388
220
  #
389
- # @example Process a task instance
390
- # task = MyTask.new(data: "input")
391
- # task.process
392
- # puts task.result.status #=> "success", "failed", or "skipped"
393
- def process
394
- cmd_middlewares.call(self) { |task| TaskProcessor.call(task) }
221
+ # @example
222
+ # logger.info "Starting task execution"
223
+ # logger.error "Task failed", error: exception
224
+ def logger
225
+ @logger ||= begin
226
+ logger = self.class.settings[:logger] || CMDx.configuration.logger
227
+ logger.level = self.class.settings[:log_level] || logger.level
228
+ logger.formatter = self.class.settings[:log_formatter] || logger.formatter
229
+ logger
230
+ end
395
231
  end
396
232
 
397
- # Executes the task through the middleware pipeline, raising exceptions on failure.
398
- #
399
- # Processes the task by running it through all registered middleware and
400
- # the TaskProcessor. This method will raise appropriate fault exceptions
401
- # if the task fails or is skipped.
233
+ # @return [Hash] A hash representation of the task
402
234
  #
403
- # @return [void]
235
+ # @option return [Integer] :index The result index
236
+ # @option return [String] :chain_id The chain identifier
237
+ # @option return [String] :type The task type ("Task" or "Workflow")
238
+ # @option return [Array<Symbol>] :tags The task tags
239
+ # @option return [String] :class The task class name
240
+ # @option return [String] :id The task identifier
404
241
  #
405
- # @raise [Failed] when task execution fails
406
- # @raise [Skipped] when task execution is skipped
407
- #
408
- # @example Process a task instance with exception handling
409
- # task = RiskyTask.new(data: "input")
410
- # begin
411
- # task.process!
412
- # puts "Task completed successfully"
413
- # rescue CMDx::Failed => e
414
- # puts "Task failed: #{e.message}"
415
- # end
416
- def process!
417
- cmd_middlewares.call(self) { |task| TaskProcessor.call!(task) }
242
+ # @example
243
+ # task_hash = task.to_h
244
+ # puts "Task type: #{task_hash[:type]}"
245
+ # puts "Task tags: #{task_hash[:tags].join(', ')}"
246
+ def to_h
247
+ {
248
+ index: result.index,
249
+ chain_id: chain.id,
250
+ type: self.class.include?(Workflow) ? "Workflow" : "Task",
251
+ tags: self.class.settings[:tags],
252
+ class: self.class.name,
253
+ id:
254
+ }
418
255
  end
419
256
 
420
- # Creates a logger instance configured for this task.
421
- #
422
- # Returns a logger instance that is pre-configured with the task's
423
- # settings and context information for consistent logging throughout
424
- # task execution.
257
+ # @return [String] A string representation of the task
425
258
  #
426
- # @return [Logger] configured logger instance for this task
427
- #
428
- # @example Log task execution
429
- # def call
430
- # logger.info "Starting user processing"
431
- # # ... task logic ...
432
- # logger.info "User processing completed"
433
- # end
434
- def logger
435
- Logger.call(self)
259
+ # @example
260
+ # puts task.to_s
261
+ # # Output: "Task[MyTask] tags: [:important] id: abc123"
262
+ def to_s
263
+ Utils::Format.to_str(to_h)
436
264
  end
437
265
 
438
266
  end