jetstream_bridge 1.15.0 β†’ 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52de97c8d390176c6113c836f07126258f513ddf7feac51dd570d271a43f8ca5
4
- data.tar.gz: 56a583ab2be2170e28afe5e1c89b33e51f8d40fa84dfb66b89666949e9390dc9
3
+ metadata.gz: 7addd218fb551e3cf359fb7a1ba35c5dc7b19b6085d391de3c94be4002d26d34
4
+ data.tar.gz: 218f2a64bf0617be5a770ef43ec6defbfd89825700441e35f59f77e813b9fbf5
5
5
  SHA512:
6
- metadata.gz: 64408358299860a7bb08897d8e0360e265274990499fe963cbd133a4592e186d71055814d9aeaf87b695b8035fab095a6f67ef99027135f9c7a5205e299be11e
7
- data.tar.gz: 0bff44a878e9a78f8288c6aed2f9dd8319a249b8f6b1f3275666040f248999a5cfda73fbc5196f6641a84f52ffcad0eba001bc0ac71ab83e6b04c0ce21b8d8f2
6
+ metadata.gz: ac1ab60e3908269576f740b046913c5d32f438aa6f4ce5e65b7977d7a0c20c5c304b1b1784eb61a4aaab8b58e6513822ab68a3372298650c21640049c6f3396b
7
+ data.tar.gz: 49e59b0ca025bca0a3a20f96cf68a1f43cf3627e701a1841ae5b4f3bb809347f68ad0ed066353e54bb9873472be6d7e37b2eb286f99ffa8450f97a87726a5ace
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- jetstream_bridge (1.15.0)
4
+ jetstream_bridge (2.1.0)
5
5
  activerecord (>= 6.0)
6
6
  activesupport (>= 6.0)
7
7
  nats-pure (~> 2.4)
data/README.md CHANGED
@@ -23,7 +23,7 @@ Includes durable consumers, backpressure, retries, **DLQ**, optional **Inbox/Out
23
23
 
24
24
  ```ruby
25
25
  # Gemfile
26
- gem "jetstream_bridge"
26
+ gem "jetstream_bridge", "~> 2.0"
27
27
  ```
28
28
 
29
29
  ```bash
@@ -98,11 +98,11 @@ end
98
98
 
99
99
  ## πŸ“‘ Subject Conventions
100
100
 
101
- | Direction | Subject Pattern |
102
- |---------------|--------------------------------|
103
- | **Publish** | `{env}.data.sync.{app}.{dest}` |
104
- | **Subscribe** | `{env}.data.sync.{dest}.{app}` |
105
- | **DLQ** | `{env}.data.sync.dlq` |
101
+ | Direction | Subject Pattern |
102
+ |---------------|---------------------------|
103
+ | **Publish** | `{env}.sync.{app}.{dest}` |
104
+ | **Subscribe** | `{env}.sync.{dest}.{app}` |
105
+ | **DLQ** | `{env}.sync.dlq` |
106
106
 
107
107
  * `{app}`: `app_name`
108
108
  * `{dest}`: `destination_app`
@@ -110,12 +110,12 @@ end
110
110
 
111
111
  ---
112
112
 
113
- ## 🧱 Stream Topology (auto-ensure & overlap-safe)
113
+ ## 🧱 Stream Topology (auto-ensure and overlap-safe)
114
114
 
115
115
  On first connection, Jetstream Bridge **ensures** a single stream exists for your `env` and that it covers:
116
116
 
117
- * `source_subject` (`{env}.data.sync.{app}.{dest}`)
118
- * `destination_subject` (`{env}.data.sync.{dest}.{app}`)
117
+ * `source_subject` (`{env}.sync.{app}.{dest}`)
118
+ * `destination_subject` (`{env}.sync.{dest}.{app}`)
119
119
  * `dlq_subject` (if enabled)
120
120
 
121
121
  It’s **overlap-safe**:
@@ -200,7 +200,7 @@ If **Outbox** is enabled, the publish call:
200
200
 
201
201
  ```ruby
202
202
  JetstreamBridge::Consumer.new(
203
- durable_name: "#{Rails.env}-peerapp-events",
203
+ durable_name: "#{Rails.env}-#{app_name}-workers",
204
204
  batch_size: 25
205
205
  ) do |event, subject, deliveries|
