cmdx 1.0.1 → 1.1.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.
- checksums.yaml +4 -4
- data/.cursor/prompts/rspec.md +20 -0
- data/.cursor/prompts/yardoc.md +8 -0
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +17 -2
- data/README.md +1 -1
- data/docs/basics/call.md +2 -2
- data/docs/basics/chain.md +1 -1
- data/docs/callbacks.md +3 -36
- data/docs/configuration.md +58 -12
- data/docs/interruptions/exceptions.md +1 -1
- data/docs/interruptions/faults.md +2 -2
- data/docs/logging.md +4 -4
- data/docs/middlewares.md +43 -43
- data/docs/parameters/coercions.md +49 -38
- data/docs/parameters/defaults.md +1 -1
- data/docs/parameters/validations.md +0 -39
- data/docs/testing.md +11 -12
- data/docs/workflows.md +4 -4
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +36 -56
- data/lib/cmdx/callback_registry.rb +82 -73
- data/lib/cmdx/chain.rb +65 -122
- data/lib/cmdx/chain_inspector.rb +22 -115
- data/lib/cmdx/chain_serializer.rb +17 -148
- data/lib/cmdx/coercion.rb +49 -0
- data/lib/cmdx/coercion_registry.rb +94 -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 +57 -171
- data/lib/cmdx/context.rb +22 -165
- 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 +40 -156
- data/lib/cmdx/error.rb +37 -202
- data/lib/cmdx/errors.rb +165 -202
- data/lib/cmdx/fault.rb +55 -158
- data/lib/cmdx/faults.rb +26 -137
- data/lib/cmdx/immutator.rb +22 -109
- data/lib/cmdx/lazy_struct.rb +103 -187
- 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 +20 -82
- data/lib/cmdx/logger_ansi.rb +18 -75
- data/lib/cmdx/logger_serializer.rb +24 -114
- data/lib/cmdx/middleware.rb +38 -60
- data/lib/cmdx/middleware_registry.rb +81 -77
- data/lib/cmdx/middlewares/correlate.rb +41 -226
- data/lib/cmdx/middlewares/timeout.rb +46 -185
- data/lib/cmdx/parameter.rb +120 -198
- data/lib/cmdx/parameter_evaluator.rb +231 -0
- data/lib/cmdx/parameter_inspector.rb +25 -56
- data/lib/cmdx/parameter_registry.rb +59 -84
- data/lib/cmdx/parameter_serializer.rb +23 -74
- data/lib/cmdx/railtie.rb +24 -107
- data/lib/cmdx/result.rb +254 -260
- data/lib/cmdx/result_ansi.rb +19 -85
- data/lib/cmdx/result_inspector.rb +27 -68
- data/lib/cmdx/result_logger.rb +18 -81
- data/lib/cmdx/result_serializer.rb +28 -132
- 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 +213 -425
- data/lib/cmdx/task_deprecator.rb +55 -0
- data/lib/cmdx/task_processor.rb +245 -0
- data/lib/cmdx/task_serializer.rb +22 -70
- 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 +13 -63
- data/lib/cmdx/utils/name_affix.rb +21 -71
- data/lib/cmdx/validator.rb +48 -0
- data/lib/cmdx/validator_registry.rb +86 -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 +46 -339
- 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 +34 -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
data/lib/cmdx/callback.rb
CHANGED
@@ -1,67 +1,47 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
|
5
|
-
# Base class for CMDx callbacks that provides lifecycle execution points.
|
4
|
+
# Base class for implementing callback functionality in task execution.
|
6
5
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# @example Basic callback implementation
|
13
|
-
# class LoggingCallback < CMDx::Callback
|
14
|
-
# def call(task, callback_type)
|
15
|
-
# puts "Executing #{callback_type} callback for #{task.class.name}"
|
16
|
-
# task.logger.info("Callback executed: #{callback_type}")
|
17
|
-
# end
|
18
|
-
# end
|
19
|
-
#
|
20
|
-
# @example Callback with initialization parameters
|
21
|
-
# class NotificationCallback < CMDx::Callback
|
22
|
-
# def initialize(channels)
|
23
|
-
# @channels = channels
|
24
|
-
# end
|
25
|
-
#
|
26
|
-
# def call(task, callback_type)
|
27
|
-
# return unless callback_type == :on_success
|
28
|
-
#
|
29
|
-
# @channels.each do |channel|
|
30
|
-
# NotificationService.send(channel, "Task #{task.class.name} completed")
|
31
|
-
# end
|
32
|
-
# end
|
33
|
-
# end
|
34
|
-
#
|
35
|
-
# @example Conditional callback execution
|
36
|
-
# class ErrorReportingCallback < CMDx::Callback
|
37
|
-
# def call(task, callback_type)
|
38
|
-
# return unless callback_type == :on_failure
|
39
|
-
# return unless task.result.failed?
|
40
|
-
#
|
41
|
-
# ErrorReporter.notify(
|
42
|
-
# task.errors.full_messages.join(", "),
|
43
|
-
# context: task.context.to_h
|
44
|
-
# )
|
45
|
-
# end
|
46
|
-
# end
|
47
|
-
#
|
48
|
-
# @see CallbackRegistry Callback management
|
49
|
-
# @see Task Callback integration
|
50
|
-
# @since 1.0.0
|
6
|
+
# Callbacks are executed at specific points during task lifecycle to
|
7
|
+
# provide hooks for custom behavior, logging, validation, or cleanup.
|
8
|
+
# All callback implementations must inherit from this class and implement
|
9
|
+
# the abstract call method.
|
51
10
|
class Callback
|
52
11
|
|
53
|
-
|
54
|
-
#
|
12
|
+
# Executes a callback by creating a new instance and calling it.
|
13
|
+
#
|
14
|
+
# @param task [Task] the task instance executing the callback
|
15
|
+
# @param type [Symbol] the callback type identifier
|
16
|
+
#
|
17
|
+
# @return [Object] the result of the callback execution
|
18
|
+
#
|
19
|
+
# @raise [UndefinedCallError] when the callback subclass doesn't implement call
|
20
|
+
#
|
21
|
+
# @example Execute a callback on a task
|
22
|
+
# MyCallback.call(task, :before)
|
23
|
+
def self.call(task, type)
|
24
|
+
new.call(task, type)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Abstract method that must be implemented by callback subclasses.
|
28
|
+
#
|
29
|
+
# This method contains the actual callback logic to be executed.
|
30
|
+
# Subclasses must override this method to provide their specific
|
31
|
+
# callback implementation.
|
32
|
+
#
|
33
|
+
# @param _task [Task] the task instance executing the callback
|
34
|
+
# @param _type [Symbol] the callback type identifier
|
35
|
+
#
|
36
|
+
# @return [Object] the result of the callback execution
|
55
37
|
#
|
56
|
-
#
|
57
|
-
# behavior. The method receives the task instance and the callback type
|
58
|
-
# being executed.
|
38
|
+
# @raise [UndefinedCallError] always raised in the base class
|
59
39
|
#
|
60
|
-
# @
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
def call(_task,
|
40
|
+
# @example Implement in a subclass
|
41
|
+
# def call(task, type)
|
42
|
+
# puts "Executing #{type} callback for #{task.class.name}"
|
43
|
+
# end
|
44
|
+
def call(_task, _type)
|
65
45
|
raise UndefinedCallError, "call method not defined in #{self.class.name}"
|
66
46
|
end
|
67
47
|
|
@@ -1,106 +1,115 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
|
5
|
-
# The CallbackRegistry collection provides a lifecycle callback system that executes
|
6
|
-
# registered callbacks at specific points during task execution. Callbacks can be
|
7
|
-
# conditionally executed based on task state and support both method references
|
8
|
-
# and callable objects.
|
4
|
+
# Registry for managing callback definitions and execution within tasks.
|
9
5
|
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
# callback_registry.each { |callback_name, callbacks| puts "#{callback_name}: #{callbacks}" }
|
27
|
-
#
|
28
|
-
# @see Callback Base callback execution class
|
29
|
-
# @see Task Task lifecycle callbacks
|
30
|
-
# @since 1.0.0
|
31
|
-
class CallbackRegistry < Hash
|
6
|
+
# This registry handles the registration and execution of callbacks at various
|
7
|
+
# points in the task lifecycle, including validation, execution, and outcome
|
8
|
+
# handling phases.
|
9
|
+
class CallbackRegistry
|
10
|
+
|
11
|
+
TYPES = [
|
12
|
+
:before_validation,
|
13
|
+
:after_validation,
|
14
|
+
:before_execution,
|
15
|
+
:after_execution,
|
16
|
+
:on_executed,
|
17
|
+
:on_good,
|
18
|
+
:on_bad,
|
19
|
+
*Result::STATUSES.map { |s| :"on_#{s}" },
|
20
|
+
*Result::STATES.map { |s| :"on_#{s}" }
|
21
|
+
].freeze
|
32
22
|
|
33
|
-
|
34
|
-
# Initializes a new CallbackRegistry.
|
23
|
+
# The internal hash storing callback definitions.
|
35
24
|
#
|
36
|
-
# @
|
25
|
+
# @return [Hash] hash containing callback type keys and callback definition arrays
|
26
|
+
attr_reader :registry
|
27
|
+
|
28
|
+
# Initializes a new callback registry.
|
37
29
|
#
|
38
|
-
# @
|
39
|
-
# registry = CallbackRegistry.new
|
30
|
+
# @param registry [Hash] initial registry hash with callback definitions
|
40
31
|
#
|
41
|
-
# @
|
42
|
-
#
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
32
|
+
# @return [CallbackRegistry] a new callback registry instance
|
33
|
+
#
|
34
|
+
# @example Creating an empty registry
|
35
|
+
# CallbackRegistry.new
|
36
|
+
#
|
37
|
+
# @example Creating a registry with initial callbacks
|
38
|
+
# CallbackRegistry.new(before_execution: [[:my_callback, {}]])
|
39
|
+
def initialize(registry = {})
|
40
|
+
@registry = registry.to_h
|
50
41
|
end
|
51
42
|
|
52
|
-
# Registers
|
43
|
+
# Registers one or more callbacks for a specific type.
|
53
44
|
#
|
54
|
-
# @param
|
55
|
-
# @param callables [Array<
|
56
|
-
# @param options [Hash]
|
57
|
-
# @
|
58
|
-
# @option options [Symbol, Proc, #call] :unless condition that must be falsy
|
59
|
-
# @param block [Proc] Block to execute as part of the callback
|
60
|
-
# @return [CallbackRegistry] self for method chaining
|
45
|
+
# @param type [Symbol] the callback type to register
|
46
|
+
# @param callables [Array<Object>] callable objects to register
|
47
|
+
# @param options [Hash] options for conditional callback execution
|
48
|
+
# @param block [Proc] optional block to register as a callback
|
61
49
|
#
|
62
|
-
# @
|
63
|
-
# registry.register(:before_validation, :check_permissions)
|
50
|
+
# @return [CallbackRegistry] returns self for method chaining
|
64
51
|
#
|
65
|
-
# @example
|
66
|
-
# registry.register(:
|
52
|
+
# @example Registering a symbol callback
|
53
|
+
# registry.register(:before_execution, :setup_database)
|
67
54
|
#
|
68
|
-
# @example
|
69
|
-
# registry.register(:
|
70
|
-
|
55
|
+
# @example Registering a Proc callback
|
56
|
+
# registry.register(:on_good, ->(task) { puts "Task completed: #{task.name}" })
|
57
|
+
#
|
58
|
+
# @example Registering a Callback class
|
59
|
+
# registry.register(:after_validation, NotificationCallback)
|
60
|
+
#
|
61
|
+
# @example Registering multiple callbacks with options
|
62
|
+
# registry.register(:on_good, :send_notification, :log_success, if: -> { Rails.env.production? })
|
63
|
+
#
|
64
|
+
# @example Registering a block callback
|
65
|
+
# registry.register(:after_validation) { |task| puts "Validation complete" }
|
66
|
+
def register(type, *callables, **options, &block)
|
71
67
|
callables << block if block_given?
|
72
|
-
(
|
68
|
+
(registry[type] ||= []).push([callables, options]).uniq!
|
73
69
|
self
|
74
70
|
end
|
75
71
|
|
76
|
-
# Executes all callbacks
|
77
|
-
#
|
72
|
+
# Executes all registered callbacks for a specific type.
|
73
|
+
#
|
74
|
+
# @param task [Task] the task instance to execute callbacks on
|
75
|
+
# @param type [Symbol] the callback type to execute
|
78
76
|
#
|
79
|
-
# @param task [Task] The task instance to execute callbacks on
|
80
|
-
# @param callback [Symbol] The callback type to execute (e.g., :before_validation, :on_success)
|
81
77
|
# @return [void]
|
82
78
|
#
|
83
|
-
# @
|
79
|
+
# @raise [UnknownCallbackError] when the callback type is not recognized
|
80
|
+
#
|
81
|
+
# @example Executing before_validation callbacks
|
84
82
|
# registry.call(task, :before_validation)
|
85
83
|
#
|
86
|
-
# @example
|
87
|
-
#
|
88
|
-
|
89
|
-
|
90
|
-
return unless key?(callback)
|
84
|
+
# @example Executing outcome callbacks
|
85
|
+
# registry.call(task, :on_good)
|
86
|
+
def call(task, type)
|
87
|
+
raise UnknownCallbackError, "unknown callback #{type}" unless TYPES.include?(type)
|
91
88
|
|
92
|
-
Array(
|
93
|
-
next unless task.
|
89
|
+
Array(registry[type]).each do |callables, options|
|
90
|
+
next unless task.cmdx_eval(options)
|
94
91
|
|
95
|
-
Array(callables).each do |
|
96
|
-
|
97
|
-
|
92
|
+
Array(callables).each do |callable|
|
93
|
+
case callable
|
94
|
+
when Symbol, String, Proc
|
95
|
+
task.cmdx_try(callable)
|
98
96
|
else
|
99
|
-
|
97
|
+
callable.call(task)
|
100
98
|
end
|
101
99
|
end
|
102
100
|
end
|
103
101
|
end
|
104
102
|
|
103
|
+
# Returns a hash representation of the registry.
|
104
|
+
#
|
105
|
+
# @return [Hash] a deep copy of the registry hash
|
106
|
+
#
|
107
|
+
# @example Getting registry contents
|
108
|
+
# registry.to_h
|
109
|
+
# # => { before_execution: [[:setup, {}]], on_good: [[:notify, { if: -> { true } }]] }
|
110
|
+
def to_h
|
111
|
+
registry.transform_values(&:dup)
|
112
|
+
end
|
113
|
+
|
105
114
|
end
|
106
115
|
end
|
data/lib/cmdx/chain.rb
CHANGED
@@ -1,79 +1,42 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
#
|
4
|
+
# Manages execution chains for task results with thread-local storage support.
|
5
5
|
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# @example Basic usage with automatic chain creation
|
12
|
-
# # Chain is automatically created when first task runs
|
13
|
-
# result1 = MyTask.call(data: "first")
|
14
|
-
# result2 = MyTask.call(data: "second")
|
15
|
-
#
|
16
|
-
# result1.chain.id == result2.chain.id #=> true
|
17
|
-
# result1.index #=> 0
|
18
|
-
# result2.index #=> 1
|
19
|
-
#
|
20
|
-
# @example Using custom chain ID
|
21
|
-
# chain = CMDx::Chain.new(id: "custom-correlation-123")
|
22
|
-
# CMDx::Chain.current = chain
|
23
|
-
#
|
24
|
-
# result = MyTask.call(data: "test")
|
25
|
-
# result.chain.id #=> "custom-correlation-123"
|
26
|
-
#
|
27
|
-
# @example Thread isolation
|
28
|
-
# # Each thread gets its own chain
|
29
|
-
# Thread.new do
|
30
|
-
# result = MyTask.call(data: "thread1")
|
31
|
-
# result.chain.id #=> unique ID for this thread
|
32
|
-
# end
|
33
|
-
#
|
34
|
-
# Thread.new do
|
35
|
-
# result = MyTask.call(data: "thread2")
|
36
|
-
# result.chain.id #=> different unique ID
|
37
|
-
# end
|
38
|
-
#
|
39
|
-
# @example Temporary chain context
|
40
|
-
# CMDx::Chain.use(id: "temp-correlation") do
|
41
|
-
# result = MyTask.call(data: "test")
|
42
|
-
# result.chain.id #=> "temp-correlation"
|
43
|
-
# end
|
44
|
-
# # Original chain is restored after block
|
45
|
-
#
|
46
|
-
# @see CMDx::Correlator
|
47
|
-
# @since 1.0.0
|
6
|
+
# Chain provides a mechanism to track and correlate multiple task executions
|
7
|
+
# within a single logical operation. It maintains a collection of results
|
8
|
+
# and provides thread-local storage for tracking the current execution chain.
|
9
|
+
# The chain automatically delegates common methods to its results collection
|
10
|
+
# and the first result for convenient access to execution state.
|
48
11
|
class Chain
|
49
12
|
|
50
|
-
# Thread-local storage key for the current chain
|
51
13
|
THREAD_KEY = :cmdx_correlation_chain
|
52
14
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
15
|
+
cmdx_attr_delegator :index, :first, :last, :size,
|
16
|
+
to: :results
|
17
|
+
cmdx_attr_delegator :state, :status, :outcome, :runtime,
|
18
|
+
to: :first
|
57
19
|
|
58
|
-
#
|
59
|
-
|
60
|
-
# @!attribute [r] results
|
61
|
-
# @return [Array<CMDx::Result>] the collection of task results in this chain
|
62
|
-
attr_reader :id, :results
|
20
|
+
# @return [String] the unique identifier for this chain
|
21
|
+
attr_reader :id
|
63
22
|
|
64
|
-
#
|
23
|
+
# @return [Array<CMDx::Result>] the collection of task results in this chain
|
24
|
+
attr_reader :results
|
25
|
+
|
26
|
+
# Creates a new execution chain with optional attributes.
|
27
|
+
#
|
28
|
+
# @param attributes [Hash] optional attributes for chain initialization
|
29
|
+
# @option attributes [String] :id custom chain identifier, defaults to current correlation ID or generates new one
|
65
30
|
#
|
66
|
-
# @
|
67
|
-
# @option attributes [String] :id custom identifier for the chain.
|
68
|
-
# If not provided, uses the current correlator ID or generates a new UUID.
|
31
|
+
# @return [Chain] the newly created chain instance
|
69
32
|
#
|
70
|
-
# @example Create chain with default ID
|
33
|
+
# @example Create a chain with default ID
|
71
34
|
# chain = CMDx::Chain.new
|
72
|
-
# chain.id
|
35
|
+
# chain.id #=> "generated-uuid"
|
73
36
|
#
|
74
|
-
# @example Create chain with custom ID
|
75
|
-
# chain = CMDx::Chain.new(id: "
|
76
|
-
# chain.id
|
37
|
+
# @example Create a chain with custom ID
|
38
|
+
# chain = CMDx::Chain.new(id: "custom-123")
|
39
|
+
# chain.id #=> "custom-123"
|
77
40
|
def initialize(attributes = {})
|
78
41
|
@id = attributes[:id] || CMDx::Correlator.id || CMDx::Correlator.generate
|
79
42
|
@results = []
|
@@ -81,53 +44,55 @@ module CMDx
|
|
81
44
|
|
82
45
|
class << self
|
83
46
|
|
84
|
-
#
|
47
|
+
# Gets the current execution chain from thread-local storage.
|
85
48
|
#
|
86
|
-
# @return [
|
49
|
+
# @return [Chain, nil] the current chain or nil if none is set
|
87
50
|
#
|
88
|
-
# @example
|
89
|
-
# CMDx::Chain.current
|
90
|
-
#
|
91
|
-
# MyTask.call(data: "test")
|
92
|
-
# CMDx::Chain.current #=> #<CMDx::Chain:0x... @id="018c2b95...">
|
51
|
+
# @example Access current chain
|
52
|
+
# chain = CMDx::Chain.current
|
53
|
+
# chain.id if chain #=> "current-chain-id"
|
93
54
|
def current
|
94
55
|
Thread.current[THREAD_KEY]
|
95
56
|
end
|
96
57
|
|
97
|
-
# Sets the current thread-local
|
58
|
+
# Sets the current execution chain in thread-local storage.
|
59
|
+
#
|
60
|
+
# @param chain [Chain, nil] the chain to set as current
|
98
61
|
#
|
99
|
-
# @
|
100
|
-
# @return [CMDx::Chain, nil] the chain that was set
|
62
|
+
# @return [Chain, nil] the chain that was set
|
101
63
|
#
|
102
|
-
# @example
|
103
|
-
#
|
104
|
-
# CMDx::Chain.current =
|
105
|
-
# CMDx::Chain.current.id
|
64
|
+
# @example Set current chain
|
65
|
+
# new_chain = CMDx::Chain.new
|
66
|
+
# CMDx::Chain.current = new_chain
|
67
|
+
# CMDx::Chain.current.id #=> new_chain.id
|
106
68
|
def current=(chain)
|
107
69
|
Thread.current[THREAD_KEY] = chain
|
108
70
|
end
|
109
71
|
|
110
|
-
# Clears the current thread-local
|
72
|
+
# Clears the current execution chain from thread-local storage.
|
111
73
|
#
|
112
|
-
# @return [nil]
|
74
|
+
# @return [nil] always returns nil
|
113
75
|
#
|
114
|
-
# @example
|
115
|
-
# CMDx::Chain.current #=> #<CMDx::Chain:0x...>
|
76
|
+
# @example Clear current chain
|
116
77
|
# CMDx::Chain.clear
|
117
|
-
# CMDx::Chain.current
|
78
|
+
# CMDx::Chain.current #=> nil
|
118
79
|
def clear
|
119
80
|
Thread.current[THREAD_KEY] = nil
|
120
81
|
end
|
121
82
|
|
122
|
-
#
|
83
|
+
# Builds or extends the current execution chain with a new result.
|
123
84
|
#
|
124
|
-
#
|
125
|
-
# and should not be used directly in application code.
|
85
|
+
# @param result [CMDx::Result] the result to add to the chain
|
126
86
|
#
|
127
|
-
# @
|
128
|
-
# @return [CMDx::Chain] the chain containing the result
|
87
|
+
# @return [Chain] the current chain with the result added
|
129
88
|
#
|
130
|
-
# @
|
89
|
+
# @raise [TypeError] if result is not a Result instance
|
90
|
+
#
|
91
|
+
# @example Build chain with result
|
92
|
+
# task = MyTask.new
|
93
|
+
# result = CMDx::Result.new(task)
|
94
|
+
# chain = CMDx::Chain.build(result)
|
95
|
+
# chain.results.size #=> 1
|
131
96
|
def build(result)
|
132
97
|
raise TypeError, "must be a Result" unless result.is_a?(Result)
|
133
98
|
|
@@ -138,50 +103,28 @@ module CMDx
|
|
138
103
|
|
139
104
|
end
|
140
105
|
|
141
|
-
# Converts the chain to a hash representation.
|
142
|
-
#
|
143
|
-
# Serializes the chain and all its results into a structured hash
|
144
|
-
# suitable for logging, debugging, and data interchange.
|
106
|
+
# Converts the chain to a hash representation using the serializer.
|
145
107
|
#
|
146
|
-
# @return [Hash]
|
108
|
+
# @return [Hash] serialized hash representation of the chain
|
147
109
|
#
|
148
|
-
# @example
|
149
|
-
# chain.to_h
|
150
|
-
# # => {
|
151
|
-
# # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
152
|
-
# # state: "complete",
|
153
|
-
# # status: "success",
|
154
|
-
# # outcome: "success",
|
155
|
-
# # runtime: 0.5,
|
156
|
-
# # results: [
|
157
|
-
# # { class: "ProcessOrderTask", state: "complete", status: "success", ... },
|
158
|
-
# # { class: "SendEmailTask", state: "complete", status: "success", ... }
|
159
|
-
# # ]
|
160
|
-
# # }
|
110
|
+
# @example Convert to hash
|
111
|
+
# chain.to_h #=> { id: "abc123", results: [...], state: "complete" }
|
161
112
|
def to_h
|
162
113
|
ChainSerializer.call(self)
|
163
114
|
end
|
164
115
|
alias to_a to_h
|
165
116
|
|
166
|
-
# Converts the chain to a string representation
|
167
|
-
#
|
168
|
-
# Creates a comprehensive, human-readable summary of the chain including
|
169
|
-
# all task results with formatted headers and footers.
|
117
|
+
# Converts the chain to a formatted string representation.
|
170
118
|
#
|
171
|
-
# @return [String]
|
119
|
+
# @return [String] formatted string representation of the chain
|
172
120
|
#
|
173
|
-
# @example
|
174
|
-
# chain.to_s
|
175
|
-
# #
|
176
|
-
# #
|
177
|
-
# #
|
178
|
-
# #
|
179
|
-
# #
|
180
|
-
# # SendEmailTask: index=1 state=complete status=success ...
|
181
|
-
# #
|
182
|
-
# # ================================================
|
183
|
-
# # state: complete | status: success | outcome: success | runtime: 0.5
|
184
|
-
# # "
|
121
|
+
# @example Convert to string
|
122
|
+
# puts chain.to_s
|
123
|
+
# # chain: abc123
|
124
|
+
# # ===================
|
125
|
+
# # {...}
|
126
|
+
# # ===================
|
127
|
+
# # state: complete | status: success | outcome: success | runtime: 0.001
|
185
128
|
def to_s
|
186
129
|
ChainInspector.call(self)
|
187
130
|
end
|