jetstream_bridge 4.5.0 → 4.5.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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +338 -87
  3. data/README.md +3 -13
  4. data/docs/GETTING_STARTED.md +8 -12
  5. data/docs/PRODUCTION.md +13 -35
  6. data/docs/RESTRICTED_PERMISSIONS.md +399 -0
  7. data/docs/TESTING.md +33 -22
  8. data/lib/generators/jetstream_bridge/health_check/health_check_generator.rb +3 -3
  9. data/lib/generators/jetstream_bridge/initializer/templates/jetstream_bridge.rb +3 -0
  10. data/lib/jetstream_bridge/consumer/consumer.rb +100 -39
  11. data/lib/jetstream_bridge/consumer/message_processor.rb +1 -1
  12. data/lib/jetstream_bridge/consumer/subscription_manager.rb +97 -121
  13. data/lib/jetstream_bridge/core/bridge_helpers.rb +127 -0
  14. data/lib/jetstream_bridge/core/config.rb +32 -161
  15. data/lib/jetstream_bridge/core/connection.rb +508 -0
  16. data/lib/jetstream_bridge/core/connection_factory.rb +95 -0
  17. data/lib/jetstream_bridge/core/debug_helper.rb +2 -9
  18. data/lib/jetstream_bridge/core.rb +2 -0
  19. data/lib/jetstream_bridge/models/subject.rb +15 -23
  20. data/lib/jetstream_bridge/provisioner.rb +67 -0
  21. data/lib/jetstream_bridge/publisher/publisher.rb +121 -92
  22. data/lib/jetstream_bridge/rails/integration.rb +5 -8
  23. data/lib/jetstream_bridge/rails/railtie.rb +3 -4
  24. data/lib/jetstream_bridge/tasks/install.rake +17 -1
  25. data/lib/jetstream_bridge/topology/topology.rb +1 -6
  26. data/lib/jetstream_bridge/version.rb +1 -1
  27. data/lib/jetstream_bridge.rb +345 -202
  28. metadata +8 -8
  29. data/lib/jetstream_bridge/consumer/health_monitor.rb +0 -107
  30. data/lib/jetstream_bridge/core/connection_manager.rb +0 -513
  31. data/lib/jetstream_bridge/core/health_checker.rb +0 -184
  32. data/lib/jetstream_bridge/facade.rb +0 -212
  33. data/lib/jetstream_bridge/publisher/event_envelope_builder.rb +0 -110
