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
@@ -1,106 +1,113 @@
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
- # The CallbackRegistry collection extends Hash to provide specialized functionality for
11
- # managing collections of callback definitions within CMDx tasks. It handles
12
- # callback registration, conditional execution, and inspection.
13
- #
14
- # @example Basic callback usage
15
- # callback_registry = CallbackRegistry.new
16
- # callback_registry.register(:before_validation, :check_permissions)
17
- # callback_registry.register(:on_success, :log_success, if: :important?)
18
- # callback_registry.register(:on_failure, proc { alert_admin }, unless: :test_env?)
19
- #
20
- # callback_registry.call(task, :before_validation)
21
- #
22
- # @example Hash-like operations
23
- # callback_registry[:before_validation] = [[:check_permissions, {}]]
24
- # callback_registry.keys # => [:before_validation]
25
- # callback_registry.empty? # => false
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
+ # @return [Hash] hash containing callback type keys and callback definition arrays
24
+ attr_reader :registry
25
+
26
+ # Initializes a new callback registry.
35
27
  #
36
- # @param registry [CallbackRegistry, Hash, nil] Optional registry to copy from
28
+ # @param registry [Hash] initial registry hash with callback definitions
37
29
  #
38
- # @example Initialize empty registry
39
- # registry = CallbackRegistry.new
30
+ # @return [CallbackRegistry] a new callback registry instance
40
31
  #
41
- # @example Initialize with existing registry
42
- # global_callbacks = CallbackRegistry.new
43
- # task_callbacks = CallbackRegistry.new(global_callbacks)
44
- def initialize(registry = nil)
45
- super()
46
-
47
- registry&.each do |callback_type, callback_definitions|
48
- self[callback_type] = callback_definitions.dup
49
- end
32
+ # @example Creating an empty registry
33
+ # CallbackRegistry.new
34
+ #
35
+ # @example Creating a registry with initial callbacks
36
+ # CallbackRegistry.new(before_execution: [[:my_callback, {}]])
37
+ def initialize(registry = {})
38
+ @registry = registry.to_h
50
39
  end
51
40
 
52
- # Registers a callback for the given callback type.
41
+ # Registers one or more callbacks for a specific type.
53
42
  #
54
- # @param callback [Symbol] The callback type (e.g., :before_validation, :on_success)
55
- # @param callables [Array<Symbol, Proc, #call>] Methods or callables to execute
56
- # @param options [Hash] Conditions for callback execution
57
- # @option options [Symbol, Proc, #call] :if condition that must be truthy
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
43
+ # @param type [Symbol] the callback type to register
44
+ # @param callables [Array<Object>] callable objects to register
45
+ # @param options [Hash] options for conditional callback execution
46
+ # @param block [Proc] optional block to register as a callback
61
47
  #
62
- # @example Register method callback
63
- # registry.register(:before_validation, :check_permissions)
48
+ # @return [CallbackRegistry] returns self for method chaining
64
49
  #
65
- # @example Register conditional callback
66
- # registry.register(:on_failure, :alert_admin, if: :critical?)
50
+ # @example Registering a symbol callback
51
+ # registry.register(:before_execution, :setup_database)
67
52
  #
68
- # @example Register proc callback
69
- # registry.register(:on_success, proc { log_completion })
70
- def register(callback, *callables, **options, &block)
53
+ # @example Registering a Proc callback
54
+ # registry.register(:on_good, ->(task) { puts "Task completed: #{task.name}" })
55
+ #
56
+ # @example Registering a Callback class
57
+ # registry.register(:after_validation, NotificationCallback)
58
+ #
59
+ # @example Registering multiple callbacks with options
60
+ # registry.register(:on_good, :send_notification, :log_success, if: -> { Rails.env.production? })
61
+ #
62
+ # @example Registering a block callback
63
+ # registry.register(:after_validation) { |task| puts "Validation complete" }
64
+ def register(type, *callables, **options, &block)
71
65
  callables << block if block_given?
