jetstream_bridge 5.1.0 → 7.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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -0
  3. data/README.md +6 -3
  4. data/docs/API.md +395 -0
  5. data/docs/ARCHITECTURE.md +123 -171
  6. data/docs/GETTING_STARTED.md +72 -1
  7. data/docs/PRODUCTION.md +10 -3
  8. data/docs/RESTRICTED_PERMISSIONS.md +7 -14
  9. data/docs/TESTING.md +3 -3
  10. data/lib/generators/jetstream_bridge/migrations/templates/create_jetstream_inbox_events.rb.erb +29 -13
  11. data/lib/jetstream_bridge/config_helpers/lifecycle.rb +34 -0
  12. data/lib/jetstream_bridge/config_helpers.rb +118 -0
  13. data/lib/jetstream_bridge/consumer/consumer.rb +131 -41
  14. data/lib/jetstream_bridge/consumer/consumer_state.rb +58 -0
  15. data/lib/jetstream_bridge/consumer/inbox/inbox_repository.rb +12 -2
  16. data/lib/jetstream_bridge/consumer/pull_subscription_builder.rb +6 -6
  17. data/lib/jetstream_bridge/consumer/subscription_manager.rb +72 -110
  18. data/lib/jetstream_bridge/core/config.rb +31 -0
  19. data/lib/jetstream_bridge/core/connection.rb +97 -31
  20. data/lib/jetstream_bridge/core/consumer_mode_resolver.rb +64 -0
  21. data/lib/jetstream_bridge/core/duration.rb +30 -0
  22. data/lib/jetstream_bridge/models/inbox_event.rb +1 -1
  23. data/lib/jetstream_bridge/models/outbox_event.rb +1 -1
  24. data/lib/jetstream_bridge/provisioner.rb +108 -13
  25. data/lib/jetstream_bridge/publisher/outbox_repository.rb +35 -20
  26. data/lib/jetstream_bridge/publisher/publisher.rb +4 -4
  27. data/lib/jetstream_bridge/tasks/install.rake +2 -2
  28. data/lib/jetstream_bridge/topology/stream.rb +6 -1
  29. data/lib/jetstream_bridge/topology/topology.rb +1 -1
  30. data/lib/jetstream_bridge/version.rb +1 -1
  31. data/lib/jetstream_bridge.rb +8 -12
  32. metadata +7 -2
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'core/logging'
4
+ require_relative 'core/config'
5
+ require_relative 'core/consumer_mode_resolver'
6
+ require_relative 'config_helpers/lifecycle'
7
+
8
+ module JetstreamBridge
9
+ # Convenience helpers to keep example configuration lean and consistent.
10
+ module ConfigHelpers
11
+ DEFAULT_STREAM = 'sync-stream'
12
+ DEFAULT_BACKOFF = %w[1s 5s 15s 30s 60s].freeze
13
+ DEFAULT_ACK_WAIT = '30s'
14
+ DEFAULT_MAX_DELIVER = 5
15
+
16
+ module_function
17
+
18
+ # Configure a bidirectional bridge with sensible defaults.
19
+ #
20
+ # @param app_name [String] Name of the local app (publisher + consumer)
21
+ # @param destination_app [String] Remote app to sync with
22
+ # @param mode [Symbol] :non_restrictive (auto provision) or :restrictive
23
+ # @param stream_name [String] JetStream stream name
24
+ # @param nats_url [String] NATS connection URL(s)
25
+ # @param use_outbox [Boolean] Enable transactional outbox pattern
26
+ # @param use_inbox [Boolean] Enable idempotent inbox pattern
27
+ # @param logger [Logger,nil] Logger to attach to configuration
28
+ # @param overrides [Hash] Additional config overrides applied verbatim
29
+ #
30
+ # @yield [config] Optional block for further customization
31
+ #
32
+ # @return [JetstreamBridge::Config]
33
+ def configure_bidirectional(
34
+ app_name:,
35
+ destination_app:,
36
+ mode: :non_restrictive,
37
+ stream_name: DEFAULT_STREAM,
38
+ nats_url: ENV.fetch('NATS_URL', 'nats://nats:4222'),
39
+ use_outbox: true,
40
+ use_inbox: true,
41
+ logger: nil,
42
+ **overrides
43
+ )
44
+ JetstreamBridge.configure do |config|
45
+ apply_base_settings(config, app_name, destination_app, stream_name, nats_url, use_outbox, use_inbox, mode,
46
+ overrides)
47
+ apply_reliability_defaults(config, overrides)
48
+ config.logger = logger if logger
49
+ apply_overrides(config, overrides)
50
+ yield(config) if block_given?
51
+
52
+ config
53
+ end
54
+ end
55
+
56
+ # Wire JetstreamBridge lifecycle into Rails boot/shutdown.
57
+ def setup_rails_lifecycle(logger: nil, rails_app: nil)
58
+ Lifecycle.setup(logger: logger, rails_app: rails_app)
59
+ end
60
+
61
+ def restrictive?(mode)
62
+ mode.to_sym == :restrictive
63
+ end
64
+ private_class_method :restrictive?
65
+
66
+ def apply_base_settings(config, app_name, destination_app, stream_name, nats_url, use_outbox, use_inbox, mode,
67
+ overrides)
68
+ config.nats_urls = nats_url
69
+ config.app_name = app_name
70
+ config.destination_app = destination_app
71
+ config.stream_name = stream_name
72
+ config.auto_provision = !restrictive?(mode)
73
+ config.use_outbox = use_outbox
74
+ config.use_inbox = use_inbox
75
+ config.consumer_mode = resolve_consumer_mode(app_name, overrides)
76
+ end
77
+ private_class_method :apply_base_settings
78
+
79
+ # Resolve consumer_mode with priority:
80
+ # 1) explicit override passed to configure_bidirectional
81
+ # 2) per-app env via CONSUMER_MODES map or CONSUMER_MODE_<APP_NAME>
82
+ # 3) shared env CONSUMER_MODE
83
+ # 4) existing config value or :pull
84
+ def resolve_consumer_mode(app_name, overrides)
85
+ explicit = overrides[:consumer_mode] if overrides.key?(:consumer_mode)
86
+ config_default = begin
87
+ JetstreamBridge.config&.consumer_mode
88
+ rescue StandardError
89
+ nil
90
+ end
91
+
92
+ ConsumerModeResolver.resolve(
93
+ app_name: app_name,
94
+ override: explicit,
95
+ fallback: config_default || :pull
96
+ )
97
+ end
98
+ private_class_method :resolve_consumer_mode
99
+
100
+ def apply_reliability_defaults(config, overrides)
101
+ config.max_deliver = overrides.fetch(:max_deliver, DEFAULT_MAX_DELIVER)
102
+ config.ack_wait = overrides.fetch(:ack_wait, DEFAULT_ACK_WAIT)
103
+ config.backoff = overrides.fetch(:backoff, DEFAULT_BACKOFF)
104
+ end
105
+ private_class_method :apply_reliability_defaults
106
+
107
+ def apply_overrides(config, overrides)
108
+ ignored = [:max_deliver, :ack_wait, :backoff, :consumer_mode]
109
+ overrides.each do |key, value|
110
+ next if ignored.include?(key)
111
+
112
+ setter = "#{key}="
113
+ config.public_send(setter, value) if config.respond_to?(setter)
114
+ end
115
+ end
116
+ private_class_method :apply_overrides
117
+ end
118
+ end
@@ -9,6 +9,7 @@ require_relative '../core/config'
9
9
  require_relative '../core/model_utils'