@@ -0,0 +1,508 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nats/io/client'
4
+ require 'singleton'
5
+ require 'oj'
6
+ require_relative 'duration'
7
+ require_relative 'logging'
8
+ require_relative 'config'
9
+ require_relative '../topology/topology'
10
+
11
+ module JetstreamBridge
12
+ # Singleton connection to NATS with thread-safe initialization.
13
+ #
14
+ # This class manages a single NATS connection for the entire application,
15
+ # ensuring thread-safe access in multi-threaded environments like Rails
16
+ # with Puma or Sidekiq.
17
+ #
18
+ # Thread Safety:
19
+ # - Connection initialization is synchronized with a mutex
20
+ # - The singleton pattern ensures only one connection instance exists
21
+ # - Safe to call from multiple threads/workers simultaneously
22
+ #
23
+ # Example:
24
+ # # Safe from any thread
25
+ # jts = JetstreamBridge::Connection.connect!
26
+ # jts.publish(...)
27
+ class Connection
28
+ include Singleton
29
+
30
+ # Connection states for observability
31
+ module State
32
+ DISCONNECTED = :disconnected
33
+ CONNECTING = :connecting
34
+ CONNECTED = :connected
35
+ RECONNECTING = :reconnecting
36
+ FAILED = :failed
37
+ end
38
+
39
+ DEFAULT_CONN_OPTS = {
40
+ reconnect: true,
41
+ reconnect_time_wait: 2,
42
+ max_reconnect_attempts: 10,
43
+ connect_timeout: 5
44
+ }.freeze
45
+
46
+ VALID_NATS_SCHEMES = %w[nats nats+tls].freeze
47
+
48
+ # Class-level mutex for thread-safe connection initialization
49
+ # Using class variable to avoid race condition in mutex creation
50
+ # rubocop:disable Style/ClassVars
51
+ @@connection_lock = Mutex.new
52
+ # rubocop:enable Style/ClassVars
53
+
54
+ class << self
55
+ # Thread-safe delegator to the singleton instance.
56
+ # Returns a live JetStream context.
57
+ #
58
+ # Safe to call from multiple threads - uses class-level mutex for synchronization.
59
+ #
60
+ # @return [NATS::JetStream::JS] JetStream context
61
+ def connect!(verify_js: nil)
62
+ @@connection_lock.synchronize { instance.connect!(verify_js: verify_js) }
63
+ end
64
+
65
+ # Optional accessors if callers need raw handles
66
+ def nc
67
+ instance.__send__(:nc)
68
+ end
69
+
70
+ def jetstream
71
+ instance.__send__(:jetstream)
72
+ end
73
+ end
74
+
75
+ # Idempotent: returns an existing, healthy JetStream context or establishes one.
76
+ def connect!(verify_js: nil)
77
+ verify_js = config_auto_provision if verify_js.nil?
78
+ # Check if already connected without acquiring mutex (for performance)
79
+ return @jts if @jts && @nc&.connected?
80
+
81
+ servers = nats_servers
82
+ raise 'No NATS URLs configured' if servers.empty?
83
+
84
+ @state = State::CONNECTING
85
+ establish_connection_with_retry(servers, verify_js: verify_js)
86
+
87
+ Logging.info(
88
+ "Connected to NATS (#{servers.size} server#{'s' unless servers.size == 1}): " \
89
+ "#{sanitize_urls(servers).join(', ')}",
90
+ tag: 'JetstreamBridge::Connection'
91
+ )
92
+
93
+ @connected_at = Time.now.utc
94
+ @state = State::CONNECTED
95
+ @jts
96
+ rescue StandardError
97
+ @state = State::FAILED
98
+ cleanup_connection!
99
+ raise
100
+ end
101
+
102
+ # Public API for checking connection status
103
+ #
104
+ # Uses cached health check result to avoid excessive network calls.
105
+ # Cache expires after 30 seconds.
106
+ #
107
+ # Thread-safe: Cache updates are synchronized to prevent race conditions.
108
+ #
109
+ # @param skip_cache [Boolean] Force fresh health check, bypass cache
110
+ # @return [Boolean] true if NATS client is connected and JetStream is healthy
111
+ def connected?(skip_cache: false)
112
+ return false unless @nc&.connected?
113
+ return false unless @jts
114
+
115
+ # Use cached result if available and fresh
116
+ now = Time.now.to_i
117
+ return @cached_health_status if !skip_cache && @last_health_check && (now - @last_health_check) < 30
118
+
119
+ # Thread-safe cache update to prevent race conditions
120
+ @@connection_lock.synchronize do
121
+ # Double-check after acquiring lock (another thread may have updated)
122
+ now = Time.now.to_i
123
+ return @cached_health_status if !skip_cache && @last_health_check && (now - @last_health_check) < 30
124
+
125
+ # Perform actual health check (management APIs optional)
126
+ @cached_health_status = jetstream_healthy?(verify_js: config_auto_provision)
127
+ @last_health_check = now
128
+ @cached_health_status
129
+ end
130
+ end
131
+
132
+ # Public API for getting connection timestamp
133
+ # @return [Time, nil] timestamp when connection was established
134
+ attr_reader :connected_at
135
+
136
+ # Last reconnection error metadata (exposed for health checks/diagnostics)
137
+ attr_reader :last_reconnect_error, :last_reconnect_error_at
138
+
139
+ # Get current connection state
140
+ #
141
+ # @return [Symbol] Current connection state (see State module)
142
+ def state
143
+ return State::DISCONNECTED unless @nc
144
+ return State::FAILED if @last_reconnect_error && !@nc.connected?
145
+ return State::RECONNECTING if @reconnecting
146
+
147
+ @nc.connected? ? (@state || State::CONNECTED) : State::DISCONNECTED
148
+ end
149
+
150
+ private
151
+
152
+ def jetstream_healthy?(verify_js:)
153
+ # Lightweight health when management APIs are disabled
154
+ return ping_only_health unless verify_js
155
+
156
+ # Verify JetStream responds to simple API call
157
+ @jts.account_info
158
+ true
159
+ rescue StandardError => e
160
+ Logging.warn(
161
+ "JetStream health check failed: #{e.class} #{e.message}",
162
+ tag: 'JetstreamBridge::Connection'
163
+ )
164
+ false
165
+ end
166
+
167
+ def ping_only_health
168
+ return false unless @nc&.connected?
169
+
170
+ # Flush acts as a ping/pong round-trip without hitting JetStream management subjects
171
+ @nc.flush(0.5)
172
+ true
173
+ rescue StandardError => e
174
+ Logging.warn(
175
+ "NATS connectivity check failed: #{e.class} #{e.message}",
176
+ tag: 'JetstreamBridge::Connection'
177
+ )
178
+ false
179
+ end
180
+
181
+ def nats_servers
182
+ servers = JetstreamBridge.config.nats_urls
183
+ .to_s
184
+ .split(',')
185
+ .map(&:strip)
186
+ .reject(&:empty?)
187
+
188
+ validate_nats_urls!(servers)
189
+ servers
190
+ end
191
+
192
+ def establish_connection_with_retry(servers, verify_js:)
193
+ attempts = 0
194
+ max_attempts = JetstreamBridge.config.connect_retry_attempts
195
+ retry_delay = JetstreamBridge.config.connect_retry_delay
196
+
197
+ begin
198
+ attempts += 1
199
+ establish_connection(servers, verify_js: verify_js)
200
+ rescue ConnectionError => e
201
+ if attempts < max_attempts
202
+ delay = retry_delay * attempts
203
+ Logging.warn(
204
+ "Connection attempt #{attempts}/#{max_attempts} failed: #{e.message}. " \
205
+ "Retrying in #{delay}s...",
206
+ tag: 'JetstreamBridge::Connection'
207
+ )
208
+ sleep(delay)
209
+ retry
210
+ else
211
+ Logging.error(
212
+ "Failed to establish connection after #{attempts} attempts",
213
+ tag: 'JetstreamBridge::Connection'
214
+ )
215
+ cleanup_connection!
216
+ raise
217
+ end
218
+ end
219
+ end
220
+
221
+ def establish_connection(servers, verify_js:)
222
+ # Use mock NATS client if explicitly enabled for testing
223
+ # This allows test helpers to inject a mock without affecting normal operation
224
+ @nc = if defined?(JetstreamBridge::TestHelpers) &&
225
+ JetstreamBridge::TestHelpers.respond_to?(:test_mode?) &&
226
+ JetstreamBridge::TestHelpers.test_mode? &&
227
+ JetstreamBridge.instance_variable_defined?(:@mock_nats_client)
228
+ JetstreamBridge.instance_variable_get(:@mock_nats_client)
229
+ else
230
+ NATS::IO::Client.new
231
+ end
232
+
233
+ # Setup reconnect handler to refresh JetStream context
234
+ @nc.on_reconnect do
235
+ @reconnecting = true
236
+ Logging.info(
237
+ 'NATS reconnected, refreshing JetStream context',
238
+ tag: 'JetstreamBridge::Connection'
239
+ )
240
+ refresh_jetstream_context
241
+ @reconnecting = false
242
+ end
243
+
244
+ @nc.on_disconnect do |reason|
245
+ @state = State::DISCONNECTED
246
+ Logging.warn(
247
+ "NATS disconnected: #{reason}",
248
+ tag: 'JetstreamBridge::Connection'
249
+ )
250
+ end
251
+
252
+ @nc.on_error do |err|
253
+ Logging.error(
254
+ "NATS error: #{err}",
255
+ tag: 'JetstreamBridge::Connection'
256
+ )
257
+ end
258
+
259
+ # Only connect if not already connected (mock may be pre-connected)
260
+ # Note: For test helpers mock, skip connect. For RSpec mocks, always call connect
261
+ skip_connect = @nc.connected? &&
262
+ defined?(JetstreamBridge::TestHelpers) &&
263
+ JetstreamBridge::TestHelpers.respond_to?(:test_mode?) &&
264
+ JetstreamBridge::TestHelpers.test_mode?
265
+
266
+ @nc.connect({ servers: servers }.merge(DEFAULT_CONN_OPTS)) unless skip_connect
267
+
268
+ # Verify connection is established
269
+ verify_connection!
270
+
271
+ # Create JetStream context
272
+ @jts = @nc.jetstream
273
+
274
+ # Verify JetStream is available
275
+ if verify_js
276
+ verify_jetstream!
277
+ if config_auto_provision
278
+ Topology.ensure!(@jts)
279
+ Logging.info(
280
+ 'Topology ensured after connection (auto_provision=true).',
281
+ tag: 'JetstreamBridge::Connection'
282
+ )
283
+ end
284
+ else
285
+ Logging.info(
286
+ 'Skipping JetStream account_info verification (auto_provision=false). ' \
287
+ 'Assuming JetStream is enabled.',
288
+ tag: 'JetstreamBridge::Connection'
289
+ )
290
+ end
291
+
292
+ # Ensure JetStream responds to #nc
293
+ return if @jts.respond_to?(:nc)
294
+
295
+ nc_ref = @nc
296
+ @jts.define_singleton_method(:nc) { nc_ref }
297
+ end
298
+
299
+ def validate_nats_urls!(servers)
300
+ Logging.debug(
301
+ "Validating #{servers.size} NATS URL(s): #{sanitize_urls(servers).join(', ')}",
302
+ tag: 'JetstreamBridge::Connection'
303
+ )
304
+
305
+ servers.each do |url|
306
+ # Check for basic URL format (scheme://host)
307
+ unless url.include?('://')
308
+ Logging.error(
309
+ "Invalid URL format (missing scheme): #{url}",
310
+ tag: 'JetstreamBridge::Connection'
311
+ )
312
+ raise ConnectionError, "Invalid NATS URL format: #{url}. Expected format: nats://host:port"
313
+ end
314
+
315
+ uri = URI.parse(url)
316
+
317
+ # Validate scheme
318
+ scheme = uri.scheme&.downcase
319
+ unless VALID_NATS_SCHEMES.include?(scheme)
320
+ Logging.error(
321
+ "Invalid URL scheme '#{uri.scheme}': #{Logging.sanitize_url(url)}",
322
+ tag: 'JetstreamBridge::Connection'
323
+ )
324
+ raise ConnectionError, "Invalid NATS URL scheme '#{uri.scheme}' in: #{url}. Expected 'nats' or 'nats+tls'"
325
+ end
326
+
327
+ # Validate host is present
328
+ if uri.host.nil? || uri.host.empty?
329
+ Logging.error(
330
+ "Missing host in URL: #{Logging.sanitize_url(url)}",
331
+ tag: 'JetstreamBridge::Connection'
332
+ )
333
+ raise ConnectionError, "Invalid NATS URL - missing host: #{url}"
334
+ end
335
+
336
+ # Validate port if present
337
+ if uri.port && (uri.port < 1 || uri.port > 65_535)
338
+ Logging.error(
339
+ "Invalid port #{uri.port} in URL: #{Logging.sanitize_url(url)}",
340
+ tag: 'JetstreamBridge::Connection'
341
+ )
342
+ raise ConnectionError, "Invalid NATS URL - port must be 1-65535: #{url}"
343
+ end
344
+
345
+ Logging.debug(
346
+ "URL validated: #{Logging.sanitize_url(url)}",
347
+ tag: 'JetstreamBridge::Connection'
348
+ )
349
+ rescue URI::InvalidURIError => e
350
+ Logging.error(
351
+ "Malformed URL: #{url} (#{e.message})",
352
+ tag: 'JetstreamBridge::Connection'
353
+ )
354
+ raise ConnectionError, "Invalid NATS URL format: #{url} (#{e.message})"
355
+ end
356
+
357
+ Logging.info(
358
+ 'All NATS URLs validated successfully',
359
+ tag: 'JetstreamBridge::Connection'
360
+ )
361
+ end
362
+
363
+ def verify_connection!
364
+ Logging.debug(
365
+ 'Verifying NATS connection...',
366
+ tag: 'JetstreamBridge::Connection'
367
+ )
368
+
369
+ unless @nc.connected?
370
+ Logging.error(
371
+ 'NATS connection verification failed - client not connected',
372
+ tag: 'JetstreamBridge::Connection'
373
+ )
374
+ raise ConnectionError, 'Failed to establish connection to NATS server(s)'
375
+ end
376
+
377
+ Logging.info(
378
+ 'NATS connection verified successfully',
379
+ tag: 'JetstreamBridge::Connection'
380
+ )
381
+ end
382
+
383
+ def verify_jetstream!
384
+ Logging.debug(
385
+ 'Verifying JetStream availability...',
386
+ tag: 'JetstreamBridge::Connection'
387
+ )
388
+
389
+ # Verify JetStream is enabled by checking account info
390
+ account_info = @jts.account_info
391
+
392
+ # Handle both object-style and hash-style access for compatibility
393
+ streams = account_info.respond_to?(:streams) ? account_info.streams : account_info[:streams]
394
+ consumers = account_info.respond_to?(:consumers) ? account_info.consumers : account_info[:consumers]
395
+ memory = account_info.respond_to?(:memory) ? account_info.memory : account_info[:memory]
396
+ storage = account_info.respond_to?(:storage) ? account_info.storage : account_info[:storage]
397
+
398
+ Logging.info(
399
+ "JetStream verified - Streams: #{streams}, " \
400
+ "Consumers: #{consumers}, " \
401
+ "Memory: #{format_bytes(memory)}, " \
402
+ "Storage: #{format_bytes(storage)}",
403
+ tag: 'JetstreamBridge::Connection'
404
+ )
405
+ rescue NATS::IO::NoRespondersError
406
+ Logging.error(
407
+ 'JetStream not available - no responders (JetStream not enabled)',
408
+ tag: 'JetstreamBridge::Connection'
409
+ )
410
+ raise ConnectionError, 'JetStream not enabled on NATS server. Please enable JetStream with -js flag'
411
+ rescue StandardError => e
412
+ Logging.error(
413
+ "JetStream verification failed: #{e.class} - #{e.message}",
414
+ tag: 'JetstreamBridge::Connection'
415
+ )
416
+ raise ConnectionError, "JetStream verification failed: #{e.message}"
417
+ end
418
+
419
+ def format_bytes(bytes)
420
+ return 'N/A' if bytes.nil? || bytes.zero?
421
+
422
+ units = %w[B KB MB GB TB]
423
+ exp = (Math.log(bytes) / Math.log(1024)).to_i
424
+ exp = [exp, units.length - 1].min
425
+ "#{(bytes / (1024.0**exp)).round(2)} #{units[exp]}"
426
+ end
427
+
428
+ def refresh_jetstream_context
429
+ @jts = @nc.jetstream
430
+ nc_ref = @nc
431
+ @jts.define_singleton_method(:nc) { nc_ref } unless @jts.respond_to?(:nc)
432
+
433
+ # Re-ensure topology after reconnect when allowed
434
+ if config_auto_provision
435
+ Topology.ensure!(@jts)
436
+ else
437
+ Logging.info(
438
+ 'Skipping topology provisioning after reconnect (auto_provision=false).',
439
+ tag: 'JetstreamBridge::Connection'
440
+ )
441
+ end
442
+
443
+ # Invalidate health check cache on successful reconnect
444
+ @cached_health_status = nil
445
+ @last_health_check = nil
446
+
447
+ # Clear error state on successful reconnect
448
+ @last_reconnect_error = nil
449
+ @last_reconnect_error_at = nil
450
+ @state = State::CONNECTED
451
+
452
+ Logging.info(
453
+ 'JetStream context refreshed successfully after reconnect',
454
+ tag: 'JetstreamBridge::Connection'
455
+ )
456
+ rescue StandardError => e
457
+ # Store error state for diagnostics
458
+ @last_reconnect_error = e
459
+ @last_reconnect_error_at = Time.now
460
+ @state = State::FAILED
461
+ cleanup_connection!(close_nc: false)
462
+ Logging.error(
463
+ "Failed to refresh JetStream context: #{e.class} #{e.message}",
464
+ tag: 'JetstreamBridge::Connection'
465
+ )
466
+
467
+ # Invalidate health check cache to force re-check
468
+ @cached_health_status = false
469
+ @last_health_check = Time.now.to_i
470
+ end
471
+
472
+ # Expose for class-level helpers (not part of public API)
473
+ attr_reader :nc
474
+
475
+ def jetstream
476
+ @jts
477
+ end
478
+
479
+ # Mask credentials in NATS URLs:
480
+ # - "nats://user:pass@host:4222" -> "nats://user:***@host:4222"
481
+ # - "nats://token@host:4222" -> "nats://***@host:4222"
482
+ def sanitize_urls(urls)
483
+ urls.map { |u| Logging.sanitize_url(u) }
484
+ end
485
+
486
+ def cleanup_connection!(close_nc: true)
487
+ begin
488
+ # Avoid touching RSpec doubles used in unit tests
489
+ is_rspec_double = defined?(RSpec::Mocks::Double) && @nc.is_a?(RSpec::Mocks::Double)
490
+ @nc.close if !is_rspec_double && close_nc && @nc.respond_to?(:close) && @nc.connected?
491
+ rescue StandardError
492
+ # ignore cleanup errors
493
+ end
494
+ @nc = nil
495
+ @jts = nil
496
+ @cached_health_status = nil
497
+ @last_health_check = nil
498
+ @connected_at = nil
499
+ end
500
+
501
+ def config_auto_provision
502
+ cfg = JetstreamBridge.config
503
+ cfg.respond_to?(:auto_provision) ? cfg.auto_provision : true
504
+ rescue StandardError
505
+ true
506
+ end
507
+ end
508
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'connection'
4
+ require_relative '../errors'
5
+
6
+ module JetstreamBridge
7
+ module Core
8
+ # Factory for creating and managing NATS connections
9
+ class ConnectionFactory
10
+ # Connection options builder
11
+ class ConnectionOptions
12
+ DEFAULT_OPTS = JetstreamBridge::Connection::DEFAULT_CONN_OPTS
13
+
14
+ attr_accessor :servers, :reconnect, :reconnect_time_wait,
15
+ :max_reconnect_attempts, :connect_timeout,
16
+ :name, :user, :pass, :token
17
+ attr_reader :additional_opts
18
+
19
+ def initialize(servers: nil, **opts)
20
+ @servers = normalize_servers(servers) if servers
21
+ @additional_opts = {}
22
+
23
+ DEFAULT_OPTS.merge(opts).each do |key, value|
24
+ if respond_to?(:"#{key}=")
25
+ send(:"#{key}=", value)
26
+ else
27
+ @additional_opts[key] = value
28
+ end
29
+ end
30
+ end
31
+
32
+ def self.build(opts = {})
33
+ new(**opts)
34
+ end
35
+
36
+ def to_h
37
+ base = {
38
+ reconnect: @reconnect,
39
+ reconnect_time_wait: @reconnect_time_wait,
40
+ max_reconnect_attempts: @max_reconnect_attempts,
41
+ connect_timeout: @connect_timeout
42
+ }
43
+
44
+ base[:servers] = @servers if @servers
45
+ base[:name] = @name if @name
46
+ base[:user] = @user if @user
47
+ base[:pass] = @pass if @pass
48
+ base[:token] = @token if @token
49
+
50
+ base.merge(@additional_opts)
51
+ end
52
+
53
+ private
54
+
55
+ def normalize_servers(servers)
56
+ Array(servers)
57
+ .flat_map { |s| s.to_s.split(',') }
58
+ .map(&:strip)
59
+ .reject(&:empty?)
60
+ end
61
+ end
62
+
63
+ class << self
64
+ # Create connection options from config
65
+ def build_options(config = JetstreamBridge.config)
66
+ servers = config.nats_urls
67
+ raise ConnectionNotEstablishedError, 'No NATS URLs configured' if servers.to_s.strip.empty?
68
+
69
+ ConnectionOptions.new(
70
+ servers: servers,
71
+ name: config.app_name
72
+ )
73
+ end
74
+
75
+ # Create a new NATS client
76
+ def create_client(options = nil)
77
+ opts = options || build_options
78
+ client = NATS::IO::Client.new
79
+ client.connect(opts.to_h)
80
+ client
81
+ end
82
+
83
+ # Create JetStream context with health monitoring
84
+ def create_jetstream(client)
85
+ jts = client.jetstream
86
+
87
+ # Ensure JetStream responds to #nc
88
+ jts.define_singleton_method(:nc) { client } unless jts.respond_to?(:nc)
89
+
90
+ jts
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -32,11 +32,7 @@ module JetstreamBridge
32
32
  {
33
33
  app_name: cfg.app_name,
34
34
  destination_app: cfg.destination_app,
35
- stream_name: begin
36
- cfg.stream_name
37
- rescue StandardError
38
- 'ERROR'
39
- end,
35
+ stream_name: cfg.stream_name,
40
36
  source_subject: begin
41
37
  cfg.source_subject
42
38
  rescue StandardError
@@ -61,9 +57,7 @@ module JetstreamBridge
61
57
  use_inbox: cfg.use_inbox,
62
58
  use_dlq: cfg.use_dlq,
63
59
  outbox_model: cfg.outbox_model,
64
- inbox_model: cfg.inbox_model,
65
- inbox_prefix: cfg.inbox_prefix,
66
- disable_js_api: cfg.disable_js_api
60
+ inbox_model: cfg.inbox_model
67
61
  }
68
62
  end
69
63
 
@@ -81,7 +75,6 @@ module JetstreamBridge
81
75
 
82
76
  def stream_debug
83
77
  return { error: 'Not connected' } unless Connection.instance.connected?
84
- return { error: 'JS API disabled' } if JetstreamBridge.config.disable_js_api
85
78
 
86
79
  jts = Connection.jetstream
87
80
  cfg = JetstreamBridge.config
@@ -4,3 +4,5 @@
4
4
  require_relative 'core/config'
5
5
  require_relative 'core/duration'
6
6
  require_relative 'core/logging'
7
+ require_relative 'core/connection'
8
+ require_relative 'core/bridge_helpers'