72
- (self[callback] ||= []).push([callables, options]).uniq!
66
+ (registry[type] ||= []).push([callables, options]).uniq!
73
67
  self
74
68
  end
75
69
 
76
- # Executes all callbacks registered for a specific callback type on the given task.
77
- # Each callback is evaluated for its conditions (if/unless) before execution.
70
+ # Executes all registered callbacks for a specific type.
71
+ #
72
+ # @param task [Task] the task instance to execute callbacks on
73
+ # @param type [Symbol] the callback type to execute
78
74
  #
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
75
  # @return [void]
82
76
  #
83
- # @example Execute callbacks
77
+ # @raise [UnknownCallbackError] when the callback type is not recognized
78
+ #
79
+ # @example Executing before_validation callbacks
84
80
  # registry.call(task, :before_validation)
85
81
  #
86
- # @example Execute conditional callbacks
87
- # # Only executes if task.critical? returns true
88
- # registry.call(task, :on_failure) # where registry has on_failure :alert, if: :critical?
89
- def call(task, callback)
90
- return unless key?(callback)
82
+ # @example Executing outcome callbacks
83
+ # registry.call(task, :on_good)
84
+ def call(task, type)
85
+ raise UnknownCallbackError, "unknown callback #{type}" unless TYPES.include?(type)
91
86
 
92
- Array(self[callback]).each do |callables, options|
93
- next unless task.__cmdx_eval(options)
87
+ Array(registry[type]).each do |callables, options|
88
+ next unless task.cmdx_eval(options)
94
89
 
95
- Array(callables).each do |c|
96
- if c.is_a?(Callback)
97
- c.call(task, callback)
90
+ Array(callables).each do |callable|
91
+ case callable
92
+ when Symbol, String, Proc
93
+ task.cmdx_try(callable)
98
94
  else
99
- task.__cmdx_try(c)
95
+ callable.call(task)
100
96
  end
101
97
  end
102
98
  end
103
99
  end
104
100
 
101
+ # Returns a hash representation of the registry.
102
+ #
103
+ # @return [Hash] a deep copy of the registry hash
104
+ #
105
+ # @example Getting registry contents
106
+ # registry.to_h
107
+ # #=> { before_execution: [[:setup, {}]], on_good: [[:notify, { if: -> { true } }]] }
108
+ def to_h
109
+ registry.transform_values(&:dup)
110
+ end
111
+
105
112
  end
106
113
  end
data/lib/cmdx/chain.rb CHANGED
@@ -1,79 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Thread-local chain that tracks task execution results within a correlation context.
4
+ # Manages execution chains for task results with thread-local storage support.
5
5
  #
6
- # A Chain represents a sequence of task executions that are logically related,
7
- # typically within the same request or operation flow. It provides thread-local
8
- # storage to ensure that tasks executing in the same thread share the same chain
9
- # while maintaining isolation across different threads.
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
- __cmdx_attr_delegator :index, :first, :last, :size,
54
- to: :results
55
- __cmdx_attr_delegator :state, :status, :outcome, :runtime,
56
- to: :first
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
- # @!attribute [r] id
59
- # @return [String] the unique identifier for this chain
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
- # Creates a new chain instance.
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
- # @param attributes [Hash] configuration options for the chain
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 #=> "018c2b95-b764-7615-a924-cc5b910ed1e5"
35
+ # chain.id #=> "generated-uuid"
73
36
  #
74
- # @example Create chain with custom ID
75
- # chain = CMDx::Chain.new(id: "user-session-123")
76
- # chain.id #=> "user-session-123"
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
- # Returns the current thread-local chain.
47
+ # Gets the current execution chain from thread-local storage.
85
48
  #
86
- # @return [CMDx::Chain, nil] the chain for the current thread, or nil if none exists
49
+ # @return [Chain, nil] the current chain or nil if none is set
87
50
  #
