railbox 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5e7c53ea3b3e9b5c1f7a67c19273456d4cedb978257834328db3b1a3e058e0bf
4
+ data.tar.gz: 2832aeeb9558208d4ba229828403459422fcd19ea765ac193b63067031746349
5
+ SHA512:
6
+ metadata.gz: 670dbf35a4f62c0c81a779bc5d1f2e4af414f13b6d72064fbfd0eedc1bfd5097617c7f6508b8bc2abe082dea5927e5da4dfa78a23dfca0e38dda92c9b77def7a
7
+ data.tar.gz: ffdd4cc4dd8bd242483ac8b61fecacb3a9b69104d3931c3cead162a6b0c7601e2dfa0381d74005facdf1967b533643b61c242b867029e8b6b6e04f5f3b33c10d
@@ -0,0 +1,19 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module Railbox
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+ source_root File.expand_path('templates', __dir__)
9
+
10
+ def create_migration_file
11
+ migration_template 'migration.rb.tt', "db/migrate/create_transactional_outbox.rb"
12
+ end
13
+
14
+ def self.next_migration_number(dirname)
15
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ module Railbox
2
+ module Exceptions
3
+ class QueueError < StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ module Railbox
2
+ module Exceptions
3
+ # Raised when provided options or arguments are invalid for outbox creation.
4
+ class ValidationError < StandardError; end
5
+ end
6
+ end
@@ -0,0 +1,33 @@
1
+ # Module to be included in service classes for adding queue processing support
2
+ # and an additional `outbox_entity` property.
3
+ #
4
+ # @example Usage
5
+ # class MyService
6
+ # include Railbox::Handler
7
+ # end
8
+ #
9
+ # MyService.enqueue(method: 'update', body: { key: 'value' })
10
+ #
11
+ # @!method outbox_entity
12
+ # @return [Object] Returns the current value of outbox_entity.
13
+ #
14
+ module Railbox
15
+ module Handler
16
+ def self.included(base)
17
+ base.extend(ClassMethods)
18
+ end
19
+
20
+ module ClassMethods
21
+ attr_accessor :outbox_entity
22
+
23
+ # Queues a request for asynchronous execution
24
+ # @param method [String] to be called (default: 'create')
25
+ # @param body [Hash] main payload for the handler
26
+ # @param opts [Hash] any additional options (e.g. relative_entity/meta)
27
+ #
28
+ def enqueue(method: 'create', body: {}, **opts)
29
+ HandlingQueue.enqueue(service: name, method: method, body: body, **opts)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ module Railbox
2
+ module HttpClient
3
+ class Faraday
4
+ class << self
5
+ def request(method, url, query = {}, body = {}, headers = {})
6
+ connection = ::Faraday.new do |config|
7
+ config.adapter :httpclient
8
+ config.request :json
9
+ config.response :json, parser_options: {symbol_keys: true}
10
+ config.response :raise_error
11
+
12
+ config.response :logger, Rails.logger, logger_options
13
+ end
14
+
15
+ connection.send(method, url) do |req|
16
+ req.params = query if query.present?
17
+ req.headers = headers if headers.present?
18
+ req.body = body if body.present? && method != :get
19
+ end
20
+ end
21
+
22
+ def logger_options
23
+ {
24
+ headers: {request: true, response: true},
25
+ bodies: {request: true, response: true},
26
+ errors: true,
27
+ log_level: :info
28
+ }
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ module Railbox
2
+ class TransactionalOutboxInterface
3
+
4
+ def initialize(record)
5
+ @record = record
6
+ end
7
+
8
+ extend Forwardable
9
+ def_delegators :@record,
10
+ :id, :action_type, :action_data, :status, :relative_entity,
11
+ :query, :body, :headers, :meta, :attempts, :retry_at, :failure_reasons,
12
+ :group
13
+ end
14
+ end
@@ -0,0 +1,50 @@
1
+ module Railbox
2
+ module Models
3
+ # == Schema Information
4
+ #
5
+ # Table name: transactional_outboxes
6
+ #
7
+ # id :bigint not null, primary key
8
+ # action_type :string not null # Type of action/event (class_method/http)
9
+ # action_data :jsonb not null # Action data or payload ({process_class: "MyService", process_method: "create"}/{method: "POST", url: "https://..."}) (default: {})
10
+ # status :string not null # Processing status (e.g. in_progress, failed, completed)
11
+ # entity_type :string # Polymorphic type for associated entity
12
+ # entity_id :integer # Polymorphic ID for associated entity
13
+ # query :jsonb # Url query (JSON)
14
+ # body :jsonb # Main body/payload (default: {})
15
+ # headers :jsonb # Headers (default: {})
16
+ # meta :jsonb # Metadata or extra info (JSON)
17
+ # attempts :integer default(0) # Number of processing attempts
18
+ # retry_at :datetime # Next retry timestamp
19
+ # failure_reasons :jsonb, array # Array of failure reason objects
20
+ # created_at :datetime not null # Record creation timestamp
21
+ # updated_at :datetime not null # Last update timestamp
22
+ #
23
+ # Indexes
24
+ #
25
+ # index_transactional_outboxes_on_entity_type_and_entity_id (entity_type,entity_id)
26
+ #
27
+ # Purpose:
28
+ # - Queues reliable actions/events for external systems.
29
+ # - Tracks delivery status and retry logic.
30
+ # - Stores metadata and failure reasons for auditing/debugging.
31
+ #
32
+ class TransactionalOutbox < ::ActiveRecord::Base
33
+ belongs_to :relative_entity, polymorphic: true, foreign_key: :entity_id, foreign_type: :entity_type
34
+
35
+ def in_group?
36
+ group.present? || entity_group.present?
37
+ end
38
+
39
+ def group
40
+ action_data[:group]
41
+ end
42
+
43
+ def entity_group
44
+ "#{entity_type}.#{entity_id}"
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+
@@ -0,0 +1,29 @@
1
+ module Railbox
2
+ module Mutators
3
+ class TransactionalOutboxMutator
4
+ class << self
5
+
6
+ def create(attributes)
7
+ TransactionalOutbox.create!(attributes)
8
+ end
9
+
10
+ def update(record, attributes)
11
+ record.assign_attributes(attributes)
12
+
13
+ if record.status == 'in_progress'
14
+ record.attempts += 1
15
+
16
+ if record.attempts >= Railbox.configuration.max_attempts
17
+ record.status = 'failed'
18
+ else
19
+ record.retry_at = (Railbox.configuration.retry_strategy[record.attempts - 1] || Railbox.configuration.retry_strategy.last).from_now
20
+ end
21
+ end
22
+
23
+ record.save!
24
+ record
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ module Railbox
2
+ module Processors
3
+ class HandlerProcessor
4
+ class << self
5
+
6
+ def process(record)
7
+ interface = Railbox::TransactionalOutboxInterface.new(record)
8
+ action_data = record.action_data.deep_symbolize_keys!
9
+ handler = Object.const_get(action_data[:class_name])
10
+ handler.outbox_entity = interface
11
+
12
+ handler.public_send(action_data[:method_name])
13
+
14
+ handler.outbox_entity = nil
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ module Railbox
2
+ module Processors
3
+ class HttpProcessor
4
+ class << self
5
+
6
+ def process(record)
7
+ action_data = record.action_data.deep_symbolize_keys!
8
+
9
+ Railbox::HttpClient::Faraday.request(action_data[:method_name],
10
+ action_data[:url],
11
+ record.query,
12
+ record.body,
13
+ record.headers)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,42 @@
1
+ module Railbox
2
+ module Queue
3
+ # Abstract base class for enqueuing tasks into the transactional outbox.
4
+ #
5
+ # This class is intended to be subclassed: descendants should implement their own logic for enqueuing tasks.
6
+ #
7
+ # Usage example:
8
+ # class MyQueue < Railbox::BaseQueue
9
+ # def self.enqueue(**opts)
10
+ # # your implementation here
11
+ # end
12
+ # end
13
+ #
14
+ # The .enqueue method **must** be implemented in each subclass.
15
+ # Calling .enqueue directly on BaseQueue will raise NotImplementedError.
16
+ #
17
+ class BaseQueue
18
+ class << self
19
+ # Abstract method for enqueuing a task into the outbox.
20
+ #
21
+ # Each subclass must override this method with specific logic.
22
+ #
23
+ # @param opts [Hash] Parameters required by a particular implementation.
24
+ # @raise [NotImplementedError] if the method is not overridden in a subclass.
25
+ #
26
+ def enqueue(**_)
27
+ raise NotImplementedError, 'You must implement this method'
28
+ end
29
+
30
+ private
31
+
32
+ # Creates a record in the transactional outbox with the given attributes.
33
+ #
34
+ # @param attributes [Hash] Record attributes
35
+ # @return [Railbox::TransactionalOutboxMutator] The created record
36
+ def to_queue(**attributes)
37
+ Railbox::TransactionalOutboxMutator.create(**attributes)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,61 @@
1
+ module Railbox
2
+ module Queue
3
+ # HandlingQueue is responsible for enqueuing "class method call" actions into the transactional outbox table.
4
+ #
5
+ # Usage example:
6
+ # Railbox::HandlingQueue.enqueue(
7
+ # service: "MyService",
8
+ # method: "perform_action",
9
+ # body: { key: "value" },
10
+ # headers: { ... },
11
+ # query: { ... },
12
+ # entity_type: "User",
13
+ # entity_id: 123,
14
+ # meta: { ... }
15
+ # )
16
+ #
17
+ # This method creates a Railbox::TransactionalOutboxMutator record to be processed by a background job later,
18
+ # enabling reliable, auditable, and decoupled invocation of class methods in your system.
19
+ #
20
+ # A ValidationError is raised if the given options are invalid (for example, missing class, method, or incorrect body format).
21
+ #
22
+ class HandlingQueue < BaseQueue
23
+ OPTIONS = %i[headers query relative_entity meta].freeze
24
+
25
+ class << self
26
+ # Enqueues a class method call operation for asynchronous processing via the transactional outbox.
27
+ #
28
+ # @param service [String] Name of the target service class (must exist).
29
+ # @param method [String, Symbol] Name of the public class method to call (default: 'create').
30
+ # @param body [Hash] The request payload (must be a Hash)
31
+ # @param opts [Hash] Optional parameters: headers, query, entity_type, entity_id, meta.
32
+ # @raise [ValidationError] If the service, method, or body are invalid.
33
+ # @return [Boolean] true if enqueuing succeeds.
34
+ #
35
+ def enqueue(service:, method: 'create', body: {}, **opts)
36
+ opts.deep_symbolize_keys!.slice!(*OPTIONS)
37
+ validate_options(service, method, body, **opts)
38
+
39
+ to_queue(
40
+ action_type: 'handler',
41
+ action_data: {class_name: service, method_name: method},
42
+ body: body,
43
+ status: 'in_progress',
44
+ **opts
45
+ )
46
+
47
+ true
48
+ end
49
+
50
+ private
51
+
52
+ def validate_options(service, method, body, **_)
53
+ raise ValidationError, "Service #{service} is not present" unless service.present?
54
+ raise ValidationError, "Service class #{service} is not defined" unless Object.const_defined?(service)
55
+ raise ValidationError, "Method #{method} for class #{service} is not defined" unless Object.const_get(service).respond_to?(method)
56
+ raise ValidationError, 'Body must be a Hash' unless body.is_a?(Hash)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,61 @@
1
+ module Railbox
2
+ module Queue
3
+ # HttpQueue is responsible for enqueuing HTTP request actions into the transactional outbox.
4
+ #
5
+ # Usage example:
6
+ # Railbox::HttpQueue.enqueue(
7
+ # url: "https://example.com/api",
8
+ # method: :post,
9
+ # body: { data: 1 },
10
+ # headers: { "Authorization" => "Bearer ..." },
11
+ # query: { foo: "bar" },
12
+ # meta: { correlation_id: "123" }
13
+ # )
14
+ #
15
+ # This creates a Railbox::TransactionalOutboxMutator record, which will be processed later by a background worker,
16
+ # providing reliable, traceable, and decoupled execution of HTTP requests.
17
+ #
18
+ # A ValidationError will be raised if the url, method, or body are invalid.
19
+ #
20
+ class HttpQueue < BaseQueue
21
+ OPTIONS = %i[query meta group].freeze
22
+ METHODS = %i[get post put patch delete].freeze
23
+
24
+ class << self
25
+ # Enqueues an HTTP request action for asynchronous processing via the transactional outbox.
26
+ #
27
+ # @param url [String] The HTTP endpoint URL (must begin with http:// or https://).
28
+ # @param method [Symbol] HTTP method to use (:get, :post, :put, :patch, :delete). Defaults to :post.
29
+ # @param body [Hash] Request payload (must be a Hash). Defaults to empty hash.
30
+ # @param headers [Hash] Optional HTTP headers.
31
+ # @param opts [Hash] Additional options: query, meta, group.
32
+ # @raise [ValidationError] If the url, method, or body are invalid.
33
+ # @return [Boolean] true if enqueuing is successful.
34
+ #
35
+ def enqueue(url:, method: :post, body: {}, headers: {}, **opts)
36
+ opts.deep_symbolize_keys!.slice!(*OPTIONS)
37
+ validate_options(url, method, body)
38
+
39
+ to_queue(
40
+ action_type: 'http_request',
41
+ action_data: {url: url, method_name: method},
42
+ body: body,
43
+ headers: headers,
44
+ status: 'in_progress',
45
+ **opts
46
+ )
47
+
48
+ true
49
+ end
50
+
51
+ private
52
+
53
+ def validate_options(url, method, body)
54
+ raise ValidationError, "Url #{url} is not valid" unless %r{\Ahttps?://[^\s/$.?#].\S*\z}i.match?(url)
55
+ raise ValidationError, "Invalid method: #{method}. Allowed methods are: #{METHODS.join(', ')}" unless METHODS.include?(method)
56
+ raise ValidationError, 'Body must be a Hash' unless body.is_a?(Hash)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,25 @@
1
+ module Railbox
2
+ module Workers
3
+ class BaseWorker < ::ActiveJob::Base
4
+ queue_as :default
5
+
6
+ def with_lock
7
+ lock_id = Zlib.crc32(self.class.name, 0)
8
+ lock_sql = "SELECT pg_try_advisory_lock(#{lock_id}) AS locked"
9
+
10
+ result = ActiveRecord::Base.connection.execute(lock_sql)
11
+ locked = result.first['locked'] unless result.nil?
12
+
13
+ unless locked
14
+ Rails.logger.info "Another #{self.class.name} work now."
15
+ return
16
+ end
17
+
18
+ yield
19
+
20
+ ensure
21
+ ActiveRecord::Base.connection.execute("SELECT pg_advisory_unlock(#{lock_id})")
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,93 @@
1
+ module Railbox
2
+ module Workers
3
+ class ProcessQueueWorker < BaseWorker
4
+ PROCESSORS = {
5
+ 'handler' => Processors::HandlerProcessor,
6
+ 'http_request' => Processors::HttpProcessor
7
+ }.freeze
8
+
9
+ def perform
10
+ with_lock do
11
+ grouped_records.each_value do |group|
12
+ group.each do |record|
13
+ process_record(record)
14
+ rescue => e
15
+ TransactionalOutboxMutator.update(record, {failure_reasons: record.failure_reasons << { message: e.message, backtrace: e.backtrace&.take(3), at: DateTime.current}})
16
+ raise e if record.in_group? || record.action_type == 'handler'
17
+
18
+ Rails.logger.error("RailboxWorker error: #{e.message}\n #{e.backtrace&.take(3)&.join("\n")}")
19
+
20
+ next
21
+ end
22
+ rescue => e
23
+ Rails.logger.error("RailboxWorker error: #{e.message}\n #{e.backtrace&.take(3)&.join("\n")}")
24
+ next
25
+ end
26
+ rescue => e
27
+ Rails.logger.error("RailboxWorker error: #{e.message}\n #{e.backtrace&.take(3)&.join("\n")}")
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def grouped_records
34
+ sql = <<-SQL.squish
35
+ SELECT t.*
36
+ FROM transactional_outboxes t
37
+ WHERE t.status = 'in_progress'
38
+ AND (t.retry_at IS NULL OR t.retry_at <= NOW())
39
+ AND NOT EXISTS (
40
+ SELECT 1
41
+ FROM transactional_outboxes f
42
+ WHERE f.status = 'failed'
43
+ AND (
44
+ (t.action_data ? 'group' AND (f.action_data->>'group') = (t.action_data->>'group'))
45
+ OR (
46
+ (NOT (t.action_data ? 'group') OR (t.action_data->>'group') IS NULL)
47
+ AND f.entity_type = t.entity_type
48
+ AND f.entity_id = t.entity_id
49
+ )
50
+ )
51
+ )
52
+ ORDER BY t.created_at
53
+ SQL
54
+
55
+ records = Railbox::TransactionalOutbox.find_by_sql(sql)
56
+
57
+ grouped = records.group_by do |record|
58
+ record.group.present? ? [:group, record.group] : [:entity, record.entity_group]
59
+ end
60
+
61
+ grouped.transform_values do |group_records|
62
+ key_record = group_records.first
63
+ scope = Railbox::TransactionalOutbox.where(status: 'in_progress')
64
+
65
+ if key_record.group.present?
66
+ scope = scope.where("action_data ->> 'group' = ?", key_record.group)
67
+ else
68
+ scope = scope.where(entity_type: key_record.entity_type, entity_id: key_record.entity_id)
69
+ end
70
+
71
+ all_group_records = scope.order(:created_at).to_a
72
+
73
+ group_records if all_group_records.first&.id.in?(group_records.map(&:id))
74
+ end.compact.reject { |_k, v| v.blank? }
75
+ end
76
+
77
+ def process_record(record)
78
+ Rails.logger.info("Start process with transactional outbox ID #{record.id}")
79
+ processor = PROCESSORS[record.action_type]
80
+
81
+ if processor
82
+ processor.process(record)
83
+ else
84
+ raise Railbox::QueueError, "Unknown action_type=#{record.action_type} for outbox #{record.id}"
85
+ end
86
+
87
+ TransactionalOutboxMutator.update(record, {status: 'completed'})
88
+
89
+ Rails.logger.info("Finish process with transactional outbox ID #{record.id}")
90
+ end
91
+ end
92
+ end
93
+ end
data/lib/railbox.rb ADDED
@@ -0,0 +1,59 @@
1
+ require 'active_record'
2
+ require 'active_job'
3
+
4
+ require 'faraday'
5
+ require 'faraday/httpclient'
6
+
7
+ require_relative 'railbox/exceptions/validation_error'
8
+ require_relative 'railbox/exceptions/queue_error'
9
+
10
+ require_relative 'railbox/models/transactional_outbox'
11
+ require_relative 'railbox/mutators/transactional_outbox_mutator'
12
+
13
+ require_relative 'railbox/interfaces/transactional_outbox_interface'
14
+
15
+ require_relative 'railbox/queue/base_queue'
16
+ require_relative 'railbox/queue/http_queue'
17
+ require_relative 'railbox/queue/handling_queue'
18
+
19
+ require_relative 'railbox/handler/handler'
20
+
21
+ require_relative 'railbox/processors/handler_processor'
22
+ require_relative 'railbox/processors/http_processor'
23
+
24
+ require_relative 'railbox/workers/base_worker'
25
+ require_relative 'railbox/workers/process_queue_worker'
26
+
27
+ require_relative 'railbox/http_client/faraday'
28
+
29
+ module Railbox
30
+ class << self
31
+ attr_writer :configuration
32
+
33
+ def configure
34
+ self.configuration ||= Configuration.new
35
+ yield(configuration)
36
+ end
37
+
38
+ def configuration
39
+ @configuration ||= Configuration.new
40
+ end
41
+ end
42
+
43
+
44
+ class Configuration
45
+ attr_accessor :max_attempts, :retry_strategy
46
+
47
+ def initialize
48
+ @max_attempts = 5
49
+ @retry_strategy = [1.minute, 10.minutes, 1.hour, 3.hours, 1.day]
50
+ end
51
+ end
52
+ end
53
+
54
+ Railbox::HttpQueue = Railbox::Queue::HttpQueue
55
+ Railbox::HandlingQueue = Railbox::Queue::HandlingQueue
56
+ Railbox::TransactionalOutbox = Railbox::Models::TransactionalOutbox
57
+ Railbox::TransactionalOutboxMutator = Railbox::Mutators::TransactionalOutboxMutator
58
+ Railbox::QueueError = Railbox::Exceptions::QueueError
59
+ Railbox::ValidationError = Railbox::Exceptions::ValidationError
metadata ADDED
@@ -0,0 +1,201 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: railbox
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Egor Beresnev
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activejob
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: activerecord
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '7.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '7.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: faraday
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '2.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '2.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: faraday-httpclient
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: forwardable
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 1.3.3
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 1.3.3
82
+ - !ruby/object:Gem::Dependency
83
+ name: bundler
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '2.0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '2.0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: factory_bot
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rake
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '13.0'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '13.0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: rspec
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '3.12'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '3.12'
138
+ - !ruby/object:Gem::Dependency
139
+ name: sqlite3
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ description: A gem for implementing the transactional outbox pattern with support
153
+ for HTTP requests and custom message pre-processing.
154
+ email:
155
+ - egor594bed@gmail.com
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - lib/generators/railbox/install/install_generator.rb
161
+ - lib/railbox.rb
162
+ - lib/railbox/exceptions/queue_error.rb
163
+ - lib/railbox/exceptions/validation_error.rb
164
+ - lib/railbox/handler/handler.rb
165
+ - lib/railbox/http_client/faraday.rb
166
+ - lib/railbox/interfaces/transactional_outbox_interface.rb
167
+ - lib/railbox/models/transactional_outbox.rb
168
+ - lib/railbox/mutators/transactional_outbox_mutator.rb
169
+ - lib/railbox/processors/handler_processor.rb
170
+ - lib/railbox/processors/http_processor.rb
171
+ - lib/railbox/queue/base_queue.rb
172
+ - lib/railbox/queue/handling_queue.rb
173
+ - lib/railbox/queue/http_queue.rb
174
+ - lib/railbox/workers/base_worker.rb
175
+ - lib/railbox/workers/process_queue_worker.rb
176
+ homepage: https://github.com/egor594bed/railbox
177
+ licenses:
178
+ - MIT
179
+ metadata:
180
+ homepage_uri: https://github.com/egor594bed/railbox
181
+ source_code_uri: https://github.com/egor594bed/railbox
182
+ changelog_uri: https://github.com/egor594bed/railbox/CHANGELOG.md
183
+ rubygems_mfa_required: 'true'
184
+ rdoc_options: []
185
+ require_paths:
186
+ - lib
187
+ required_ruby_version: !ruby/object:Gem::Requirement
188
+ requirements:
189
+ - - ">="
190
+ - !ruby/object:Gem::Version
191
+ version: 3.0.0
192
+ required_rubygems_version: !ruby/object:Gem::Requirement
193
+ requirements:
194
+ - - ">="
195
+ - !ruby/object:Gem::Version
196
+ version: '0'
197
+ requirements: []
198
+ rubygems_version: 3.6.7
199
+ specification_version: 4
200
+ summary: Reliable transactional outbox for background tasks and decoupled processing
201
+ test_files: []