jetstream_bridge 2.10.0 → 3.0.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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +164 -0
  3. data/LICENSE +21 -0
  4. data/README.md +379 -0
  5. data/lib/generators/jetstream_bridge/health_check/health_check_generator.rb +65 -0
  6. data/lib/generators/jetstream_bridge/health_check/templates/health_controller.rb +38 -0
  7. data/lib/generators/jetstream_bridge/initializer/templates/jetstream_bridge.rb +58 -13
  8. data/lib/jetstream_bridge/consumer/consumer.rb +43 -0
  9. data/lib/jetstream_bridge/consumer/dlq_publisher.rb +4 -1
  10. data/lib/jetstream_bridge/consumer/inbox/inbox_message.rb +3 -1
  11. data/lib/jetstream_bridge/consumer/inbox/inbox_repository.rb +37 -31
  12. data/lib/jetstream_bridge/consumer/message_processor.rb +65 -31
  13. data/lib/jetstream_bridge/core/config.rb +35 -0
  14. data/lib/jetstream_bridge/core/connection.rb +80 -3
  15. data/lib/jetstream_bridge/core/connection_factory.rb +102 -0
  16. data/lib/jetstream_bridge/core/debug_helper.rb +107 -0
  17. data/lib/jetstream_bridge/core/duration.rb +8 -1
  18. data/lib/jetstream_bridge/core/retry_strategy.rb +135 -0
  19. data/lib/jetstream_bridge/errors.rb +39 -0
  20. data/lib/jetstream_bridge/models/event_envelope.rb +133 -0
  21. data/lib/jetstream_bridge/models/subject.rb +94 -0
  22. data/lib/jetstream_bridge/publisher/outbox_repository.rb +47 -28
  23. data/lib/jetstream_bridge/publisher/publisher.rb +12 -35
  24. data/lib/jetstream_bridge/railtie.rb +35 -1
  25. data/lib/jetstream_bridge/tasks/install.rake +99 -0
  26. data/lib/jetstream_bridge/topology/overlap_guard.rb +15 -1
  27. data/lib/jetstream_bridge/topology/stream.rb +15 -5
  28. data/lib/jetstream_bridge/version.rb +1 -1
  29. data/lib/jetstream_bridge.rb +65 -0
  30. metadata +51 -7
@@ -11,52 +11,71 @@ module JetstreamBridge
11
11
  end
12
12
 
13
13
  def find_or_build(event_id)