88
- # @example
89
- # CMDx::Chain.current #=> nil (no chain set)
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 chain.
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
- # @param chain [CMDx::Chain, nil] the chain to set for the current thread
100
- # @return [CMDx::Chain, nil] the chain that was set
62
+ # @return [Chain, nil] the chain that was set
101
63
  #
102
- # @example
103
- # chain = CMDx::Chain.new(id: "custom-id")
104
- # CMDx::Chain.current = chain
105
- # CMDx::Chain.current.id #=> "custom-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 chain.
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 #=> nil
78
+ # CMDx::Chain.current #=> nil
118
79
  def clear
119
80
  Thread.current[THREAD_KEY] = nil
120
81
  end
121
82
 
122
- # Adds a result to the current chain, creating a new chain if none exists.
83
+ # Builds or extends the current execution chain with a new result.
123
84
  #
124
- # This method is typically called internally by the task execution framework
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
- # @param result [CMDx::Result] the task result to add to the chain
128
- # @return [CMDx::Chain] the chain containing the result
87
+ # @return [Chain] the current chain with the result added
129
88
  #
130
- # @api private
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] Structured hash representation of the chain
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 for inspection.
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] Formatted chain summary with task details
119
+ # @return [String] formatted string representation of the chain
172
120
  #
173
- # @example
174
- # chain.to_s
175
- # # => "
176
- # # chain: 018c2b95-b764-7615-a924-cc5b910ed1e5
177
- # # ================================================
178
- # #
179
- # # ProcessOrderTask: index=0 state=complete status=success ...
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
@@ -1,137 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Chain inspection utility for generating comprehensive chain summaries.
4
+ # Provides formatted inspection and display functionality for chain execution results.
5
5
  #
6
- # The ChainInspector module provides functionality to convert Chain instances
7
- # into detailed, human-readable string representations. It creates formatted
8
- # summaries that include chain metadata, all task results, and summary statistics
9
- # with visual separators for easy debugging and monitoring.
10
- #
11
- # @example Basic chain inspection
12
- # result = ProcessOrderTask.call(order_id: 123)
13
- # chain = result.chain
14
- #
15
- # ChainInspector.call(chain)
16
- # # => "
17
- # # chain: 018c2b95-b764-7615-a924-cc5b910ed1e5
18
- # # ================================================
19
- # #
20
- # # {
21
- # # class: "ProcessOrderTask",
22
- # # type: "Task",
23
- # # index: 0,
24
- # # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
25
- # # tags: [],
26
- # # state: "complete",
27
- # # status: "success",
28
- # # outcome: "success",
29
- # # metadata: {},
30
- # # runtime: 0.5
31
- # # }
32
- # #
33
- # # ================================================
34
- # # state: complete | status: success | outcome: success | runtime: 0.5
35
- # # "
36
- #
37
- # @example Chain with multiple tasks
38
- # class ComplexTask < CMDx::Task
39
- # def call
40
- # SubTask1.call(context)
41
- # SubTask2.call(context)
42
- # end
43
- # end
44
- #
45
- # result = ComplexTask.call
46
- # ChainInspector.call(result.chain)
47
- # # => Shows formatted output with all three task results and summary
48
- #
49
- # @example Failed chain inspection
50
- # # When a chain contains failed tasks, the summary reflects the failure state
51
- # ChainInspector.call(failed_chain)
52
- # # => Shows all task results with failure information and failed summary
53
- #
54
- # @see CMDx::Chain Chain execution context and result tracking
55
- # @see CMDx::Result Individual result inspection via to_h
6
+ # ChainInspector creates human-readable string representations of execution chains,
7
+ # displaying chain metadata, individual task results, and execution summary information
8
+ # in a formatted layout. The inspector processes chain data to provide comprehensive
9
+ # debugging and monitoring output for task execution sequences.
56
10
  module ChainInspector
57
11
 
58
- # Keys to display in the chain summary footer.
59
- #
60
- # These keys represent the most important chain-level information
61
- # that should be displayed in the summary footer for quick reference.
62
12
  FOOTER_KEYS = %i[
63
13
  state status outcome runtime
64
14
  ].freeze
65
15
 
66
16
  module_function
67
17
 
68
- # Converts a Chain instance to a comprehensive string representation.
18
+ # Formats a chain into a human-readable inspection string with headers, results, and summary.
69
19
  #
70
- # Creates a formatted summary that includes:
71
- # - Chain header with unique ID
72
- # - Visual separator line
73
- # - Pretty-printed hash representation of each result
74
- # - Visual separator line
75
- # - Summary footer with key chain statistics
20
+ # Creates a comprehensive string representation of the execution chain including
21
+ # a header with the chain ID, formatted individual task results, and a footer
22
+ # summary with key execution metadata. The output uses visual separators for
23
+ # clear section delineation and consistent formatting.
76
24
  #
77
- # @param chain [CMDx::Chain] The chain instance to inspect
78
- # @return [String] Formatted chain summary with task details and statistics
25
+ # @param chain [Chain] the execution chain to format and inspect
79
26
  #
80
- # @example Single task chain
81
- # chain = SimpleTask.call.chain
82
- # ChainInspector.call(chain)
83
- # # => "
84
- # # chain: 018c2b95-b764-7615-a924-cc5b910ed1e5
85
- # # ================================================
86
- # #
87
- # # {
88
- # # class: "SimpleTask",
89
- # # type: "Task",
90
- # # index: 0,
91
- # # state: "complete",
92
- # # status: "success",
93
- # # outcome: "success",
94
- # # runtime: 0.1
95
- # # }
96
- # #
97
- # # ================================================
98
- # # state: complete | status: success | outcome: success | runtime: 0.1
99
- # # "
100
- #
101
- # @example Multi-task chain
102
- # class ParentTask < CMDx::Task
103
- # def call
104
- # ChildTask1.call(context)
105
- # ChildTask2.call(context)
106
- # end
107
- # end
108
- #
109
- # chain = ParentTask.call.chain
110
- # ChainInspector.call(chain)
111
- # # => "
112
- # # chain: 018c2b95-b764-7615-a924-cc5b910ed1e5
113
- # # ================================================
114
- # #
115
- # # { class: "ParentTask", index: 0, state: "complete", status: "success", ... }
116
- # # { class: "ChildTask1", index: 1, state: "complete", status: "success", ... }
117
- # # { class: "ChildTask2", index: 2, state: "complete", status: "success", ... }
118
- # #
119
- # # ================================================
120
- # # state: complete | status: success | outcome: success | runtime: 0.5
121
- # # "
27
+ # @return [String] formatted multi-line string representation of the chain execution
122
28
  #
123
- # @example Failed chain inspection
124
- # failed_chain = FailingTask.call.chain
125
- # ChainInspector.call(failed_chain)
126
- # # => "
127
- # # chain: 018c2b95-b764-7615-a924-cc5b910ed1e5
128
- # # ================================================
29
+ # @example Format a simple chain
30
+ # chain = MyWorkflow.call(user_id: 123)
31
+ # output = ChainInspector.call(chain.chain)
32
+ # puts output
33
+ # # =>
34
+ # # chain: abc123-def456-789
35
+ # # ===============================
129
36
  # #
130
- # # { class: "FailingTask", state: "interrupted", status: "failed", metadata: { reason: "Error" }, ... }
37
+ # # {:task=>"MyTask", :state=>"complete", :status=>"success"}
38
+ # # {:task=>"OtherTask", :state=>"complete", :status=>"success"}
131
39
  # #
132
- # # ================================================
133
- # # state: interrupted | status: failed | outcome: failed | runtime: 0.1
134
- # # "
40
+ # # ===============================
41
+ # # state: complete | status: success | outcome: good | runtime: 0.025
135
42
  def call(chain)
136
43
  header = "\nchain: #{chain.id}"
137
44
  footer = FOOTER_KEYS.map { |key| "#{key}: #{chain.send(key)}" }.join(" | ")