10
10
  require_relative 'message_processor'
11
11
  require_relative 'subscription_manager'
12
+ require_relative 'consumer_state'
12
13
  require_relative 'inbox/inbox_processor'
13
14
 
14
15
  module JetstreamBridge
@@ -57,12 +58,91 @@ module JetstreamBridge
57
58
  TracingMiddleware = ConsumerMiddleware::TracingMiddleware
58
59
  TimeoutMiddleware = ConsumerMiddleware::TimeoutMiddleware
59
60
 
61
+ class << self
62
+ def register_consumer_for_signals(consumer)
63
+ signal_registry_mutex.synchronize do
64
+ signal_consumers << consumer
65
+ install_signal_handlers_once
66
+ end
67
+ end
68
+
69
+ def unregister_consumer_for_signals(consumer)
70
+ signal_registry_mutex.synchronize { signal_consumers.delete(consumer) }
71
+ end
72
+
73
+ def reset_signal_handlers!
74
+ signal_registry_mutex.synchronize { signal_consumers.clear }
75
+ @signal_handlers_installed = false
76
+ @previous_signal_handlers = {}
77
+ end
78
+
79
+ private
80
+
81
+ def signal_consumers
82
+ @signal_consumers ||= []
83
+ end
84
+
85
+ def signal_registry_mutex
86
+ @signal_registry_mutex ||= Mutex.new
87
+ end
88
+
89
+ def install_signal_handlers_once
90
+ return if @signal_handlers_installed
91
+
92
+ %w[INT TERM].each { |sig| install_signal_handler(sig) }
93
+ @signal_handlers_installed = true
94
+ end
95
+
96
+ def install_signal_handler(sig)
97
+ previous = nil
98
+ handler = nil
99
+ handler = proc do
100
+ broadcast_signal(sig)
101
+ invoke_previous_handler(previous, sig, handler)
102
+ rescue StandardError
103
+ # Trap contexts must stay minimal; swallow any unexpected errors
104
+ end
105
+ previous = Signal.trap(sig, &handler)
106
+ previous_signal_handlers[sig] = previous
107
+ rescue ArgumentError => e
108
+ Logging.debug("Could not set up signal handlers: #{e.message}", tag: 'JetstreamBridge::Consumer')
109
+ end
110
+
111
+ def broadcast_signal(sig)
112
+ consumers = nil
113
+ signal_registry_mutex.synchronize { consumers = signal_consumers.dup }
114
+ consumers.each do |consumer|
115
+ next unless consumer.respond_to?(:lifecycle_state)
116
+
117
+ consumer.lifecycle_state.signal!(sig)
118
+ end
119
+ rescue StandardError
120
+ # Trap safety: never raise
121
+ end
122
+
123
+ def invoke_previous_handler(previous, sig, current_handler = nil)
124
+ return if previous.nil? || previous == 'DEFAULT' || previous == 'SYSTEM_DEFAULT'
125
+ return if previous == 'IGNORE'
126
+ return if current_handler && previous.equal?(current_handler)
127
+
128
+ previous.call(sig) if previous.respond_to?(:call)
129
+ rescue StandardError
130
+ # Never bubble from trap context
131
+ end
132
+
133
+ def previous_signal_handlers
134
+ @previous_signal_handlers ||= {}
135
+ end
136
+ end
137
+
60
138
  # @return [String] Durable consumer name
61
139
  attr_reader :durable
62
140
  # @return [Integer] Batch size for message fetching
63
141
  attr_reader :batch_size
64
142
  # @return [MiddlewareChain] Middleware chain for processing
65
143
  attr_reader :middleware_chain
144
+ # Expose grouped state objects for observability/testing
145
+ attr_reader :processing_state, :lifecycle_state, :connection_state
66
146
 
67
147
  # Initialize a new Consumer instance.
68
148
  #
@@ -100,15 +180,9 @@ module JetstreamBridge
100
180
 
101
181
  @batch_size = Integer(batch_size || DEFAULT_BATCH_SIZE)
102
182
  @durable = durable_name || JetstreamBridge.config.durable_name
103
- @idle_backoff = IDLE_SLEEP_SECS
104
- @reconnect_attempts = 0
105
- @running = true
106
- @shutdown_requested = false
107
- @signal_received = nil
108
- @signal_logged = false
109
- @start_time = Time.now
110
- @iterations = 0
111
- @last_health_check = Time.now
183
+ @processing_state = ProcessingState.new(idle_backoff: IDLE_SLEEP_SECS)
184
+ @lifecycle_state = LifecycleState.new
185
+ @connection_state = ConnectionState.new
112
186
  # Use existing connection (should already be established)
113
187
  @jts = Connection.jetstream
114
188
  raise ConnectionError, 'JetStream connection not available. Call JetstreamBridge.startup! first.' unless @jts
@@ -198,24 +272,33 @@ module JetstreamBridge
198
272
  "Consumer #{@durable} started (batch=#{@batch_size}, dest=#{JetstreamBridge.config.destination_subject})…",
199
273
  tag: 'JetstreamBridge::Consumer'
200
274
  )
201
- while @running
275
+ while @lifecycle_state.running
202
276
  # Check if signal was received and log it (safe from main loop)
203
- if @signal_received && !@signal_logged
204
- Logging.info("Received #{@signal_received}, stopping consumer...", tag: 'JetstreamBridge::Consumer')
205
- @signal_logged = true
277
+ if @lifecycle_state.signal_received && !@lifecycle_state.signal_logged
278
+ Logging.info("Received #{@lifecycle_state.signal_received}, stopping consumer...",
279
+ tag: 'JetstreamBridge::Consumer')
280
+ @lifecycle_state.signal_logged = true
206
281
  end
207
282
 
283
+ Logging.debug(
284
+ "Fetching messages (iteration=#{@processing_state.iterations}, batch_size=#{@batch_size})...",
285
+ tag: 'JetstreamBridge::Consumer'
286
+ )
208
287
  processed = process_batch
288
+ Logging.debug(
289
+ "Processed #{processed} messages",
290
+ tag: 'JetstreamBridge::Consumer'
291
+ )
209
292
  idle_sleep(processed)
210
293
 
211
- @iterations += 1
294
+ @processing_state.iterations += 1
212
295
 
213
296
  # Periodic health checks every 10 minutes (600 seconds)
214
297
  perform_health_check_if_due
215
298
  end
216
299
 
217
300
  # Drain in-flight messages before exiting
218
- drain_inflight_messages if @shutdown_requested
301
+ drain_inflight_messages if @lifecycle_state.shutdown_requested
219
302
  Logging.info("Consumer #{@durable} stopped gracefully", tag: 'JetstreamBridge::Consumer')
220
303
  end
221
304
 
@@ -245,8 +328,9 @@ module JetstreamBridge
245
328
  # consumer.stop! # Stop after 10 seconds
246
329
  #
247
330
  def stop!
248
- @shutdown_requested = true
249
- @running = false
331
+ @lifecycle_state.stop!
332
+ # Allow other consumers to continue receiving signals without stale references
333
+ self.class.unregister_consumer_for_signals(self)
250
334
  Logging.info("Consumer #{@durable} shutdown requested", tag: 'JetstreamBridge::Consumer')
251
335
  end
252
336
 
@@ -264,16 +348,21 @@ module JetstreamBridge
264
348
 
265
349
  # Returns number of messages processed; 0 on timeout/idle or after recovery.
266
350
  def process_batch
351
+ Logging.debug('Calling fetch_messages...', tag: 'JetstreamBridge::Consumer')
267
352
  msgs = fetch_messages
353
+ Logging.debug("Fetched #{msgs&.size || 0} messages", tag: 'JetstreamBridge::Consumer')
268
354
  return 0 if msgs.nil? || msgs.empty?
269
355
 
270
356
  msgs.sum { |m| process_one(m) }
271
- rescue NATS::Timeout, NATS::IO::Timeout
357
+ rescue NATS::Timeout, NATS::IO::Timeout => e
358
+ Logging.debug("Fetch timeout: #{e.class}", tag: 'JetstreamBridge::Consumer')
272
359
  0
273
360
  rescue NATS::JetStream::Error => e
361
+ Logging.error("JetStream error: #{e.class} #{e.message}", tag: 'JetstreamBridge::Consumer')
274
362
  handle_js_error(e)
275
363
  rescue StandardError => e
276
364
  Logging.error("Unexpected process_batch error: #{e.class} #{e.message}", tag: 'JetstreamBridge::Consumer')
365
+ Logging.error("Backtrace: #{e.backtrace.first(5).join("\n")}", tag: 'JetstreamBridge::Consumer')
277
366
  0
278
367
  end
279
368
 
@@ -288,7 +377,17 @@ module JetstreamBridge
288
377
  end
289
378
 
290
379
  def fetch_messages_pull
291
- @psub.fetch(@batch_size, timeout: FETCH_TIMEOUT_SECS)
380
+ Logging.debug(
381
+ "fetch_messages_pull called (@psub=#{@psub.class}, batch=#{@batch_size}, timeout=#{FETCH_TIMEOUT_SECS})",
382
+ tag: 'JetstreamBridge::Consumer'
383
+ )
384
+ result = @psub.fetch(@batch_size, timeout: FETCH_TIMEOUT_SECS)
385
+ Logging.debug("fetch returned #{result&.size || 0} messages", tag: 'JetstreamBridge::Consumer')
386
+ result
387
+ rescue StandardError => e
388
+ Logging.error("fetch_messages_pull error: #{e.class} #{e.message}", tag: 'JetstreamBridge::Consumer')
389
+ Logging.error("Backtrace: #{e.backtrace.first(5).join("\n")}", tag: 'JetstreamBridge::Consumer')
390
+ raise
292
391
  end
293
392
 
294
393
  def fetch_messages_push
@@ -321,11 +420,11 @@ module JetstreamBridge
321
420
  def handle_js_error(error)
322
421
  if recoverable_consumer_error?(error)
323
422
  # Increment reconnect attempts and calculate exponential backoff
324
- @reconnect_attempts += 1
325
- backoff_secs = calculate_reconnect_backoff(@reconnect_attempts)
423
+ @connection_state.reconnect_attempts += 1
424
+ backoff_secs = calculate_reconnect_backoff(@connection_state.reconnect_attempts)
326
425
 
327
426
  Logging.warn(
328
- "Recovering subscription after error (attempt #{@reconnect_attempts}): " \
427
+ "Recovering subscription after error (attempt #{@connection_state.reconnect_attempts}): " \
329
428
  "#{error.class} #{error.message}, waiting #{backoff_secs}s",
330
429
  tag: 'JetstreamBridge::Consumer'
331
430
  )
@@ -334,7 +433,7 @@ module JetstreamBridge
334
433
  ensure_subscription!
335
434
 
336
435
  # Reset counter on successful reconnection
337
- @reconnect_attempts = 0
436
+ @connection_state.reconnect_attempts = 0
338
437
  else
339
438
  Logging.error("Fetch failed (non-recoverable): #{error.class} #{error.message}", tag: 'JetstreamBridge::Consumer')
340
439
  end
@@ -366,40 +465,31 @@ module JetstreamBridge
366
465
  def idle_sleep(processed)
367
466
  if processed.zero?
368
467
  # exponential-ish backoff with a tiny jitter to avoid sync across workers
369
- @idle_backoff = [@idle_backoff * 1.5, MAX_IDLE_BACKOFF_SECS].min
370
- sleep(@idle_backoff + (rand * 0.01))
468
+ @processing_state.idle_backoff = [@processing_state.idle_backoff * 1.5, MAX_IDLE_BACKOFF_SECS].min
469
+ sleep(@processing_state.idle_backoff + (rand * 0.01))
371
470
  else