206
206
  # Your idempotent domain logic here
@@ -257,7 +257,7 @@ You may run a separate process to subscribe and triage messages that exceed `max
257
257
  ### Scaling
258
258
 
259
259
  * Run consumers in **separate processes/containers**
260
- * Scale consumers independently from web
260
+ * Scale consumers independently of web
261
261
  * Tune `batch_size`, `ack_wait`, `max_deliver`, and `backoff`
262
262
 
263
263
  ### Health check
@@ -13,7 +13,7 @@ require_relative 'subscription_manager'
13
13
  require_relative 'inbox/inbox_processor'
14
14
 
15
15
  module JetstreamBridge
16
- # Subscribes to "{env}.data.sync.{dest}.{app}" and processes messages.
16
+ # Subscribes to "{env}.sync.{dest}.{app}" and processes messages.
17
17
  class Consumer
18
18
  DEFAULT_BATCH_SIZE = 25
19
19
  FETCH_TIMEOUT_SECS = 5
@@ -22,7 +22,7 @@ module JetstreamBridge
22
22
  def initialize(durable_name:, batch_size: DEFAULT_BATCH_SIZE, &block)
23
23
  @handler = block
24
24
  @batch_size = batch_size
25
- @durable = durable_name
25
+ @durable = durable_name || JetstreamBridge.config.durable_name
26
26
  @jts = Connection.connect!
27
27
 
28
28
  ensure_destination!
@@ -11,7 +11,7 @@ module JetstreamBridge
11
11
  @nats_urls = ENV['NATS_URLS'] || ENV['NATS_URL'] || 'nats://localhost:4222'
12
12
  @env = ENV['NATS_ENV'] || 'development'
13
13
  @app_name = ENV['APP_NAME'] || 'app'
14
- @destination_app = ENV['DESTINATION_APP']
14
+ @destination_app = ENV.fetch('DESTINATION_APP', nil)
15
15
 
16
16
  @max_deliver = 5
17
17
  @ack_wait = '30s'
@@ -30,19 +30,23 @@ module JetstreamBridge
30
30
  end
31
31
 
32
32
  # Base subjects
33
- # Producer publishes to: {env}.data.sync.{app}.{dest}
34
- # Consumer subscribes to: {env}.data.sync.{dest}.{app}
33
+ # Producer publishes to: {env}.sync.{app}.{dest}
34
+ # Consumer subscribes to: {env}.sync.{dest}.{app}
35
35
  def source_subject
36
- "#{env}.data.sync.#{app_name}.#{destination_app}"
36
+ "#{env}.#{app_name}.sync.#{destination_app}"
37
37
  end
38
38
 
39
39
  def destination_subject
40
- "#{env}.data.sync.#{destination_app}.#{app_name}"
40
+ "#{env}.#{destination_app}.sync.#{app_name}"
41
41
  end
42
42
 
43
43
  # DLQ
44
44
  def dlq_subject
45
- "#{env}.data.sync.dlq"
45
+ "#{env}.sync.dlq"
46
+ end
47
+
48
+ def durable_name
49
+ "#{env}-#{app_name}-workers"
46
50
  end
47
51
  end
48
52
  end
@@ -21,11 +21,21 @@ module JetstreamBridge
21
21
  }.freeze
22
22
 
23
23
  class << self
24
- # Thread-safe delegator to the singleton instance
24
+ # Thread-safe delegator to the singleton instance.
25
+ # Returns a live JetStream context.
25
26
  def connect!
26
27
  @__mutex ||= Mutex.new
27
28
  @__mutex.synchronize { instance.connect! }
28
29
  end
30
+
31
+ # Optional accessors if callers need raw handles
32
+ def nc
33
+ instance.__send__(:nc)
34
+ end
35
+
36
+ def jetstream
37
+ instance.__send__(:jetstream)
38
+ end
29
39
  end
30
40
 
31
41
  # Idempotent: returns an existing, healthy JetStream context or establishes one.
@@ -36,12 +46,17 @@ module JetstreamBridge
36
46
  raise 'No NATS URLs configured' if servers.empty?
37
47
 
38
48
  establish_connection(servers)
49
+
39
50
  Logging.info(
40
- "Connected to NATS (#{servers.size} server#{servers.size == 1 ? '' : 's'}): #{sanitize_urls(servers).join(',')}",
51
+ "Connected to NATS (#{servers.size} server#{unless servers.size == 1
52
+ 's'
53
+ end}): #{sanitize_urls(servers).join(', ')}",
41
54
  tag: 'JetstreamBridge::Connection'
42
55
  )
43
56
 
57
+ # Ensure topology (streams, subjects, overlap guard, etc.)
44
58
  Topology.ensure!(@jts)
59
+
45
60
  @jts
46
61
  end
47
62
 
@@ -62,7 +77,26 @@ module JetstreamBridge
62
77
  def establish_connection(servers)
63
78
  @nc = NATS::IO::Client.new
64
79
  @nc.connect({ servers: servers }.merge(DEFAULT_CONN_OPTS))
80
+
81
+ # Create JetStream context
65
82
  @jts = @nc.jetstream
83
+
84
+ # --- Compatibility shim: ensure JetStream responds to #nc for older/newer clients ---
85
+ # Some versions of the NATS Ruby client don't expose nc on the JetStream object.
86
+ # We attach a singleton method, so code expecting `js.nc` continues to work.
87
+ return if @jts.respond_to?(:nc)
88
+
89
+ nc_ref = @nc
90
+ @jts.define_singleton_method(:nc) { nc_ref }
91
+
92
+ # ------------------------------------------------------------------------------------
93
+ end
94
+
95
+ # Expose for class-level helpers (not part of public API)
96
+ attr_reader :nc
97
+
98
+ def jetstream
99
+ @jts
66
100
  end
67
101
 
68
102
  # Mask credentials in NATS URLs:
@@ -9,7 +9,7 @@ require_relative '../core/model_utils'
9
9
  require_relative 'outbox_repository'
10
10
 
11
11
  module JetstreamBridge
12
- # Publishes to "{env}.data.sync.{app}.{dest}".
12
+ # Publishes to "{env}.sync.{app}.{dest}".
13
13
  class Publisher
14
14
  DEFAULT_RETRIES = 2
15
15
  RETRY_BACKOFFS = [0.25, 1.0].freeze
@@ -33,7 +33,7 @@ module JetstreamBridge
33
33
  if JetstreamBridge.config.use_outbox
34
34
  publish_via_outbox(subject, envelope)
35
35
  else
36
- with_retries { do_publish(subject, envelope) }
36
+ with_retries { do_publish?(subject, envelope) }
37
37
  end
38
38
  rescue StandardError => e
39
39
  log_error(false, e)
@@ -47,8 +47,8 @@ module JetstreamBridge
47
47
  raise ArgumentError, 'destination_app must be configured'
48
48
  end
49
49
 
50
- def do_publish(subject, envelope)
51
- headers = { 'Nats-Msg-Id' => envelope['event_id'] }
50
+ def do_publish?(subject, envelope)
51
+ headers = { 'nats-msg-id' => envelope['event_id'] }
52
52
  @jts.publish(subject, JSON.generate(envelope), header: headers)
53
53
  Logging.info("Published #{subject} event_id=#{envelope['event_id']}",
54
54
  tag: 'JetstreamBridge::Publisher')
@@ -62,7 +62,7 @@ module JetstreamBridge
62
62
  unless ModelUtils.ar_class?(klass)
63
63
  Logging.warn("Outbox model #{klass} is not an ActiveRecord model; publishing directly.",
64
64
  tag: 'JetstreamBridge::Publisher')
65
- return with_retries { do_publish(subject, envelope) }
65
+ return with_retries { do_publish?(subject, envelope) }
66
66
  end
67
67
 
68
68
  repo = OutboxRepository.new(klass)
@@ -4,5 +4,5 @@
4
4
  #
5
5
  # Version constant for the gem.
6
6
  module JetstreamBridge
7
- VERSION = '1.15.0'
7
+ VERSION = '2.1.0'
8
8
  end
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: 1.15.0
4
+ version: 2.1.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-19 00:00:00.000000000 Z
11
+ date: 2025-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord