jetstream_bridge 3.0.0 → 3.0.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.
- checksums.yaml +4 -4
- data/README.md +21 -3
- data/lib/generators/jetstream_bridge/health_check/health_check_generator.rb +8 -8
- data/lib/generators/jetstream_bridge/migrations/templates/create_jetstream_inbox_events.rb.erb +1 -1
- data/lib/generators/jetstream_bridge/migrations/templates/create_jetstream_outbox_events.rb.erb +1 -1
- data/lib/jetstream_bridge/consumer/consumer.rb +12 -14
- data/lib/jetstream_bridge/consumer/message_processor.rb +5 -5
- data/lib/jetstream_bridge/core/connection.rb +1 -3
- data/lib/jetstream_bridge/core/connection_factory.rb +1 -3
- data/lib/jetstream_bridge/core/debug_helper.rb +29 -17
- data/lib/jetstream_bridge/core/duration.rb +1 -1
- data/lib/jetstream_bridge/core/retry_strategy.rb +2 -1
- data/lib/jetstream_bridge/models/event_envelope.rb +4 -1
- data/lib/jetstream_bridge/models/subject.rb +1 -1
- data/lib/jetstream_bridge/publisher/publisher.rb +2 -2
- data/lib/jetstream_bridge/railtie.rb +4 -6
- data/lib/jetstream_bridge/tasks/install.rake +2 -2
- data/lib/jetstream_bridge/version.rb +1 -1
- metadata +19 -19
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9fa0ea79aaf06fb588e761aaab3afc83f6e41b9427650b1a0ac1f3265d0b4d9c
|
|
4
|
+
data.tar.gz: fd3c88539aaddc3e36a33ce787ea2337300cf5e215155bfe361291131e459191
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d88a7ce64b6a0c52d1819450197d8d67d6c82c812596e79e2630c046e5851d14203fdd4014501de3478ba3f474a1ce5f0787b8168e6198e77a2992bf67564b5b
|
|
7
|
+
data.tar.gz: 7b67641d6f8af42b1ce43973c0e48cae31aac1d78a41e4d46e07199bb1f4cfd4b30b954583d73c9016c8bb283788dbdf9ceb2b8644c97d821297ddcc00c24910
|
data/README.md
CHANGED
|
@@ -12,6 +12,24 @@
|
|
|
12
12
|
Includes durable consumers, backpressure, retries, <strong>DLQ</strong>, optional <strong>Inbox/Outbox</strong>, and <strong>overlap-safe stream provisioning</strong>
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
|
+
<p align="center">
|
|
16
|
+
<a href="https://github.com/attaradev/jetstream_bridge/actions/workflows/ci.yml">
|
|
17
|
+
<img src="https://github.com/attaradev/jetstream_bridge/actions/workflows/ci.yml/badge.svg" alt="CI Status"/>
|
|
18
|
+
</a>
|
|
19
|
+
<a href="https://codecov.io/gh/attaradev/jetstream_bridge">
|
|
20
|
+
<img src="https://codecov.io/gh/attaradev/jetstream_bridge/branch/main/graph/badge.svg" alt="Coverage Status"/>
|
|
21
|
+
</a>
|
|
22
|
+
<a href="https://rubygems.org/gems/jetstream_bridge">
|
|
23
|
+
<img src="https://img.shields.io/gem/v/jetstream_bridge.svg" alt="Gem Version"/>
|
|
24
|
+
</a>
|
|
25
|
+
<a href="https://rubygems.org/gems/jetstream_bridge">
|
|
26
|
+
<img src="https://img.shields.io/gem/dt/jetstream_bridge.svg" alt="Downloads"/>
|
|
27
|
+
</a>
|
|
28
|
+
<a href="LICENSE">
|
|
29
|
+
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"/>
|
|
30
|
+
</a>
|
|
31
|
+
</p>
|
|
32
|
+
|
|
15
33
|
<p align="center">
|
|
16
34
|
<a href="#-features">Features</a> •
|
|
17
35
|
<a href="#-install">Install</a> •
|
|
@@ -35,7 +53,7 @@
|
|
|
35
53
|
* ⚡️ **Eager-loaded models** via Railtie (production)
|
|
36
54
|
* 📊 Configurable logging with sensible defaults
|
|
37
55
|
|
|
38
|
-
### Production-Ready Features
|
|
56
|
+
### Production-Ready Features
|
|
39
57
|
|
|
40
58
|
* 🏥 **Health checks** - Monitor NATS connection and stream status
|
|
41
59
|
* 🔄 **Auto-reconnection** - Automatic recovery from connection failures
|
|
@@ -53,7 +71,7 @@
|
|
|
53
71
|
|
|
54
72
|
```ruby
|
|
55
73
|
# Gemfile
|
|
56
|
-
gem "jetstream_bridge", "~>
|
|
74
|
+
gem "jetstream_bridge", "~> 3.0"
|
|
57
75
|
```
|
|
58
76
|
|
|
59
77
|
```bash
|
|
@@ -335,7 +353,7 @@ health = JetstreamBridge.health_check
|
|
|
335
353
|
# connected_at: "2025-11-22T20:00:00Z",
|
|
336
354
|
# stream: { exists: true, name: "...", ... },
|
|
337
355
|
# config: { env: "production", ... },
|
|
338
|
-
# version: "
|
|
356
|
+
# version: "3.0.0"
|
|
339
357
|
# }
|
|
340
358
|
|
|
341
359
|
# Force-connect & ensure topology at boot or in a check
|
|
@@ -13,8 +13,8 @@ module JetstreamBridge
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def add_route
|
|
16
|
-
route_content = " # JetStream Bridge health check endpoint\n" \
|
|
17
|
-
|
|
16
|
+
route_content = " # JetStream Bridge health check endpoint\n " \
|
|
17
|
+
"get '/health/jetstream', to: 'jetstream_health#show'"
|
|
18
18
|
|
|
19
19
|
if File.exist?('config/routes.rb')
|
|
20
20
|
inject_into_file 'config/routes.rb', after: /Rails\.application\.routes\.draw do\n/ do
|
|
@@ -28,11 +28,11 @@ module JetstreamBridge
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def show_usage
|
|
31
|
-
say "\n
|
|
31
|
+
say "\n#{'=' * 70}", :green
|
|
32
32
|
say 'Health Check Endpoint Created!', :green
|
|
33
33
|
say '=' * 70, :green
|
|
34
34
|
say "\nThe health check endpoint is now available at:"
|
|
35
|
-
say
|
|
35
|
+
say ' GET /health/jetstream', :cyan
|
|
36
36
|
say "\nExample response:"
|
|
37
37
|
say <<~EXAMPLE, :white
|
|
38
38
|
{
|
|
@@ -54,10 +54,10 @@ module JetstreamBridge
|
|
|
54
54
|
}
|
|
55
55
|
EXAMPLE
|
|
56
56
|
say "\nUse this endpoint for:"
|
|
57
|
-
say
|
|
58
|
-
say
|
|
59
|
-
say
|
|
60
|
-
say
|
|
57
|
+
say ' • Kubernetes liveness/readiness probes', :white
|
|
58
|
+
say ' • Docker health checks', :white
|
|
59
|
+
say ' • Monitoring and alerting', :white
|
|
60
|
+
say ' • Load balancer health checks', :white
|
|
61
61
|
say '=' * 70, :green
|
|
62
62
|
end
|
|
63
63
|
end
|
data/lib/generators/jetstream_bridge/migrations/templates/create_jetstream_inbox_events.rb.erb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class CreateJetstreamInboxEvents < ActiveRecord::Migration[
|
|
3
|
+
class CreateJetstreamInboxEvents < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
4
4
|
def change
|
|
5
5
|
create_table :jetstream_inbox_events do |t|
|
|
6
6
|
t.string :event_id # preferred dedupe key
|
data/lib/generators/jetstream_bridge/migrations/templates/create_jetstream_outbox_events.rb.erb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class CreateJetstreamOutboxEvents < ActiveRecord::Migration[
|
|
3
|
+
class CreateJetstreamOutboxEvents < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
4
4
|
def change
|
|
5
5
|
create_table :jetstream_outbox_events do |t|
|
|
6
6
|
t.string :event_id, null: false
|
|
@@ -28,7 +28,7 @@ module JetstreamBridge
|
|
|
28
28
|
@idle_backoff = IDLE_SLEEP_SECS
|
|
29
29
|
@running = true
|
|
30
30
|
@shutdown_requested = false
|
|
31
|
-
@jts
|
|
31
|
+
@jts = Connection.connect!
|
|
32
32
|
|
|
33
33
|
ensure_destination!
|
|
34
34
|
|
|
@@ -162,22 +162,20 @@ module JetstreamBridge
|
|
|
162
162
|
def drain_inflight_messages
|
|
163
163
|
return unless @psub
|
|
164
164
|
|
|
165
|
-
Logging.info(
|
|
165
|
+
Logging.info('Draining in-flight messages...', tag: 'JetstreamBridge::Consumer')
|
|
166
166
|
# Process any pending messages with a short timeout
|
|
167
167
|
5.times do
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
break
|
|
178
|
-
end
|
|
168
|
+
msgs = @psub.fetch(@batch_size, timeout: 1)
|
|
169
|
+
break if msgs.nil? || msgs.empty?
|
|
170
|
+
|
|
171
|
+
msgs.each { |m| process_one(m) }
|
|
172
|
+
rescue NATS::Timeout, NATS::IO::Timeout
|
|
173
|
+
break
|
|
174
|
+
rescue StandardError => e
|
|
175
|
+
Logging.warn("Error draining messages: #{e.class} #{e.message}", tag: 'JetstreamBridge::Consumer')
|
|
176
|
+
break
|
|
179
177
|
end
|
|
180
|
-
Logging.info(
|
|
178
|
+
Logging.info('Drain complete', tag: 'JetstreamBridge::Consumer')
|
|
181
179
|
rescue StandardError => e
|
|
182
180
|
Logging.error("Drain failed: #{e.class} #{e.message}", tag: 'JetstreamBridge::Consumer')
|
|
183
181
|
end
|
|
@@ -79,7 +79,7 @@ module JetstreamBridge
|
|
|
79
79
|
Oj.load(data, mode: :strict)
|
|
80
80
|
rescue Oj::ParseError => e
|
|
81
81
|
dlq_success = @dlq.publish(msg, ctx,
|
|
82
|
-
|
|
82
|
+
reason: 'malformed_json', error_class: e.class.name, error_message: e.message)
|
|
83
83
|
if dlq_success
|
|
84
84
|
msg.ack
|
|
85
85
|
Logging.warn(
|
|
@@ -106,7 +106,7 @@ module JetstreamBridge
|
|
|
106
106
|
)
|
|
107
107
|
rescue *UNRECOVERABLE_ERRORS => e
|
|
108
108
|
dlq_success = @dlq.publish(msg, ctx,
|
|
109
|
-
|
|
109
|
+
reason: 'unrecoverable', error_class: e.class.name, error_message: e.message)
|
|
110
110
|
if dlq_success
|
|
111
111
|
msg.ack
|
|
112
112
|
Logging.warn(
|
|
@@ -130,9 +130,9 @@ module JetstreamBridge
|
|
|
130
130
|
if ctx.deliveries >= max_deliver
|
|
131
131
|
# Only ACK if DLQ publish succeeds
|
|
132
132
|
dlq_success = @dlq.publish(msg, ctx,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
reason: 'max_deliver_exceeded',
|
|
134
|
+
error_class: error.class.name,
|
|
135
|
+
error_message: error.message)
|
|
136
136
|
|
|
137
137
|
if dlq_success
|
|
138
138
|
msg.ack
|
|
@@ -90,9 +90,7 @@ module JetstreamBridge
|
|
|
90
90
|
jts = client.jetstream
|
|
91
91
|
|
|
92
92
|
# Ensure JetStream responds to #nc for compatibility
|
|
93
|
-
unless jts.respond_to?(:nc)
|
|
94
|
-
jts.define_singleton_method(:nc) { client }
|
|
95
|
-
end
|
|
93
|
+
jts.define_singleton_method(:nc) { client } unless jts.respond_to?(:nc)
|
|
96
94
|
|
|
97
95
|
jts
|
|
98
96
|
end
|
|
@@ -8,22 +8,22 @@ module JetstreamBridge
|
|
|
8
8
|
class << self
|
|
9
9
|
# Print comprehensive debug information about the current setup
|
|
10
10
|
def debug_info
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
info = {
|
|
12
|
+
config: config_debug,
|
|
13
|
+
connection: connection_debug,
|
|
14
|
+
stream: stream_debug,
|
|
15
|
+
health: JetstreamBridge.health_check
|
|
16
|
+
}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
Logging.info('=== JetStream Bridge Debug Info ===', tag: 'JetstreamBridge::Debug')
|
|
19
|
+
info.each do |section, data|
|
|
20
|
+
Logging.info("#{section.to_s.upcase}:", tag: 'JetstreamBridge::Debug')
|
|
21
|
+
log_hash(data, indent: 2)
|
|
22
|
+
end
|
|
23
|
+
Logging.info('=== End Debug Info ===', tag: 'JetstreamBridge::Debug')
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
info
|
|
26
|
+
end
|
|
27
27
|
|
|
28
28
|
private
|
|
29
29
|
|
|
@@ -34,9 +34,21 @@ module JetstreamBridge
|
|
|
34
34
|
app_name: cfg.app_name,
|
|
35
35
|
destination_app: cfg.destination_app,
|
|
36
36
|
stream_name: cfg.stream_name,
|
|
37
|
-
source_subject:
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
source_subject: begin
|
|
38
|
+
cfg.source_subject
|
|
39
|
+
rescue StandardError
|
|
40
|
+
'ERROR'
|
|
41
|
+
end,
|
|
42
|
+
destination_subject: begin
|
|
43
|
+
cfg.destination_subject
|
|
44
|
+
rescue StandardError
|
|
45
|
+
'ERROR'
|
|
46
|
+
end,
|
|
47
|
+
dlq_subject: begin
|
|
48
|
+
cfg.dlq_subject
|
|
49
|
+
rescue StandardError
|
|
50
|
+
'ERROR'
|
|
51
|
+
end,
|
|
40
52
|
durable_name: cfg.durable_name,
|
|
41
53
|
nats_urls: cfg.nats_urls,
|
|
42
54
|
max_deliver: cfg.max_deliver,
|
|
@@ -72,7 +72,7 @@ module JetstreamBridge
|
|
|
72
72
|
case default_unit
|
|
73
73
|
when :auto
|
|
74
74
|
# Preserve existing heuristic for compatibility but log deprecation warning
|
|
75
|
-
if defined?(Logging) && num
|
|
75
|
+
if defined?(Logging) && num.positive? && num < 1_000
|
|
76
76
|
Logging.debug(
|
|
77
77
|
"Duration :auto heuristic treating #{num} as seconds. " \
|
|
78
78
|
"Consider specifying default_unit: :s or :ms for clarity.",
|
|
@@ -109,7 +109,10 @@ module JetstreamBridge
|
|
|
109
109
|
def deep_freeze(obj)
|
|
110
110
|
case obj
|
|
111
111
|
when Hash
|
|
112
|
-
obj.each
|
|
112
|
+
obj.each do |k, v|
|
|
113
|
+
deep_freeze(k)
|
|
114
|
+
deep_freeze(v)
|
|
115
|
+
end
|
|
113
116
|
obj.freeze
|
|
114
117
|
when Array
|
|
115
118
|
obj.each { |item| deep_freeze(item) }
|
|
@@ -96,8 +96,8 @@ module JetstreamBridge
|
|
|
96
96
|
# ---- /Outbox path ----
|
|
97
97
|
|
|
98
98
|
# Retry using strategy pattern
|
|
99
|
-
def with_retries
|
|
100
|
-
@retry_strategy.execute(context: 'Publisher')
|
|
99
|
+
def with_retries(&block)
|
|
100
|
+
@retry_strategy.execute(context: 'Publisher', &block)
|
|
101
101
|
rescue RetryStrategy::RetryExhausted => e
|
|
102
102
|
log_error(false, e)
|
|
103
103
|
end
|
|
@@ -23,11 +23,9 @@ module JetstreamBridge
|
|
|
23
23
|
initializer 'jetstream_bridge.validate_config', after: :load_config_initializers do |app|
|
|
24
24
|
if Rails.env.development? || Rails.env.test?
|
|
25
25
|
app.config.after_initialize do
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Rails.logger.warn "[JetStream Bridge] Configuration warning: #{e.message}"
|
|
30
|
-
end
|
|
26
|
+
JetstreamBridge.config.validate! if JetstreamBridge.config.destination_app
|
|
27
|
+
rescue JetstreamBridge::ConfigurationError => e
|
|
28
|
+
Rails.logger.warn "[JetStream Bridge] Configuration warning: #{e.message}"
|
|
31
29
|
end
|
|
32
30
|
end
|
|
33
31
|
end
|
|
@@ -35,7 +33,7 @@ module JetstreamBridge
|
|
|
35
33
|
# Add console helper methods
|
|
36
34
|
console do
|
|
37
35
|
Rails.logger.info "[JetStream Bridge] Loaded v#{JetstreamBridge::VERSION}"
|
|
38
|
-
Rails.logger.info
|
|
36
|
+
Rails.logger.info '[JetStream Bridge] Use JetstreamBridge.health_check to check status'
|
|
39
37
|
end
|
|
40
38
|
|
|
41
39
|
# Load rake tasks
|
|
@@ -87,8 +87,8 @@ namespace :jetstream_bridge do
|
|
|
87
87
|
begin
|
|
88
88
|
jts = JetstreamBridge.ensure_topology!
|
|
89
89
|
puts '✓ Successfully connected to NATS'
|
|
90
|
-
puts
|
|
91
|
-
puts
|
|
90
|
+
puts '✓ JetStream is available'
|
|
91
|
+
puts '✓ Stream topology ensured'
|
|
92
92
|
|
|
93
93
|
# Check if we can get account info
|
|
94
94
|
info = jts.account_info
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jetstream_bridge
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mike Attara
|
|
@@ -16,7 +16,7 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
19
|
+
version: 7.1.5.2
|
|
20
20
|
- - "<"
|
|
21
21
|
- !ruby/object:Gem::Version
|
|
22
22
|
version: '8.0'
|
|
@@ -26,7 +26,7 @@ dependencies:
|
|
|
26
26
|
requirements:
|
|
27
27
|
- - ">="
|
|
28
28
|
- !ruby/object:Gem::Version
|
|
29
|
-
version:
|
|
29
|
+
version: 7.1.5.2
|
|
30
30
|
- - "<"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
32
|
version: '8.0'
|
|
@@ -36,7 +36,7 @@ dependencies:
|
|
|
36
36
|
requirements:
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version:
|
|
39
|
+
version: 7.1.5.2
|
|
40
40
|
- - "<"
|
|
41
41
|
- !ruby/object:Gem::Version
|
|
42
42
|
version: '8.0'
|
|
@@ -46,10 +46,24 @@ dependencies:
|
|
|
46
46
|
requirements:
|
|
47
47
|
- - ">="
|
|
48
48
|
- !ruby/object:Gem::Version
|
|
49
|
-
version:
|
|
49
|
+
version: 7.1.5.2
|
|
50
50
|
- - "<"
|
|
51
51
|
- !ruby/object:Gem::Version
|
|
52
52
|
version: '8.0'
|
|
53
|
+
- !ruby/object:Gem::Dependency
|
|
54
|
+
name: mutex_m
|
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '0'
|
|
60
|
+
type: :runtime
|
|
61
|
+
prerelease: false
|
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0'
|
|
53
67
|
- !ruby/object:Gem::Dependency
|
|
54
68
|
name: nats-pure
|
|
55
69
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -84,20 +98,6 @@ dependencies:
|
|
|
84
98
|
- - "<"
|
|
85
99
|
- !ruby/object:Gem::Version
|
|
86
100
|
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'
|
|
101
101
|
description: |-
|
|
102
102
|
Publisher/Consumer utilities for NATS JetStream with environment-scoped subjects,
|
|
103
103
|
overlap guards, DLQ routing, retries/backoff, and optional Inbox/Outbox patterns.
|