ruby_reactor 0.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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +98 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/README.md +570 -0
  6. data/Rakefile +12 -0
  7. data/documentation/DAG.md +457 -0
  8. data/documentation/README.md +123 -0
  9. data/documentation/async_reactors.md +369 -0
  10. data/documentation/composition.md +199 -0
  11. data/documentation/core_concepts.md +662 -0
  12. data/documentation/data_pipelines.md +224 -0
  13. data/documentation/examples/inventory_management.md +749 -0
  14. data/documentation/examples/order_processing.md +365 -0
  15. data/documentation/examples/payment_processing.md +654 -0
  16. data/documentation/getting_started.md +224 -0
  17. data/documentation/retry_configuration.md +357 -0
  18. data/lib/ruby_reactor/async_router.rb +91 -0
  19. data/lib/ruby_reactor/configuration.rb +41 -0
  20. data/lib/ruby_reactor/context.rb +169 -0
  21. data/lib/ruby_reactor/context_serializer.rb +164 -0
  22. data/lib/ruby_reactor/dependency_graph.rb +126 -0
  23. data/lib/ruby_reactor/dsl/compose_builder.rb +86 -0
  24. data/lib/ruby_reactor/dsl/map_builder.rb +112 -0
  25. data/lib/ruby_reactor/dsl/reactor.rb +151 -0
  26. data/lib/ruby_reactor/dsl/step_builder.rb +177 -0
  27. data/lib/ruby_reactor/dsl/template_helpers.rb +36 -0
  28. data/lib/ruby_reactor/dsl/validation_helpers.rb +35 -0
  29. data/lib/ruby_reactor/error/base.rb +16 -0
  30. data/lib/ruby_reactor/error/compensation_error.rb +8 -0
  31. data/lib/ruby_reactor/error/context_too_large_error.rb +11 -0
  32. data/lib/ruby_reactor/error/dependency_error.rb +8 -0
  33. data/lib/ruby_reactor/error/deserialization_error.rb +11 -0
  34. data/lib/ruby_reactor/error/input_validation_error.rb +29 -0
  35. data/lib/ruby_reactor/error/schema_version_error.rb +11 -0
  36. data/lib/ruby_reactor/error/step_failure_error.rb +18 -0
  37. data/lib/ruby_reactor/error/undo_error.rb +8 -0
  38. data/lib/ruby_reactor/error/validation_error.rb +8 -0
  39. data/lib/ruby_reactor/executor/compensation_manager.rb +79 -0
  40. data/lib/ruby_reactor/executor/graph_manager.rb +41 -0
  41. data/lib/ruby_reactor/executor/input_validator.rb +39 -0
  42. data/lib/ruby_reactor/executor/result_handler.rb +103 -0
  43. data/lib/ruby_reactor/executor/retry_manager.rb +156 -0
  44. data/lib/ruby_reactor/executor/step_executor.rb +319 -0
  45. data/lib/ruby_reactor/executor.rb +123 -0
  46. data/lib/ruby_reactor/map/collector.rb +65 -0
  47. data/lib/ruby_reactor/map/element_executor.rb +154 -0
  48. data/lib/ruby_reactor/map/execution.rb +60 -0
  49. data/lib/ruby_reactor/map/helpers.rb +67 -0
  50. data/lib/ruby_reactor/max_retries_exhausted_failure.rb +19 -0
  51. data/lib/ruby_reactor/reactor.rb +75 -0
  52. data/lib/ruby_reactor/retry_context.rb +92 -0
  53. data/lib/ruby_reactor/retry_queued_result.rb +26 -0
  54. data/lib/ruby_reactor/sidekiq_workers/map_collector_worker.rb +13 -0
  55. data/lib/ruby_reactor/sidekiq_workers/map_element_worker.rb +13 -0
  56. data/lib/ruby_reactor/sidekiq_workers/map_execution_worker.rb +15 -0
  57. data/lib/ruby_reactor/sidekiq_workers/worker.rb +55 -0
  58. data/lib/ruby_reactor/step/compose_step.rb +107 -0
  59. data/lib/ruby_reactor/step/map_step.rb +234 -0
  60. data/lib/ruby_reactor/step.rb +33 -0
  61. data/lib/ruby_reactor/storage/adapter.rb +51 -0
  62. data/lib/ruby_reactor/storage/configuration.rb +15 -0
  63. data/lib/ruby_reactor/storage/redis_adapter.rb +140 -0
  64. data/lib/ruby_reactor/template/base.rb +15 -0
  65. data/lib/ruby_reactor/template/element.rb +25 -0
  66. data/lib/ruby_reactor/template/input.rb +48 -0
  67. data/lib/ruby_reactor/template/result.rb +48 -0
  68. data/lib/ruby_reactor/template/value.rb +22 -0
  69. data/lib/ruby_reactor/validation/base.rb +26 -0
  70. data/lib/ruby_reactor/validation/input_validator.rb +62 -0
  71. data/lib/ruby_reactor/validation/schema_builder.rb +17 -0
  72. data/lib/ruby_reactor/version.rb +5 -0
  73. data/lib/ruby_reactor.rb +159 -0
  74. data/sig/ruby_reactor.rbs +4 -0
  75. metadata +178 -0
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyReactor
4
+ module Template
5
+ class Element < Base
6
+ attr_reader :map_name, :path
7
+
8
+ def initialize(map_name, path = nil)
9
+ super()
10
+ @map_name = map_name
11
+ @path = path
12
+ end
13
+
14
+ def resolve(_context)
15
+ # Element resolution happens inside MapStep, not generic resolve
16
+ # But if called in wrong context, raise error
17
+ raise RubyReactor::Error::DependencyError, "element() can only be used within a map argument mapping"
18
+ end
19
+
20
+ def inspect
21
+ "element(:#{@map_name}#{", #{@path.inspect}" if @path})"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyReactor
4
+ module Template
5
+ class Input < Base
6
+ attr_reader :name, :path
7
+
8
+ def initialize(name, path = nil)
9
+ super()
10
+ @name = name
11
+ @path = path
12
+ end
13
+
14
+ def resolve(context)
15
+ value = context.get_input(@name)
16
+ return nil if value.nil?
17
+
18
+ if @path
19
+ extract_path(value, @path)
20
+ else
21
+ value
22
+ end
23
+ end
24
+
25
+ def inspect
26
+ if @path
27
+ "input(:#{@name}, #{@path.inspect})"
28
+ else
29
+ "input(:#{@name})"
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def extract_path(value, path)
36
+ if path.is_a?(Symbol) && value.respond_to?(:[])
37
+ value[path]
38
+ elsif path.is_a?(String)
39
+ path.split(".").reduce(value) { |v, key| v&.send(:[], key) }
40
+ elsif path.is_a?(Array)
41
+ path.reduce(value) { |v, key| v&.send(:[], key) }
42
+ elsif value.respond_to?(path)
43
+ value.send(path)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyReactor
4
+ module Template
5
+ class Result < Base
6
+ attr_reader :step_name, :path
7
+
8
+ def initialize(step_name, path = nil)
9
+ super()
10
+ @step_name = step_name
11
+ @path = path
12
+ end
13
+
14
+ def resolve(context)
15
+ value = context.get_result(@step_name)
16
+ return nil if value.nil?
17
+
18
+ if @path
19
+ extract_path(value, @path)
20
+ else
21
+ value
22
+ end
23
+ end
24
+
25
+ def inspect
26
+ if @path
27
+ "result(:#{@step_name}, #{@path.inspect})"
28
+ else
29
+ "result(:#{@step_name})"
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def extract_path(value, path)
36
+ if path.is_a?(Symbol) && value.respond_to?(:[])
37
+ value[path]
38
+ elsif path.is_a?(String)
39
+ path.split(".").reduce(value) { |v, key| v&.send(:[], key) }
40
+ elsif path.is_a?(Array)
41
+ path.reduce(value) { |v, key| v&.send(:[], key) }
42
+ elsif value.respond_to?(path)
43
+ value.send(path)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyReactor
4
+ module Template
5
+ class Value < Base
6
+ attr_reader :value
7
+
8
+ def initialize(value)
9
+ super()
10
+ @value = value
11
+ end
12
+
13
+ def resolve(_context)
14
+ @value
15
+ end
16
+
17
+ def inspect
18
+ "value(#{@value.inspect})"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyReactor
4
+ module Validation
5
+ class Base
6
+ def self.call(data)
7
+ new.call(data)
8
+ end
9
+
10
+ def call(data)
11
+ raise NotImplementedError
12
+ end
13
+
14
+ protected
15
+
16
+ def success(value)
17
+ RubyReactor.Success(value)
18
+ end
19
+
20
+ def failure(errors)
21
+ error = RubyReactor::Error::InputValidationError.new(errors)
22
+ RubyReactor.Failure(error)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-validation"
4
+
5
+ module RubyReactor
6
+ module Validation
7
+ class InputValidator < Base
8
+ attr_reader :schema
9
+
10
+ def initialize(schema)
11
+ super()
12
+ @schema = schema
13
+ end
14
+
15
+ def call(data)
16
+ result = schema.call(data)
17
+
18
+ if result.success?
19
+ success(result.to_h)
20
+ else
21
+ failure(format_errors(result.errors))
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def format_errors(errors)
28
+ formatted = {}
29
+ flatten_errors(errors.to_h, formatted, [])
30
+ formatted
31
+ end
32
+
33
+ def flatten_errors(errors_hash, formatted, path)
34
+ errors_hash.each do |key, messages|
35
+ current_path = path + [key]
36
+
37
+ case messages
38
+ when Array
39
+ # This is a leaf node with error messages
40
+ flat_key = if current_path.size == 1
41
+ current_path.first.to_s
42
+ else
43
+ "#{current_path.first}#{current_path[1..].map { |k| "[#{k}]" }.join}"
44
+ end
45
+ formatted[flat_key.to_sym] = messages.join(", ")
46
+ when Hash
47
+ # This is a nested structure, recurse
48
+ flatten_errors(messages, formatted, current_path)
49
+ else
50
+ # Single message
51
+ flat_key = if current_path.size == 1
52
+ current_path.first.to_s
53
+ else
54
+ "#{current_path.first}#{current_path[1..].map { |k| "[#{k}]" }.join}"
55
+ end
56
+ formatted[flat_key.to_sym] = messages.to_s
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-validation"
4
+
5
+ module RubyReactor
6
+ module Validation
7
+ class SchemaBuilder
8
+ def self.build_from_block(&block)
9
+ Dry::Schema.Params(&block)
10
+ end
11
+
12
+ def self.build_contract_from_block(&block)
13
+ Class.new(Dry::Validation::Contract, &block).new
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyReactor
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zeitwerk"
4
+
5
+ # Load dry-validation if available (for validation features)
6
+ begin
7
+ require "dry-validation"
8
+ rescue LoadError
9
+ # dry-validation is optional, validation features won't be available
10
+ end
11
+
12
+ # Load sidekiq if available (for async features)
13
+ begin
14
+ require "sidekiq"
15
+ rescue LoadError
16
+ # sidekiq is optional, async features won't be available
17
+ end
18
+
19
+ loader = Zeitwerk::Loader.for_gem
20
+ loader.setup
21
+
22
+ module RubyReactor
23
+ # Success/Failure pattern for results
24
+ class Success
25
+ attr_reader :value
26
+
27
+ def initialize(value = nil)
28
+ @value = value
29
+ end
30
+
31
+ def success?
32
+ true
33
+ end
34
+
35
+ def failure?
36
+ false
37
+ end
38
+ end
39
+
40
+ class Failure
41
+ attr_reader :error, :retryable, :step_name, :inputs, :backtrace, :reactor_name, :step_arguments
42
+
43
+ # rubocop:disable Metrics/ParameterLists
44
+ def initialize(error, retryable: nil, step_name: nil, inputs: {}, backtrace: nil, redact_inputs: [],
45
+ reactor_name: nil, step_arguments: {})
46
+ # rubocop:enable Metrics/ParameterLists
47
+ @error = error
48
+ @retryable = if retryable.nil?
49
+ error.respond_to?(:retryable?) ? error.retryable? : true
50
+ else
51
+ retryable
52
+ end
53
+ @step_name = step_name
54
+ @reactor_name = reactor_name
55
+ @inputs = inputs
56
+ @step_arguments = step_arguments
57
+ @backtrace = backtrace || (error.respond_to?(:backtrace) ? error.backtrace : caller)
58
+ @redact_inputs = redact_inputs
59
+ end
60
+
61
+ def success?
62
+ false
63
+ end
64
+
65
+ def failure?
66
+ true
67
+ end
68
+
69
+ def retryable?
70
+ @retryable
71
+ end
72
+
73
+ def message
74
+ msg = []
75
+ header = "Error"
76
+ header += " in reactor '#{reactor_name}'" if reactor_name
77
+ header += " step '#{step_name}'" if step_name
78
+ header += ": #{error_message}"
79
+
80
+ msg << header
81
+
82
+ if inputs && !inputs.empty?
83
+ msg << "Inputs:"
84
+ inputs.each do |key, value|
85
+ val = @redact_inputs.include?(key) ? "[REDACTED]" : value.inspect
86
+ msg << " #{key}: #{val}"
87
+ end
88
+ end
89
+
90
+ if step_arguments && !step_arguments.empty?
91
+ msg << "Step Arguments:"
92
+ step_arguments.each do |key, value|
93
+ # We might want to redact step arguments too if they come from redacted inputs
94
+ # For now, let's assume if the input key matches a redacted input key, it should be redacted
95
+ # But step arguments have different names.
96
+ # We can't easily track redaction for step arguments without more metadata.
97
+ # For now, let's just display them.
98
+ msg << " #{key}: #{value.inspect}"
99
+ end
100
+ end
101
+
102
+ if backtrace
103
+ msg << "Backtrace:"
104
+ msg << backtrace.take(5).map { |line| " #{line}" }.join("\n")
105
+ end
106
+
107
+ msg.join("\n")
108
+ end
109
+
110
+ def to_s
111
+ message
112
+ end
113
+
114
+ private
115
+
116
+ def error_message
117
+ @error.respond_to?(:message) ? @error.message : @error.to_s
118
+ end
119
+ end
120
+
121
+ # Async result for background job execution
122
+ class AsyncResult
123
+ attr_reader :job_id, :intermediate_results
124
+
125
+ def initialize(job_id:, intermediate_results: {})
126
+ @job_id = job_id
127
+ @intermediate_results = intermediate_results
128
+ end
129
+
130
+ def async?
131
+ true
132
+ end
133
+
134
+ def success?
135
+ false
136
+ end
137
+
138
+ def failure?
139
+ false
140
+ end
141
+ end
142
+
143
+ # Global helper methods
144
+ def self.Success(value = nil)
145
+ Success.new(value)
146
+ end
147
+
148
+ def self.Failure(error, **kwargs)
149
+ Failure.new(error, **kwargs)
150
+ end
151
+
152
+ def self.configure
153
+ yield(Configuration.instance) if block_given?
154
+ end
155
+
156
+ def self.configuration
157
+ Configuration.instance
158
+ end
159
+ end
@@ -0,0 +1,4 @@
1
+ module RubyReactor
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,178 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_reactor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Artur
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-12-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-validation
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: redis
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sidekiq
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '7.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '7.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: zeitwerk
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.6'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.6'
69
+ description: Ruby Reactor implements the Saga pattern with compensation-based error
70
+ handling and DAG-based execution planning. It supports async execution via Sidekiq
71
+ and persistence via Redis.
72
+ email:
73
+ - 1930175+arturictus@users.noreply.github.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - ".rspec"
79
+ - ".rubocop.yml"
80
+ - CODE_OF_CONDUCT.md
81
+ - README.md
82
+ - Rakefile
83
+ - documentation/DAG.md
84
+ - documentation/README.md
85
+ - documentation/async_reactors.md
86
+ - documentation/composition.md
87
+ - documentation/core_concepts.md
88
+ - documentation/data_pipelines.md
89
+ - documentation/examples/inventory_management.md
90
+ - documentation/examples/order_processing.md
91
+ - documentation/examples/payment_processing.md
92
+ - documentation/getting_started.md
93
+ - documentation/retry_configuration.md
94
+ - lib/ruby_reactor.rb
95
+ - lib/ruby_reactor/async_router.rb
96
+ - lib/ruby_reactor/configuration.rb
97
+ - lib/ruby_reactor/context.rb
98
+ - lib/ruby_reactor/context_serializer.rb
99
+ - lib/ruby_reactor/dependency_graph.rb
100
+ - lib/ruby_reactor/dsl/compose_builder.rb
101
+ - lib/ruby_reactor/dsl/map_builder.rb
102
+ - lib/ruby_reactor/dsl/reactor.rb
103
+ - lib/ruby_reactor/dsl/step_builder.rb
104
+ - lib/ruby_reactor/dsl/template_helpers.rb
105
+ - lib/ruby_reactor/dsl/validation_helpers.rb
106
+ - lib/ruby_reactor/error/base.rb
107
+ - lib/ruby_reactor/error/compensation_error.rb
108
+ - lib/ruby_reactor/error/context_too_large_error.rb
109
+ - lib/ruby_reactor/error/dependency_error.rb
110
+ - lib/ruby_reactor/error/deserialization_error.rb
111
+ - lib/ruby_reactor/error/input_validation_error.rb
112
+ - lib/ruby_reactor/error/schema_version_error.rb
113
+ - lib/ruby_reactor/error/step_failure_error.rb
114
+ - lib/ruby_reactor/error/undo_error.rb
115
+ - lib/ruby_reactor/error/validation_error.rb
116
+ - lib/ruby_reactor/executor.rb
117
+ - lib/ruby_reactor/executor/compensation_manager.rb
118
+ - lib/ruby_reactor/executor/graph_manager.rb
119
+ - lib/ruby_reactor/executor/input_validator.rb
120
+ - lib/ruby_reactor/executor/result_handler.rb
121
+ - lib/ruby_reactor/executor/retry_manager.rb
122
+ - lib/ruby_reactor/executor/step_executor.rb
123
+ - lib/ruby_reactor/map/collector.rb
124
+ - lib/ruby_reactor/map/element_executor.rb
125
+ - lib/ruby_reactor/map/execution.rb
126
+ - lib/ruby_reactor/map/helpers.rb
127
+ - lib/ruby_reactor/max_retries_exhausted_failure.rb
128
+ - lib/ruby_reactor/reactor.rb
129
+ - lib/ruby_reactor/retry_context.rb
130
+ - lib/ruby_reactor/retry_queued_result.rb
131
+ - lib/ruby_reactor/sidekiq_workers/map_collector_worker.rb
132
+ - lib/ruby_reactor/sidekiq_workers/map_element_worker.rb
133
+ - lib/ruby_reactor/sidekiq_workers/map_execution_worker.rb
134
+ - lib/ruby_reactor/sidekiq_workers/worker.rb
135
+ - lib/ruby_reactor/step.rb
136
+ - lib/ruby_reactor/step/compose_step.rb
137
+ - lib/ruby_reactor/step/map_step.rb
138
+ - lib/ruby_reactor/storage/adapter.rb
139
+ - lib/ruby_reactor/storage/configuration.rb
140
+ - lib/ruby_reactor/storage/redis_adapter.rb
141
+ - lib/ruby_reactor/template/base.rb
142
+ - lib/ruby_reactor/template/element.rb
143
+ - lib/ruby_reactor/template/input.rb
144
+ - lib/ruby_reactor/template/result.rb
145
+ - lib/ruby_reactor/template/value.rb
146
+ - lib/ruby_reactor/validation/base.rb
147
+ - lib/ruby_reactor/validation/input_validator.rb
148
+ - lib/ruby_reactor/validation/schema_builder.rb
149
+ - lib/ruby_reactor/version.rb
150
+ - sig/ruby_reactor.rbs
151
+ homepage: https://github.com/arturictus/ruby_reactor
152
+ licenses: []
153
+ metadata:
154
+ allowed_push_host: https://rubygems.org
155
+ rubygems_mfa_required: 'true'
156
+ homepage_uri: https://github.com/arturictus/ruby_reactor
157
+ source_code_uri: https://github.com/arturictus/ruby_reactor
158
+ changelog_uri: https://github.com/arturictus/ruby_reactor/blob/main/CHANGELOG.md
159
+ post_install_message:
160
+ rdoc_options: []
161
+ require_paths:
162
+ - lib
163
+ required_ruby_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: 3.0.0
168
+ required_rubygems_version: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ requirements: []
174
+ rubygems_version: 3.4.19
175
+ signing_key:
176
+ specification_version: 4
177
+ summary: A dynamic, concurrent, dependency-resolving saga orchestrator for Ruby.
178
+ test_files: []