372
- @idle_backoff = IDLE_SLEEP_SECS
471
+ @processing_state.idle_backoff = IDLE_SLEEP_SECS
373
472
  end
374
473
  end
375
474
 
376
475
  def setup_signal_handlers
377
- %w[INT TERM].each do |sig|
378
- Signal.trap(sig) do
379
- # CRITICAL: Only set flags in trap context, no I/O or mutex operations
380
- # Logging and other operations are unsafe from signal handlers
381
- @signal_received = sig
382
- @running = false
383
- @shutdown_requested = true
384
- end
385
- end
386
- rescue ArgumentError => e
387
- # Signal handlers may not be available in all environments (e.g., threads)
476
+ self.class.register_consumer_for_signals(self)
477
+ rescue StandardError => e
388
478
  Logging.debug("Could not set up signal handlers: #{e.message}", tag: 'JetstreamBridge::Consumer')
389
479
  end
390
480
 
391
481
  def perform_health_check_if_due
392
482
  now = Time.now
393
- time_since_check = now - @last_health_check
483
+ time_since_check = now - @connection_state.last_health_check
394
484
 
395
485
  return unless time_since_check >= 600 # 10 minutes
396
486
 
397
- @last_health_check = now
398
- uptime = now - @start_time
487
+ @connection_state.mark_health_check(now)
488
+ uptime = @lifecycle_state.uptime(now)
399
489
  memory_mb = memory_usage_mb
400
490
 
401
491
  Logging.info(
402
- "Consumer health: iterations=#{@iterations}, " \
492
+ "Consumer health: iterations=#{@processing_state.iterations}, " \
403
493
  "memory=#{memory_mb}MB, uptime=#{uptime.round}s",
404
494
  tag: 'JetstreamBridge::Consumer'
405
495
  )
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JetstreamBridge
4
+ class Consumer
5
+ # Tracks processing counters and backoff state.
6
+ class ProcessingState
7
+ attr_accessor :idle_backoff, :iterations
8
+
9
+ def initialize(idle_backoff:, iterations: 0)
10
+ @idle_backoff = idle_backoff
11
+ @iterations = iterations
12
+ end
13
+ end
14
+
15
+ # Tracks lifecycle flags and timing for the consumer.
16
+ class LifecycleState
17
+ attr_accessor :running, :shutdown_requested, :signal_received, :signal_logged
18
+ attr_reader :start_time
19
+
20
+ def initialize(start_time: Time.now)
21
+ @running = true
22
+ @shutdown_requested = false
23
+ @signal_received = nil
24
+ @signal_logged = false
25
+ @start_time = start_time
26
+ end
27
+
28
+ def stop!
29
+ @shutdown_requested = true
30
+ @running = false
31
+ end
32
+
33
+ def signal!(sig)
34
+ @signal_received = sig
35
+ stop!
36
+ end
37
+
38
+ def uptime(now = Time.now)
39
+ now - @start_time
40
+ end
41
+ end
42
+
43
+ # Tracks reconnection attempts and health check timing.
44
+ class ConnectionState
45
+ attr_accessor :reconnect_attempts
46
+ attr_reader :last_health_check
47
+
48
+ def initialize(now: Time.now)
49
+ @reconnect_attempts = 0
50
+ @last_health_check = now
51
+ end
52
+
53
+ def mark_health_check(now = Time.now)
54
+ @last_health_check = now
55
+ end
56
+ end
57
+ end
58
+ end
@@ -30,6 +30,9 @@ module JetstreamBridge
30
30
  ActiveRecord::Base.transaction do
31
31
  attrs = {
32
32
  event_id: msg.event_id,
33
+ event_type: msg.body['type'] || msg.body['event_type'],
34
+ resource_type: msg.body['resource_type'],
35
+ resource_id: msg.body['resource_id'],
33
36
  subject: msg.subject,
34
37
  payload: ModelUtils.json_dump(msg.body_for_store),
35
38
  headers: ModelUtils.json_dump(msg.headers),
@@ -37,10 +40,14 @@ module JetstreamBridge
37
40
  stream_seq: msg.seq,
38
41
  deliveries: msg.deliveries,
39
42
  status: 'processing',
40
- last_error: nil,
43
+ error_message: nil, # Clear any previous error
44
+ last_error: nil, # Legacy field (for backwards compatibility)
45
+ processing_attempts: (record.respond_to?(:processing_attempts) ? (record.processing_attempts || 0) + 1 : nil),
41
46
  received_at: record.respond_to?(:received_at) ? (record.received_at || msg.now) : nil,
42
47
  updated_at: record.respond_to?(:updated_at) ? msg.now : nil
43
48
  }
49
+ # Some schemas capture the producing app
50
+ attrs[:source_app] = msg.body['producer'] || msg.headers['producer'] if record.respond_to?(:source_app=)
44
51
  ModelUtils.assign_known_attrs(record, attrs)
45
52
  record.save!
46
53
  end
@@ -64,9 +71,12 @@ module JetstreamBridge
64
71
 
65
72
  ActiveRecord::Base.transaction do
66
73
  now = Time.now.utc
74
+ error_msg = "#{error.class}: #{error.message}"
67
75
  attrs = {
68
76
  status: 'failed',
69
- last_error: "#{error.class}: #{error.message}",
77
+ error_message: error_msg, # Standard field name
78
+ last_error: error_msg, # Legacy field (for backwards compatibility)
79
+ failed_at: record.respond_to?(:failed_at) ? now : nil,
70
80
  updated_at: record.respond_to?(:updated_at) ? now : nil
71
81
  }
72
82
  ModelUtils.assign_known_attrs(record, attrs)
@@ -22,8 +22,8 @@ module JetstreamBridge
22
22
  sub.instance_variable_set(:@_jsb_deliver, deliver)
23
23
  sub.instance_variable_set(:@_jsb_next_subject, "#{prefix}.CONSUMER.MSG.NEXT.#{@stream_name}.#{@durable}")
24
24
 
25
- extend_pull_subscription(sub)
26
- attach_jsi(sub)
25
+ apply_pull_subscription_extensions(sub)
26
+ attach_js_subscription_metadata(sub)
27
27
 
28
28
  Logging.info(
29
29
  "Created pull subscription without verification for consumer #{@durable} " \
@@ -36,7 +36,7 @@ module JetstreamBridge
36
36
 
37
37
  private
38
38
 
39
- def extend_pull_subscription(sub)
39
+ def apply_pull_subscription_extensions(sub)
40
40
  pull_mod = begin
41
41
  NATS::JetStream.const_get(:PullSubscription)
42
42
  rescue NameError
@@ -44,10 +44,10 @@ module JetstreamBridge
44
44
  end
45
45
 
46
46
  sub.extend(pull_mod) if pull_mod
47
- shim_fetch(sub) unless pull_mod
47
+ define_fetch_fallback(sub) unless pull_mod
48
48
  end
49
49
 
50
- def shim_fetch(sub)
50
+ def define_fetch_fallback(sub)
51
51
  Logging.warn(
52
52
  'PullSubscription mixin unavailable; using shim fetch implementation',
53
53
  tag: 'JetstreamBridge::Consumer'
@@ -78,7 +78,7 @@ module JetstreamBridge
78
78
  end
79
79
  end
80
80
 
81
- def attach_jsi(sub)
81
+ def attach_js_subscription_metadata(sub)
82
82
  js_sub_class = begin
83
83
  NATS::JetStream.const_get(:JS).const_get(:Sub)
84
84
  rescue NameError