14
- ModelUtils.find_or_init_by_best(
14
+ record = ModelUtils.find_or_init_by_best(
15
15
  @klass,
16
16
  { event_id: event_id },
17
17
  { dedup_key: event_id } # fallback if app uses a different unique column
18
18
  )
19
+
20
+ # Lock the row to prevent concurrent processing
21
+ if record.persisted? && !record.new_record? && record.respond_to?(:lock!)
22
+ begin
23
+ record.lock!
24
+ rescue ActiveRecord::RecordNotFound
25
+ # Record was deleted between find and lock, create new
26
+ record = @klass.new
27
+ end
28
+ end
29
+
30
+ record
19
31
  end
20
32
 
21
33
  def already_sent?(record)
22
- record.respond_to?(:sent_at) && record.sent_at
34
+ (record.respond_to?(:sent_at) && record.sent_at) ||
35
+ (record.respond_to?(:status) && record.status == 'sent')
23
36
  end
24
37
 
25
38
  def persist_pre(record, subject, envelope)
26
- now = Time.now.utc
27
- event_id = envelope['event_id'].to_s
39
+ ActiveRecord::Base.transaction do
40
+ now = Time.now.utc
41
+ event_id = envelope['event_id'].to_s
28
42
 
29
- attrs = {
30
- event_id: event_id,
31
- subject: subject,
32
- payload: ModelUtils.json_dump(envelope),
33
- headers: ModelUtils.json_dump({ 'nats-msg-id' => event_id }),
34
- status: 'publishing',
35
- last_error: nil
36
- }
37
- attrs[:attempts] = 1 + (record.attempts || 0) if record.respond_to?(:attempts)
38
- attrs[:enqueued_at] = (record.enqueued_at || now) if record.respond_to?(:enqueued_at)
39
- attrs[:updated_at] = now if record.respond_to?(:updated_at)
43
+ attrs = {
44
+ event_id: event_id,
45
+ subject: subject,
46
+ payload: ModelUtils.json_dump(envelope),
47
+ headers: ModelUtils.json_dump({ 'nats-msg-id' => event_id }),
48
+ status: 'publishing',
49
+ last_error: nil
50
+ }
51
+ attrs[:attempts] = 1 + (record.attempts || 0) if record.respond_to?(:attempts)
52
+ attrs[:enqueued_at] = (record.enqueued_at || now) if record.respond_to?(:enqueued_at)
53
+ attrs[:updated_at] = now if record.respond_to?(:updated_at)
40
54
 
41
- ModelUtils.assign_known_attrs(record, attrs)
42
- record.save!
55
+ ModelUtils.assign_known_attrs(record, attrs)
56
+ record.save!
57
+ end
43
58
  end
44
59
 
45
60
  def persist_success(record)
46
- now = Time.now.utc
47
- attrs = { status: 'sent' }
48
- attrs[:sent_at] = now if record.respond_to?(:sent_at)
49
- attrs[:updated_at] = now if record.respond_to?(:updated_at)
50
- ModelUtils.assign_known_attrs(record, attrs)
51
- record.save!
61
+ ActiveRecord::Base.transaction do
62
+ now = Time.now.utc
63
+ attrs = { status: 'sent' }
64
+ attrs[:sent_at] = now if record.respond_to?(:sent_at)
65
+ attrs[:updated_at] = now if record.respond_to?(:updated_at)
66
+ ModelUtils.assign_known_attrs(record, attrs)
67
+ record.save!
68
+ end
52
69
  end
53
70
 
54
71
  def persist_failure(record, message)
55
- now = Time.now.utc
56
- attrs = { status: 'failed', last_error: message }
57
- attrs[:updated_at] = now if record.respond_to?(:updated_at)
58
- ModelUtils.assign_known_attrs(record, attrs)
59
- record.save!
72
+ ActiveRecord::Base.transaction do
73
+ now = Time.now.utc
74
+ attrs = { status: 'failed', last_error: message }
75
+ attrs[:updated_at] = now if record.respond_to?(:updated_at)
76
+ ModelUtils.assign_known_attrs(record, attrs)
77
+ record.save!
78
+ end
60
79
  end
61
80
 
62
81
  def persist_exception(record, error)
@@ -6,22 +6,15 @@ require_relative '../core/connection'
6
6
  require_relative '../core/logging'
7
7
  require_relative '../core/config'
8
8
  require_relative '../core/model_utils'
9
+ require_relative '../core/retry_strategy'
9
10
  require_relative 'outbox_repository'
10
11
 
11
12
  module JetstreamBridge
12
13
  # Publishes to "{env}.{app}.sync.{dest}".
13
14
  class Publisher
14
- DEFAULT_RETRIES = 2
15
- RETRY_BACKOFFS = [0.25, 1.0].freeze
16
-
17
- TRANSIENT_ERRORS = begin
18
- errs = [NATS::IO::Timeout, NATS::IO::Error]
19
- errs << NATS::IO::SocketTimeoutError if defined?(NATS::IO::SocketTimeoutError)
20
- errs.freeze
21
- end
22
-
23
- def initialize
15
+ def initialize(retry_strategy: nil)
24
16
  @jts = Connection.connect!
17
+ @retry_strategy = retry_strategy || PublisherRetryStrategy.new
25
18
  end
26
19
 
27
20
  # @return [Boolean]
@@ -33,7 +26,7 @@ module JetstreamBridge
33
26
  if JetstreamBridge.config.use_outbox
34
27
  publish_via_outbox(subject, envelope)
35
28
  else
36
- with_retries { do_publish?(subject, envelope) }
29
+ with_retries { publish_to_nats(subject, envelope) }
37
30
  end
38
31
  rescue StandardError => e
39
32
  log_error(false, e)
@@ -47,7 +40,7 @@ module JetstreamBridge
47
40
  raise ArgumentError, 'destination_app must be configured'
48
41
  end
49
42
 
50
- def do_publish?(subject, envelope)
43
+ def publish_to_nats(subject, envelope)
51
44
  headers = { 'nats-msg-id' => envelope['event_id'] }
52
45
 
53
46
  ack = @jts.publish(subject, Oj.dump(envelope, mode: :compat), header: headers)
@@ -76,7 +69,7 @@ module JetstreamBridge
76
69
  "Outbox model #{klass} is not an ActiveRecord model; publishing directly.",
77
70
  tag: 'JetstreamBridge::Publisher'
78
71
  )
79
- return with_retries { do_publish?(subject, envelope) }
72
+ return with_retries { publish_to_nats(subject, envelope) }
80
73
  end
81
74
 
82
75
  repo = OutboxRepository.new(klass)
@@ -93,7 +86,7 @@ module JetstreamBridge
93
86
 
94
87
  repo.persist_pre(record, subject, envelope)
95
88
 
96
- ok = with_retries { do_publish?(subject, envelope) }
89
+ ok = with_retries { publish_to_nats(subject, envelope) }
97
90
  ok ? repo.persist_success(record) : repo.persist_failure(record, 'Publish returned false')
98
91
  ok
99
92
  rescue StandardError => e
@@ -102,27 +95,11 @@ module JetstreamBridge
102
95
  end
103
96
  # ---- /Outbox path ----
104
97
 
105
- # Retry only on transient NATS IO errors
106
- def with_retries(retries = DEFAULT_RETRIES)
107
- attempts = 0
108
- begin
109
- yield
110
- rescue *TRANSIENT_ERRORS => e
111
- attempts += 1
112
- return log_error(false, e) if attempts > retries
113
-
114
- backoff(attempts, e)
115
- retry
116
- end
117
- end
118
-
119
- def backoff(attempts, error)
120
- delay = RETRY_BACKOFFS[attempts - 1] || RETRY_BACKOFFS.last
121
- Logging.warn(
122
- "Publish retry #{attempts} after #{error.class}: #{error.message}",
123
- tag: 'JetstreamBridge::Publisher'
124
- )
125
- sleep delay
98
+ # Retry using strategy pattern
99
+ def with_retries
100
+ @retry_strategy.execute(context: 'Publisher') { yield }
101
+ rescue RetryStrategy::RetryExhausted => e
102
+ log_error(false, e)
126
103
  end
127
104
 
128
105
  def log_error(val, exc)
@@ -1,17 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'core/model_codec_setup'
4
+ require_relative 'core/logging'
4
5
 
5
6
  module JetstreamBridge
6
7
  class Railtie < ::Rails::Railtie
7
- initializer 'jetstream_bridge.defer_model_tweaks' do
8
+ # Set up logger to use Rails.logger by default
9
+ initializer 'jetstream_bridge.logger', before: :initialize_logger do
10
+ JetstreamBridge.configure do |config|
11
+ config.logger ||= Rails.logger if defined?(Rails.logger)
12
+ end
13
+ end
14
+
15
+ # Load ActiveRecord model tweaks after ActiveRecord is loaded
16
+ initializer 'jetstream_bridge.active_record', after: 'active_record.initialize_database' do
8
17
  ActiveSupport.on_load(:active_record) do
9
18
  ActiveSupport::Reloader.to_prepare { JetstreamBridge::ModelCodecSetup.apply! }
10
19
  end
11
20
  end
12
21
 
22
+ # Validate configuration in development/test
23
+ initializer 'jetstream_bridge.validate_config', after: :load_config_initializers do |app|
24
+ if Rails.env.development? || Rails.env.test?
25
+ app.config.after_initialize do
26
+ begin
27
+ JetstreamBridge.config.validate! if JetstreamBridge.config.destination_app
28
+ rescue JetstreamBridge::ConfigurationError => e
29
+ Rails.logger.warn "[JetStream Bridge] Configuration warning: #{e.message}"
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ # Add console helper methods
36
+ console do
37
+ Rails.logger.info "[JetStream Bridge] Loaded v#{JetstreamBridge::VERSION}"
38
+ Rails.logger.info "[JetStream Bridge] Use JetstreamBridge.health_check to check status"
39
+ end
40
+
41
+ # Load rake tasks
13
42
  rake_tasks do
14
43
  load File.expand_path('tasks/install.rake', __dir__)
15
44
  end
45
+
46
+ # Add generators
47
+ generators do
48
+ require 'generators/jetstream_bridge/health_check/health_check_generator'
49
+ end
16
50
  end
17
51
  end
@@ -7,4 +7,103 @@ namespace :jetstream_bridge do
7
7
  Rails::Generators.invoke('jetstream_bridge:install', [], behavior: :invoke, destination_root: Rails.root.to_s)
8
8
  puts '[jetstream_bridge] Done.'
9
9
  end
10
+
11
+ desc 'Check health and connection status'
12
+ task health: :environment do
13
+ require 'json'
14
+
15
+ puts '=' * 70
16
+ puts 'JetStream Bridge Health Check'
17
+ puts '=' * 70
18
+
19
+ health = JetstreamBridge.health_check
20
+
21
+ puts "\nStatus: #{health[:healthy] ? '✓ HEALTHY' : '✗ UNHEALTHY'}"
22
+ puts "NATS Connected: #{health[:nats_connected] ? 'Yes' : 'No'}"
23
+ puts "Connected At: #{health[:connected_at] || 'N/A'}"
24
+ puts "Version: #{health[:version]}"
25
+
26
+ if health[:stream]
27
+ puts "\nStream:"
28
+ puts " Name: #{health[:stream][:name]}"
29
+ puts " Exists: #{health[:stream][:exists] ? 'Yes' : 'No'}"
30
+ if health[:stream][:subjects]
31
+ puts " Subjects: #{health[:stream][:subjects].join(', ')}"
32
+ puts " Messages: #{health[:stream][:messages]}"
33
+ end
34
+ puts " Error: #{health[:stream][:error]}" if health[:stream][:error]
35
+ end
36
+
37
+ if health[:config]
38
+ puts "\nConfiguration:"
39
+ puts " Environment: #{health[:config][:env]}"
40
+ puts " App Name: #{health[:config][:app_name]}"
41
+ puts " Destination: #{health[:config][:destination_app] || 'NOT SET'}"
42
+ puts " Outbox: #{health[:config][:use_outbox] ? 'Enabled' : 'Disabled'}"
43
+ puts " Inbox: #{health[:config][:use_inbox] ? 'Enabled' : 'Disabled'}"
44
+ puts " DLQ: #{health[:config][:use_dlq] ? 'Enabled' : 'Disabled'}"
45
+ end
46
+
47
+ if health[:error]
48
+ puts "\n#{' ERROR '.center(70, '=')}"
49
+ puts health[:error]
50
+ end
51
+
52
+ puts '=' * 70
53
+
54
+ exit(health[:healthy] ? 0 : 1)
55
+ end
56
+
57
+ desc 'Validate configuration'
58
+ task validate: :environment do
59
+ puts '[jetstream_bridge] Validating configuration...'
60
+
61
+ begin
62
+ JetstreamBridge.config.validate!
63
+ puts '✓ Configuration is valid'
64
+ puts "\nCurrent settings:"
65
+ puts " Environment: #{JetstreamBridge.config.env}"
66
+ puts " App Name: #{JetstreamBridge.config.app_name}"
67
+ puts " Destination: #{JetstreamBridge.config.destination_app}"
68
+ puts " Stream: #{JetstreamBridge.config.stream_name}"
69
+ puts " Source Subject: #{JetstreamBridge.config.source_subject}"
70
+ puts " Destination Subject: #{JetstreamBridge.config.destination_subject}"
71
+ exit 0
72
+ rescue JetstreamBridge::ConfigurationError => e
73
+ puts "✗ Configuration error: #{e.message}"
74
+ exit 1
75
+ end
76
+ end
77
+
78
+ desc 'Show debug information'
79
+ task debug: :environment do
80
+ JetstreamBridge::DebugHelper.debug_info
81
+ end
82
+
83
+ desc 'Test connection to NATS'
84
+ task test_connection: :environment do
85
+ puts '[jetstream_bridge] Testing NATS connection...'
86
+
87
+ begin
88
+ jts = JetstreamBridge.ensure_topology!
89
+ puts '✓ Successfully connected to NATS'
90
+ puts "✓ JetStream is available"
91
+ puts "✓ Stream topology ensured"
92
+
93
+ # Check if we can get account info
94
+ info = jts.account_info
95
+ puts "\nAccount Info:"
96
+ puts " Memory: #{info.memory}"
97
+ puts " Storage: #{info.storage}"
98
+ puts " Streams: #{info.streams}"
99
+ puts " Consumers: #{info.consumers}"
100
+
101
+ exit 0
102
+ rescue StandardError => e
103
+ puts "✗ Connection failed: #{e.message}"
104
+ puts "\nBacktrace:" if ENV['VERBOSE']
105
+ puts e.backtrace.first(10).map { |line| " #{line}" }.join("\n") if ENV['VERBOSE']
106
+ exit 1
107
+ end
108
+ end
10
109
  end
