jetstream_bridge 5.1.0 → 7.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.
@@ -51,50 +51,37 @@ module JetstreamBridge
51
51
  def subscribe_without_verification!
52
52
  # Manually create a pull subscription without calling consumer_info
53
53
  # This bypasses the permission check in nats-pure's pull_subscribe
54
- nc = resolve_nc
55
-
56
- return build_pull_subscription(nc) if nc.respond_to?(:new_inbox) && nc.respond_to?(:subscribe)
57
-
58
- # Fallback for environments (mocks/tests) where low-level NATS client is unavailable.
59
- if @jts.respond_to?(:pull_subscribe)
60
- Logging.info(
61
- "Using pull_subscribe fallback for consumer #{@durable} (stream=#{stream_name})",
62
- tag: 'JetstreamBridge::Consumer'
63
- )
64
- return @jts.pull_subscribe(filter_subject, @durable, stream: stream_name)
65
- end
66
-
67
- raise JetstreamBridge::ConnectionError,
68
- 'Unable to create subscription without verification: NATS client not available'
54
+ create_subscription_with_fallback(
55
+ description: "pull subscription for consumer #{@durable} (stream=#{stream_name})",
56
+ primary_check: ->(nc) { nc.respond_to?(:new_inbox) && nc.respond_to?(:subscribe) },
57
+ primary_action: ->(nc) { build_pull_subscription(nc) },
58
+ fallback_name: :pull_subscribe,
59
+ fallback_available: -> { @jts.respond_to?(:pull_subscribe) },
60
+ fallback_action: -> { @jts.pull_subscribe(filter_subject, @durable, stream: stream_name) }
61
+ )
69
62
  end
70
63
 
71
64
  def subscribe_push!
72
65
  # Push consumers deliver messages directly to a subscription subject
73
66
  # No JetStream API calls needed - just subscribe to the delivery subject
74
- nc = resolve_nc
75
67
  delivery_subject = @cfg.push_delivery_subject
76
68
 
77
- if nc.respond_to?(:subscribe)
78
- sub = nc.subscribe(delivery_subject)
79
- Logging.info(
80
- "Created push subscription for consumer #{@durable} " \
81
- "(stream=#{stream_name}, delivery=#{delivery_subject})",
82
- tag: 'JetstreamBridge::Consumer'
83
- )
84
- return sub
85
- end
86
-
87
- # Fallback for test environments
88
- if @jts.respond_to?(:subscribe)
89
- Logging.info(
90
- "Using JetStream subscribe fallback for push consumer #{@durable} (stream=#{stream_name})",
91
- tag: 'JetstreamBridge::Consumer'
92
- )
93
- return @jts.subscribe(delivery_subject)
94
- end
95
-
96
- raise JetstreamBridge::ConnectionError,
97
- 'Unable to create push subscription: NATS client not available'
69
+ create_subscription_with_fallback(
70
+ description: "push subscription for consumer #{@durable} (stream=#{stream_name}, delivery=#{delivery_subject})",
71
+ primary_check: ->(nc) { nc.respond_to?(:subscribe) },
72
+ primary_action: lambda do |nc|
73
+ sub = nc.subscribe(delivery_subject)
74
+ Logging.info(
75
+ "Created push subscription for consumer #{@durable} " \
76
+ "(stream=#{stream_name}, delivery=#{delivery_subject})",
77
+ tag: 'JetstreamBridge::Consumer'
78
+ )
79
+ sub
80
+ end,
81
+ fallback_name: :subscribe,
82
+ fallback_available: -> { @jts.respond_to?(:subscribe) },
83
+ fallback_action: -> { @jts.subscribe(delivery_subject) }
84
+ )
98
85
  end
99
86
 
100
87
  private
@@ -107,8 +94,8 @@ module JetstreamBridge
107
94
  deliver_policy: 'all',
108
95
  max_deliver: JetstreamBridge.config.max_deliver,
109
96
  # JetStream expects seconds (the client multiplies by nanoseconds).
110
- ack_wait: duration_to_seconds(JetstreamBridge.config.ack_wait),
111
- backoff: Array(JetstreamBridge.config.backoff).map { |d| duration_to_seconds(d) }
97
+ ack_wait: Duration.to_seconds(JetstreamBridge.config.ack_wait),
98
+ backoff: Duration.normalize_list_to_seconds(JetstreamBridge.config.backoff)
112
99
  }
113
100
 
114
101
  # Add deliver_subject for push consumers
@@ -125,73 +112,6 @@ module JetstreamBridge
125
112
  )
126
113
  end
127
114
 
128
- # ---- cfg access/normalization (struct-like or hash-like) ----
129
-
130
- def get(cfg, key)
131
- cfg.respond_to?(key) ? cfg.public_send(key) : cfg[key]
132
- end
133
-
134
- def sval(cfg, key)
135
- v = get(cfg, key)
136
- v = v.to_s if v.is_a?(Symbol)
137
- v&.to_s&.downcase
138
- end
139
-
140
- def ival(cfg, key)
141
- v = get(cfg, key)
142
- v.to_i
143
- end
144
-
145
- # Normalize duration-like field to **milliseconds** (Integer).
146
- # Accepts:
147
- # - Strings:"500ms""30s" "2m", "1h", "250us", "100ns"
148
- # - Integers/Floats:
149
- # * Server may return large integers in **nanoseconds** → detect and convert.
150
- # * Otherwise, we delegate to Duration.to_millis (heuristic/explicit).
151
- def d_secs(cfg, key)
152
- raw = get(cfg, key)
153
- duration_to_seconds(raw)
154
- end
155
-
156
- # Normalize array of durations to integer milliseconds.
157
- def darr_secs(cfg, key)
158
- raw = get(cfg, key)
159
- Array(raw).map { |d| duration_to_seconds(d) }
160
- end
161
-
162
- # ---- duration coercion ----
163
-
164
- def duration_to_seconds(val)
165
- return nil if val.nil?
166
-
167
- case val
168
- when Integer
169
- # Heuristic: extremely large integers are likely **nanoseconds** from server
170
- # (e.g., 30s => 30_000_000_000 ns). Convert ns → seconds.
171
- return (val / 1_000_000_000.0).round if val >= 1_000_000_000
172
-
173
- # otherwise rely on Duration’s :auto heuristic (int <1000 => seconds, >=1000 => ms)
174
- millis = Duration.to_millis(val, default_unit: :auto)
175
- seconds_from_millis(millis)
176
- when Float
177
- millis = Duration.to_millis(val, default_unit: :auto)
178
- seconds_from_millis(millis)
179
- when String
180
- # Strings include unit (ns/us/ms/s/m/h/d) handled by Duration
181
- millis = Duration.to_millis(val) # default_unit ignored when unit given
182
- seconds_from_millis(millis)
183
- else
184
- return duration_to_seconds(val.to_f) if val.respond_to?(:to_f)
185
-
186
- raise ArgumentError, "invalid duration: #{val.inspect}"
187
- end
188
- end
189
-
190
- def seconds_from_millis(millis)
191
- # Always round up to avoid zero-second waits when sub-second durations are provided.
192
- [(millis / 1000.0).ceil, 1].max
193
- end
194
-
195
115
  def log_runtime_skip
196
116
  Logging.info(
197
117
  "Skipping consumer provisioning/verification for #{@durable} at runtime to avoid JetStream API usage. " \
@@ -213,5 +133,23 @@ module JetstreamBridge
213
133
  builder = PullSubscriptionBuilder.new(@jts, @durable, stream_name, filter_subject)
214
134
  builder.build(nats_client)
215
135
  end
136
+
137
+ def create_subscription_with_fallback(description:, primary_check:, primary_action:, fallback_name:,
138
+ fallback_available:, fallback_action:)
139
+ nc = resolve_nc
140
+
141
+ return primary_action.call(nc) if nc && primary_check.call(nc)
142
+
143
+ if fallback_available.call
144
+ Logging.info(
145
+ "Using #{fallback_name} fallback for #{description}",
146
+ tag: 'JetstreamBridge::Consumer'
147
+ )
148
+ return fallback_action.call
149
+ end
150
+
151
+ raise JetstreamBridge::ConnectionError,
152
+ "Unable to create #{description}: NATS client not available"
153
+ end
216
154
  end
217
155
  end
@@ -304,7 +304,7 @@ module JetstreamBridge
304
304
  if verify_js
305
305
  verify_jetstream!
306
306
  if config_auto_provision
307
- Topology.ensure!(@jts)
307
+ Topology.provision!(@jts)
308
308
  Logging.info(
309
309
  'Topology ensured after connection (auto_provision=true).',
310
310
  tag: 'JetstreamBridge::Connection'
@@ -461,7 +461,7 @@ module JetstreamBridge
461
461
 
462
462
  # Re-ensure topology after reconnect when allowed
463
463
  if config_auto_provision
464
- Topology.ensure!(@jts)
464
+ Topology.provision!(@jts)
465
465
  else
466
466
  Logging.info(
467
467
  'Skipping topology provisioning after reconnect (auto_provision=false).',
@@ -64,6 +64,31 @@ module JetstreamBridge
64
64
  vals.map { |v| to_millis(v, default_unit: default_unit) }
65
65
  end
66
66
 
67
+ # Convert duration-like value to seconds (rounding up, min 1s).
68
+ #
69
+ # Retains the nanosecond heuristic used in SubscriptionManager:
70
+ # extremely large integers (>= 1_000_000_000) are treated as nanoseconds
71
+ # when default_unit is :auto.
72
+ def to_seconds(val, default_unit: :auto)
73
+ return nil if val.nil?
74
+
75
+ millis = if val.is_a?(Integer) && default_unit == :auto && val >= 1_000_000_000
76
+ to_millis(val, default_unit: :ns)
77
+ else
78
+ to_millis(val, default_unit: default_unit)
79
+ end
80
+
81
+ seconds_from_millis(millis)
82
+ end
83
+
84
+ # Normalize an array of durations into integer seconds.
85
+ def normalize_list_to_seconds(values, default_unit: :auto)
86
+ vals = Array(values)
87
+ return [] if vals.empty?
88
+
89
+ vals.map { |v| to_seconds(v, default_unit: default_unit) }
90
+ end
91
+
67
92
  # --- internal helpers ---
68
93
 
69
94
  def int_to_ms(num, default_unit:)
@@ -100,5 +125,10 @@ module JetstreamBridge
100
125
 
101
126
  (num * mult).round
102
127
  end
128
+
129
+ def seconds_from_millis(millis)
130
+ # Always round up to avoid zero-second waits when sub-second durations are provided.
131
+ [(millis / 1000.0).ceil, 1].max
132
+ end
103
133
  end
104
134
  end
@@ -11,7 +11,7 @@ end
11
11
  module JetstreamBridge
12
12
  if defined?(ActiveRecord::Base)
13
13
  class InboxEvent < ActiveRecord::Base
14
- self.table_name = 'jetstream_inbox_events'
14
+ self.table_name = 'jetstream_bridge_inbox_events'
15
15
 
16
16
  class << self
17
17
  # Safe column presence check that never boots a connection during class load.
@@ -11,7 +11,7 @@ end
11
11
  module JetstreamBridge
12
12
  if defined?(ActiveRecord::Base)
13
13
  class OutboxEvent < ActiveRecord::Base
14
- self.table_name = 'jetstream_outbox_events'
14
+ self.table_name = 'jetstream_bridge_outbox_events'
15
15
 
16
16
  class << self
17
17
  # Safe column presence check that never boots a connection during class load.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'logger'
3
4
  require_relative 'topology/topology'
4
5
  require_relative 'consumer/subscription_manager'
5
6
  require_relative 'core/logging'
@@ -13,52 +14,107 @@ module JetstreamBridge
13
14
  # deploy-time with admin credentials or during runtime when auto_provision
14
15
  # is enabled.
15
16
  class Provisioner
17
+ class << self
18
+ # Provision both directions (A->B and B->A) with shared defaults.
19
+ #
20
+ # @param app_a [String] First app name
21
+ # @param app_b [String] Second app name
22
+ # @param stream_name [String] Stream used for both directions
23
+ # @param nats_url [String] NATS connection URL
24
+ # @param logger [Logger] Logger used for progress output
25
+ # @param shared_config [Hash] Additional config applied to both directions
26
+ #
27
+ # @return [void]
28
+ def provision_bidirectional!(
29
+ app_a:,
30
+ app_b:,
31
+ stream_name: 'sync-stream',
32
+ nats_url: ENV.fetch('NATS_URL', 'nats://nats:4222'),
33
+ logger: Logger.new($stdout),
34
+ **shared_config
35
+ )
36
+ [
37
+ { app_name: app_a, destination_app: app_b },
38
+ { app_name: app_b, destination_app: app_a }
39
+ ].each do |direction|
40
+ logger&.info "Provisioning #{direction[:app_name]} -> #{direction[:destination_app]}"
41
+ configure_direction(direction, stream_name, nats_url, logger, shared_config)
42
+
43
+ begin
44
+ JetstreamBridge.startup!
45
+ new.provision!
46
+ ensure
47
+ JetstreamBridge.shutdown!
48
+ end
49
+ end
50
+ end
51
+
52
+ def configure_direction(direction, stream_name, nats_url, logger, shared_config)
53
+ JetstreamBridge.configure do |cfg|
54
+ cfg.nats_urls = nats_url
55
+ cfg.app_name = direction[:app_name]
56
+ cfg.destination_app = direction[:destination_app]
57
+ cfg.stream_name = stream_name
58
+ cfg.auto_provision = true
59
+ cfg.use_outbox = false
60
+ cfg.use_inbox = false
61
+ cfg.logger = logger if logger
62
+
63
+ shared_config.each do |key, value|
64
+ setter = "#{key}="
65
+ cfg.public_send(setter, value) if cfg.respond_to?(setter)
66
+ end
67
+ end
68
+ end
69
+ private :configure_direction
70
+ end
71
+
16
72
  def initialize(config: JetstreamBridge.config)
17
73
  @config = config
18
74
  end
19
75
 
20
- # Ensure stream (and optionally consumer) exist with desired config.
76
+ # Provision stream (and optionally consumer) with desired config.
21
77
  #
22
78
  # @param jts [Object, nil] Existing JetStream context (optional)
23
- # @param ensure_consumer [Boolean] Whether to create/align the consumer too
79
+ # @param provision_consumer [Boolean] Whether to create/align the consumer too
24
80
  # @return [Object] JetStream context used for provisioning
25
- def ensure!(jts: nil, ensure_consumer: true)
81
+ def provision!(jts: nil, provision_consumer: true)
26
82
  js = jts || Connection.connect!(verify_js: true)
27
83
 
28
- ensure_stream!(jts: js)
29
- ensure_consumer!(jts: js) if ensure_consumer
84
+ provision_stream!(jts: js)
85
+ provision_consumer!(jts: js) if provision_consumer
30
86
 
31
87
  Logging.info(
32
- "Provisioned stream=#{@config.stream_name} consumer=#{@config.durable_name if ensure_consumer}",
88
+ "Provisioned stream=#{@config.stream_name} consumer=#{@config.durable_name if provision_consumer}",
33
89
  tag: 'JetstreamBridge::Provisioner'
34
90
  )
35
91
 
36
92
  js
37
93
  end
38
94
 
39
- # Ensure stream only.
95
+ # Provision stream only.
40
96
  #
41
97
  # @param jts [Object, nil] Existing JetStream context (optional)
42
98
  # @return [Object] JetStream context used
43
- def ensure_stream!(jts: nil)
99
+ def provision_stream!(jts: nil)
44
100
  js = jts || Connection.connect!(verify_js: true)
45
- Topology.ensure!(js)
101
+ Topology.provision!(js)
46
102
  Logging.info(
47
- "Stream ensured: #{@config.stream_name}",
103
+ "Stream provisioned: #{@config.stream_name}",
48
104
  tag: 'JetstreamBridge::Provisioner'
49
105
  )
50
106
  js
51
107
  end
52
108
 
53
- # Ensure durable consumer only.
109
+ # Provision durable consumer only.
54
110
  #
55
111
  # @param jts [Object, nil] Existing JetStream context (optional)
56
112
  # @return [Object] JetStream context used
57
- def ensure_consumer!(jts: nil)
113
+ def provision_consumer!(jts: nil)
58
114
  js = jts || Connection.connect!(verify_js: true)
59
115
  SubscriptionManager.new(js, @config.durable_name, @config).ensure_consumer!(force: true)
60
116
  Logging.info(
61
- "Consumer ensured: #{@config.durable_name}",
117
+ "Consumer provisioned: #{@config.durable_name}",
62
118
  tag: 'JetstreamBridge::Provisioner'
63
119
  )
64
120
  js
@@ -35,29 +35,15 @@ module JetstreamBridge
35
35
  (record.respond_to?(:status) && record.status == 'sent')
36
36
  end
37
37
 
38
- def persist_pre(record, subject, envelope)
38
+ def record_publish_attempt(record, subject, envelope)
39
39
  ActiveRecord::Base.transaction do
40
- now = Time.now.utc
41
- event_id = envelope['event_id'].to_s
42
-
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)
54
-
40
+ attrs = build_publish_attrs(record, subject, envelope)
55
41
  ModelUtils.assign_known_attrs(record, attrs)
56
42
  record.save!
57
43
  end
58
44
  end
59
45
 
60
- def persist_success(record)
46
+ def record_publish_success(record)
61
47
  ActiveRecord::Base.transaction do
62
48
  now = Time.now.utc
63
49
  attrs = { status: 'sent' }
@@ -68,7 +54,7 @@ module JetstreamBridge
68
54
  end
69
55
  end
70
56
 
71
- def persist_failure(record, message)
57
+ def record_publish_failure(record, message)
72
58
  ActiveRecord::Base.transaction do
73
59
  now = Time.now.utc
74
60
  attrs = { status: 'failed', last_error: message }
@@ -78,13 +64,42 @@ module JetstreamBridge
78
64
  end
79
65
  end
80
66
 
81
- def persist_exception(record, error)
67
+ def record_publish_exception(record, error)
82
68
  return unless record
83
69
 
84
- persist_failure(record, "#{error.class}: #{error.message}")
70
+ record_publish_failure(record, "#{error.class}: #{error.message}")
85
71
  rescue StandardError => e
86
72
  Logging.warn("Failed to persist outbox failure: #{e.class}: #{e.message}",
87
73
  tag: 'JetstreamBridge::Publisher')
88
74
  end
75
+
76
+ private
77
+
78
+ def build_publish_attrs(record, subject, envelope)
79
+ now = Time.now.utc
80
+ event_id = envelope['event_id'].to_s
81
+
82
+ attrs = {
83
+ event_id: event_id,
84
+ subject: subject,
85
+ payload: ModelUtils.json_dump(envelope),
86
+ headers: ModelUtils.json_dump({ 'nats-msg-id' => event_id }),
87
+ status: 'publishing',
88
+ last_error: nil,
89
+ resource_type: envelope['resource_type'],
90
+ resource_id: envelope['resource_id'],
91
+ event_type: envelope['type'] || envelope['event_type']
92
+ }
93
+
94
+ assign_optional_publish_attrs(record, attrs, now)
95
+ attrs
96
+ end
97
+
98
+ def assign_optional_publish_attrs(record, attrs, now)
99
+ attrs[:destination_app] = JetstreamBridge.config.destination_app if record.respond_to?(:destination_app=)
100
+ attrs[:attempts] = 1 + (record.attempts || 0) if record.respond_to?(:attempts)
101
+ attrs[:enqueued_at] = (record.enqueued_at || now) if record.respond_to?(:enqueued_at)
102
+ attrs[:updated_at] = now if record.respond_to?(:updated_at)
103
+ end
89
104
  end
90
105
  end
@@ -252,17 +252,17 @@ module JetstreamBridge
252
252
  )
253
253
  end
254
254
 
255
- repo.persist_pre(record, subject, envelope)
255
+ repo.record_publish_attempt(record, subject, envelope)
256
256
 
257
257
  result = with_retries { publish_to_nats(subject, envelope) }
258
258
  if result.success?
259
- repo.persist_success(record)
259
+ repo.record_publish_success(record)
260
260
  else
261
- repo.persist_failure(record, result.error&.message || 'Publish failed')
261
+ repo.record_publish_failure(record, result.error&.message || 'Publish failed')
262
262
  end
263
263
  result
264
264
  rescue StandardError => e
265
- repo.persist_exception(record, e) if defined?(repo) && defined?(record)
265
+ repo.record_publish_exception(record, e) if defined?(repo) && defined?(record)
266
266
  Models::PublishResult.new(
267
267
  success: false,
268
268
  event_id: envelope['event_id'],
@@ -113,12 +113,12 @@ namespace :jetstream_bridge do
113
113
 
114
114
  begin
115
115
  provision_enabled = JetstreamBridge.config.auto_provision
116
- jts = JetstreamBridge.connect_and_ensure_stream!
116
+ jts = JetstreamBridge.connect_and_provision!
117
117
 
118
118
  if provision_enabled
119
119
  puts '✓ Successfully connected to NATS'
120
120
  puts '✓ JetStream is available'
121
- puts '✓ Stream topology ensured'
121
+ puts '✓ Stream topology provisioned'
122
122
 
123
123
  # Check if we can get account info
124
124
  info = jts.account_info
@@ -174,7 +174,12 @@ module JetstreamBridge
174
174
 
175
175
  # Only include mutable fields on update (subjects, storage). Never retention.
176
176
  def apply_update(jts, name, subjects, storage: nil)
177
- params = { name: name, subjects: subjects }
177
+ # Fetch existing stream config to preserve retention
178
+ info = jts.stream_info(name)
179
+ config_data = info.config
180
+ existing_retention = config_data.respond_to?(:retention) ? config_data.retention : config_data[:retention]
181
+
182
+ params = { name: name, subjects: subjects, retention: existing_retention }
178
183
  params[:storage] = storage if storage
179
184
  jts.update_stream(**params)
180
185
  end
@@ -6,7 +6,7 @@ require_relative 'stream'
6
6
 
7
7
  module JetstreamBridge
8
8
  class Topology
9
- def self.ensure!(jts)
9
+ def self.provision!(jts)
10
10
  cfg = JetstreamBridge.config
11
11
  subjects = [cfg.source_subject, cfg.destination_subject]
12
12
  subjects << cfg.dlq_subject if cfg.use_dlq
@@ -4,5 +4,5 @@
4
4
  #
5
5
  # Version constant for the gem.
6
6
  module JetstreamBridge
7
- VERSION = '5.1.0'
7
+ VERSION = '7.0.0'
8
8
  end
@@ -140,7 +140,7 @@ module JetstreamBridge
140
140
  return if @connection_initialized
141
141
 
142
142
  config.validate!
143
- connect_and_ensure_stream!
143
+ connect_and_provision!
144
144
  @connection_initialized = true
145
145
  Logging.info('JetStream Bridge started successfully', tag: 'JetstreamBridge')
146
146
  end
@@ -197,10 +197,10 @@ module JetstreamBridge
197
197
  config.use_dlq
198
198
  end
199
199
 
200
- # Establishes a connection and ensures stream topology.
200
+ # Establishes a connection and provisions stream topology.
201
201
  #
202
202
  # @return [Object] JetStream context
203
- def connect_and_ensure_stream!
203
+ def connect_and_provision!
204
204
  config.validate!
205
205
  provision = config.auto_provision
206
206
  Connection.connect!(verify_js: provision)
@@ -208,7 +208,7 @@ module JetstreamBridge
208
208
  raise ConnectionNotEstablishedError, 'JetStream connection not available' unless jts
209
209
 
210
210
  if provision
211
- Provisioner.new(config: config).ensure_stream!(jts: jts)
211
+ Provisioner.new(config: config).provision_stream!(jts: jts)
212
212
  else
213
213
  Logging.info(
214
214
  'auto_provision=false: skipping stream provisioning and JetStream account_info. ' \
@@ -222,16 +222,11 @@ module JetstreamBridge
222
222
 
223
223
  # Provision stream/consumer using management credentials (out of band from runtime).
224
224
  #
225
- # @param ensure_consumer [Boolean] Whether to create/align the consumer along with the stream.
225
+ # @param provision_consumer [Boolean] Whether to create/align the consumer along with the stream.
226
226
  # @return [Object] JetStream context
227
- def provision!(ensure_consumer: true)
227
+ def provision!(provision_consumer: true)
228
228
  config.validate!
229
- Provisioner.new(config: config).ensure!(ensure_consumer: ensure_consumer)
230
- end
231
-
232
- # Backwards-compatible alias for the previous method name
233
- def ensure_topology!
234
- connect_and_ensure_stream!
229
+ Provisioner.new(config: config).provision!(provision_consumer: provision_consumer)
235
230
  end
236
231
 
237
232
  # Active health check for monitoring and readiness probes
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: 5.1.0
4
+ version: 7.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: 2026-01-28 00:00:00.000000000 Z
11
+ date: 2026-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -121,6 +121,7 @@ files:
121
121
  - CHANGELOG.md
122
122
  - LICENSE
123
123
  - README.md
124
+ - docs/API.md
124
125
  - docs/ARCHITECTURE.md
125
126
  - docs/GETTING_STARTED.md
126
127
  - docs/PRODUCTION.md
@@ -135,7 +136,9 @@ files:
135
136
  - lib/generators/jetstream_bridge/migrations/templates/create_jetstream_inbox_events.rb.erb
136
137
  - lib/generators/jetstream_bridge/migrations/templates/create_jetstream_outbox_events.rb.erb
137
138
  - lib/jetstream_bridge.rb
139
+ - lib/jetstream_bridge/config_helpers.rb
138
140
  - lib/jetstream_bridge/consumer/consumer.rb
141
+ - lib/jetstream_bridge/consumer/consumer_state.rb
139
142
  - lib/jetstream_bridge/consumer/dlq_publisher.rb
140
143
  - lib/jetstream_bridge/consumer/inbox/inbox_message.rb
141
144
  - lib/jetstream_bridge/consumer/inbox/inbox_processor.rb