@@ -55,11 +55,25 @@ module JetstreamBridge
55
55
  def list_stream_names(jts)
56
56
  names = []
57
57
  offset = 0
58
+ max_iterations = 100 # Safety limit to prevent infinite loops
59
+ iterations = 0
60
+
58
61
  loop do
62
+ iterations += 1
63
+ if iterations > max_iterations
64
+ Logging.warn(
65
+ "Stream listing exceeded max iterations (#{max_iterations}), returning #{names.size} streams",
66
+ tag: 'JetstreamBridge::OverlapGuard'
67
+ )
68
+ break
69
+ end
70
+
59
71
  resp = js_api_request(jts, '$JS.API.STREAM.NAMES', { offset: offset })
60
72
  batch = Array(resp['streams']).filter_map { |h| h['name'] }
61
73
  names.concat(batch)
62
- break if names.size >= resp['total'].to_i || batch.empty?
74
+ total = resp['total'].to_i
75
+
76
+ break if names.size >= total || batch.empty?
63
77
 
64
78
  offset = names.size
65
79
  end
@@ -96,17 +96,27 @@ module JetstreamBridge
96
96
  raise ArgumentError, 'subjects must not be empty' if desired.empty?
97
97
 
98
98
  attempts = 0
99
+ max_attempts = 3
100
+ backoffs = [0.05, 0.2, 0.5]
101
+
99
102
  begin
100
103
  info = safe_stream_info(jts, name)
101
104
  info ? ensure_update(jts, name, info, desired) : ensure_create(jts, name, desired)
102
105
  rescue NATS::JetStream::Error => e
103
- if StreamSupport.overlap_error?(e) && (attempts += 1) <= 1
104
- Logging.warn("Overlap race while ensuring #{name}; retrying once...", tag: 'JetstreamBridge::Stream')
105
- sleep(0.05)
106
+ if StreamSupport.overlap_error?(e) && (attempts += 1) <= max_attempts
107
+ backoff = backoffs[attempts - 1] || backoffs.last
108
+ Logging.warn(
109
+ "Overlap race while ensuring #{name}; retry #{attempts}/#{max_attempts} after #{backoff}s...",
110
+ tag: 'JetstreamBridge::Stream'
111
+ )
112
+ sleep(backoff)
106
113
  retry
107
114
  elsif StreamSupport.overlap_error?(e)
108
- Logging.warn("Overlap persists ensuring #{name}; leaving unchanged. err=#{e.message.inspect}",
109
- tag: 'JetstreamBridge::Stream')
115
+ Logging.warn(
116
+ "Overlap persists ensuring #{name} after #{attempts} attempts; leaving unchanged. " \
117
+ "err=#{e.message.inspect}",
118
+ tag: 'JetstreamBridge::Stream'
119
+ )
110
120
  nil
111
121
  else
112
122
  raise
@@ -4,5 +4,5 @@
4
4
  #
5
5
  # Version constant for the gem.
6
6
  module JetstreamBridge
7
- VERSION = '2.10.0'
7
+ VERSION = '3.0.0'
8
8
  end
@@ -53,8 +53,73 @@ module JetstreamBridge
53
53
  Connection.jetstream
54
54
  end
55
55
 
56
+ # Health check for monitoring and readiness probes
57
+ #
58
+ # @return [Hash] Health status including NATS connection, stream, and version
59
+ def health_check
60
+ conn_instance = Connection.instance
61
+ connected = conn_instance.connected?
62
+ connected_at = conn_instance.connected_at
63
+
64
+ stream_info = fetch_stream_info if connected
65
+
66
+ {
67
+ healthy: connected && stream_info[:exists],
68
+ nats_connected: connected,
69
+ connected_at: connected_at&.iso8601,
70
+ stream: stream_info,
71
+ config: {
72
+ env: config.env,
73
+ app_name: config.app_name,
74
+ destination_app: config.destination_app,
75
+ use_outbox: config.use_outbox,
76
+ use_inbox: config.use_inbox,
77
+ use_dlq: config.use_dlq
78
+ },
79
+ version: JetstreamBridge::VERSION
80
+ }
81
+ rescue StandardError => e
82
+ {
83
+ healthy: false,
84
+ error: "#{e.class}: #{e.message}"
85
+ }
86
+ end
87
+
88
+ # Check if connected to NATS
89
+ #
90
+ # @return [Boolean] true if connected and healthy
91
+ def connected?
92
+ Connection.instance.connected?
93
+ rescue StandardError
94
+ false
95
+ end
96
+
97
+ # Get stream information for the configured stream
98
+ #
99
+ # @return [Hash] Stream information including subjects and message count
100
+ def stream_info
101
+ fetch_stream_info
102
+ end
103
+
56
104
  private
57
105
 
106
+ def fetch_stream_info
107
+ jts = Connection.jetstream
108
+ info = jts.stream_info(config.stream_name)
109
+ {
110
+ exists: true,
111
+ name: config.stream_name,
112
+ subjects: info.config.subjects,
113
+ messages: info.state.messages
114
+ }
115
+ rescue StandardError => e
116
+ {
117
+ exists: false,
118
+ name: config.stream_name,
119
+ error: "#{e.class}: #{e.message}"
120
+ }
121
+ end
122
+
58
123
  def assign!(cfg, key, val)
59
124
  setter = :"#{key}="
60
125
  raise ArgumentError, "Unknown configuration option: #{key}" unless cfg.respond_to?(setter)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jetstream_bridge
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.10.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Attara
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-26 00:00:00.000000000 Z
11
+ date: 2025-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -17,6 +17,9 @@ dependencies:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '6.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '8.0'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -24,6 +27,9 @@ dependencies:
24
27
  - - ">="
25
28
  - !ruby/object:Gem::Version
26
29
  version: '6.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '8.0'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: activesupport
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -31,6 +37,9 @@ dependencies:
31
37
  - - ">="
32
38
  - !ruby/object:Gem::Version
33
39
  version: '6.0'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '8.0'
34
43
  type: :runtime
35
44
  prerelease: false
36
45
  version_requirements: !ruby/object:Gem::Requirement
@@ -38,6 +47,9 @@ dependencies:
38
47
  - - ">="
39
48
  - !ruby/object:Gem::Version
40
49
  version: '6.0'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '8.0'
41
53
  - !ruby/object:Gem::Dependency
42
54
  name: nats-pure
43
55
  requirement: !ruby/object:Gem::Requirement
@@ -59,6 +71,9 @@ dependencies:
59
71
  - - ">="
60
72
  - !ruby/object:Gem::Version
61
73
  version: '3.16'
74
+ - - "<"
75
+ - !ruby/object:Gem::Version
76
+ version: '4.0'
62
77
  type: :runtime
63
78
  prerelease: false
64
79
  version_requirements: !ruby/object:Gem::Requirement
@@ -66,16 +81,39 @@ dependencies:
66
81
  - - ">="
67
82
  - !ruby/object:Gem::Version
68
83
  version: '3.16'
84
+ - - "<"
85
+ - !ruby/object:Gem::Version
86
+ version: '4.0'
87
+ - !ruby/object:Gem::Dependency
88
+ name: mutex_m
89
+ requirement: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ type: :runtime
95
+ prerelease: false
96
+ version_requirements: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
69
101
  description: |-
70
102
  Publisher/Consumer utilities for NATS JetStream with environment-scoped subjects,
71
103
  overlap guards, DLQ routing, retries/backoff, and optional Inbox/Outbox patterns.
72
- Includes topology setup helpers for production-safe operation.
104
+ Includes health checks, auto-reconnection, graceful shutdown, and topology setup
105
+ helpers for production-safe operation.
73
106
  email:
74
107
  - mpyebattara@gmail.com
75
108
  executables: []
76
109
  extensions: []
77
110
  extra_rdoc_files: []
78
111
  files:
112
+ - CHANGELOG.md
113
+ - LICENSE
114
+ - README.md
115
+ - lib/generators/jetstream_bridge/health_check/health_check_generator.rb
116
+ - lib/generators/jetstream_bridge/health_check/templates/health_controller.rb
79
117
  - lib/generators/jetstream_bridge/initializer/initializer_generator.rb
80
118
  - lib/generators/jetstream_bridge/initializer/templates/jetstream_bridge.rb
81
119
  - lib/generators/jetstream_bridge/install/install_generator.rb
@@ -92,11 +130,17 @@ files:
92
130
  - lib/jetstream_bridge/consumer/subscription_manager.rb
93
131
  - lib/jetstream_bridge/core/config.rb
94
132
  - lib/jetstream_bridge/core/connection.rb
133
+ - lib/jetstream_bridge/core/connection_factory.rb
134
+ - lib/jetstream_bridge/core/debug_helper.rb
95
135
  - lib/jetstream_bridge/core/duration.rb
96
136
  - lib/jetstream_bridge/core/logging.rb
97
137
  - lib/jetstream_bridge/core/model_codec_setup.rb
98
138
  - lib/jetstream_bridge/core/model_utils.rb
139
+ - lib/jetstream_bridge/core/retry_strategy.rb
140
+ - lib/jetstream_bridge/errors.rb
99
141
  - lib/jetstream_bridge/inbox_event.rb
142
+ - lib/jetstream_bridge/models/event_envelope.rb
143
+ - lib/jetstream_bridge/models/subject.rb
100
144
  - lib/jetstream_bridge/outbox_event.rb
101
145
  - lib/jetstream_bridge/publisher/outbox_repository.rb
102
146
  - lib/jetstream_bridge/publisher/publisher.rb
@@ -116,8 +160,8 @@ metadata:
116
160
  changelog_uri: https://github.com/attaradev/jetstream_bridge/blob/main/CHANGELOG.md
117
161
  documentation_uri: https://github.com/attaradev/jetstream_bridge#readme
118
162
  bug_tracker_uri: https://github.com/attaradev/jetstream_bridge/issues
119
- github_repo: ssh://github.com/attaradev/jetstream_bridge
120
163
  rubygems_mfa_required: 'true'
164
+ allowed_push_host: https://rubygems.org
121
165
  post_install_message:
122
166
  rdoc_options: []
123
167
  require_paths:
@@ -126,15 +170,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
126
170
  requirements:
127
171
  - - ">="
128
172
  - !ruby/object:Gem::Version
129
- version: 2.7.0
173
+ version: 3.2.0
130
174
  required_rubygems_version: !ruby/object:Gem::Requirement
131
175
  requirements:
132
176
  - - ">="
133
177
  - !ruby/object:Gem::Version
134
- version: 3.3.0
178
+ version: '0'
135
179
  requirements: []
136
180
  rubygems_version: 3.4.19
137
181
  signing_key:
138
182
  specification_version: 4
139
- summary: Reliable realtime bridge over NATS JetStream for Rails/Ruby apps
183
+ summary: Production-safe realtime data bridge using NATS JetStream
140
184
  test_files: []