nats_wave 1.1.4 → 1.1.7
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/.idea/nats_wave.iml +5 -5
- data/Gemfile.lock +1 -1
- data/README.md +1153 -401
- data/lib/generators/nats_wave/templates/initializer.rb +103 -54
- data/lib/nats_wave/adapters/datadog_metrics.rb +1 -1
- data/lib/nats_wave/client.rb +638 -29
- data/lib/nats_wave/concerns/mappable.rb +9 -3
- data/lib/nats_wave/configuration.rb +5 -4
- data/lib/nats_wave/dead_letter_queue.rb +4 -4
- data/lib/nats_wave/model_mapper.rb +1 -1
- data/lib/nats_wave/model_registry.rb +39 -15
- data/lib/nats_wave/publisher.rb +56 -61
- data/lib/nats_wave/railtie.rb +4 -3
- data/lib/nats_wave/subscriber.rb +127 -47
- data/lib/nats_wave/version.rb +1 -1
- data/lib/nats_wave.rb +2 -1
- metadata +2 -5
- data/examples/catalog_model.rb +0 -36
- data/examples/configuration_examples.rb +0 -68
- data/examples/user_model.rb +0 -58
data/lib/nats_wave/client.rb
CHANGED
@@ -1,15 +1,478 @@
|
|
1
|
+
# # # # frozen_string_literal: true
|
2
|
+
# # #
|
3
|
+
# # # module NatsWave
|
4
|
+
# # # class Client
|
5
|
+
# # # attr_reader :config, :publisher, :subscriber, :client
|
6
|
+
# # #
|
7
|
+
# # # def initialize(options = {})
|
8
|
+
# # # @config = Configuration.new(options)
|
9
|
+
# # # @client = nil
|
10
|
+
# # # @publisher = nil
|
11
|
+
# # # @subscriber = nil
|
12
|
+
# # # @middleware_stack = []
|
13
|
+
# # #
|
14
|
+
# # # setup_connection_pool
|
15
|
+
# # # setup_middleware
|
16
|
+
# # # establish_connections
|
17
|
+
# # # end
|
18
|
+
# # #
|
19
|
+
# # # def publish(subject:, model:, action:, data:, metadata: {})
|
20
|
+
# # # ensure_connected!
|
21
|
+
# # # @publisher.publish(
|
22
|
+
# # # subject: subject,
|
23
|
+
# # # model: model,
|
24
|
+
# # # action: action,
|
25
|
+
# # # data: data,
|
26
|
+
# # # metadata: metadata
|
27
|
+
# # # )
|
28
|
+
# # # end
|
29
|
+
# # #
|
30
|
+
# # # def publish_batch(events)
|
31
|
+
# # # ensure_connected!
|
32
|
+
# # # @publisher.publish_batch(events)
|
33
|
+
# # # end
|
34
|
+
# # #
|
35
|
+
# # # def subscribe(subjects:, model_mappings: {}, &block)
|
36
|
+
# # # NatsWave.logger.info "Ensuring connection..."
|
37
|
+
# # # ensure_connected!
|
38
|
+
# # # raise "Subscriber not initialized" unless @subscriber
|
39
|
+
# # #
|
40
|
+
# # # NatsWave.logger.info "Subscribing to subjects: #{subjects.inspect}"
|
41
|
+
# # # NatsWave.logger.info "Subscribing with model mapping: #{model_mappings.inspect}"
|
42
|
+
# # # # puts "Subscribing to subjects: #{subjects.inspect}"
|
43
|
+
# # # @subscriber.listen(
|
44
|
+
# # # subjects: subjects,
|
45
|
+
# # # model_mappings: model_mappings,
|
46
|
+
# # # &block
|
47
|
+
# # # )
|
48
|
+
# # # end
|
49
|
+
# # #
|
50
|
+
# # #
|
51
|
+
# # # def start_subscriber
|
52
|
+
# # # ensure_connected!
|
53
|
+
# # # @subscriber.begin if @subscriber
|
54
|
+
# # # end
|
55
|
+
# # #
|
56
|
+
# # # def health_check
|
57
|
+
# # # {
|
58
|
+
# # # nats_connected: connected?,
|
59
|
+
# # # database_connected: database_connected?,
|
60
|
+
# # # nats_url: @config.nats_url,
|
61
|
+
# # # nats_server_url: @config.nats_server_url,
|
62
|
+
# # # service_name: @config.service_name,
|
63
|
+
# # # version: @config.version,
|
64
|
+
# # # instance_id: @config.instance_id,
|
65
|
+
# # # published_subjects: @config.subject_patterns,
|
66
|
+
# # # timestamp: Time.current.iso8601,
|
67
|
+
# # # }
|
68
|
+
# # # end
|
69
|
+
# # #
|
70
|
+
# # # def connected?
|
71
|
+
# # # @client && @client.connected?
|
72
|
+
# # # end
|
73
|
+
# # #
|
74
|
+
# # # def disconnect!
|
75
|
+
# # # @subscriber&.reset
|
76
|
+
# # # @client&.close
|
77
|
+
# # # @connection_pool&.shutdown
|
78
|
+
# # # @connection_pool&.wait_for_termination(5)
|
79
|
+
# # # end
|
80
|
+
# # #
|
81
|
+
# # # private
|
82
|
+
# # #
|
83
|
+
# # # def setup_connection_pool
|
84
|
+
# # # return unless defined?(Concurrent)
|
85
|
+
# # #
|
86
|
+
# # # @connection_pool = Concurrent::ThreadPoolExecutor.new(
|
87
|
+
# # # min_threads: 5,
|
88
|
+
# # # max_threads: @config.connection_pool_size
|
89
|
+
# # # )
|
90
|
+
# # # end
|
91
|
+
# # #
|
92
|
+
# # # def setup_middleware
|
93
|
+
# # # @middleware_stack = []
|
94
|
+
# # #
|
95
|
+
# # # if @config.middleware_authentication_enabled
|
96
|
+
# # # @middleware_stack << Middleware::Authentication.new(@config)
|
97
|
+
# # # end
|
98
|
+
# # #
|
99
|
+
# # # if @config.middleware_validation_enabled
|
100
|
+
# # # @middleware_stack << Middleware::Validation.new(@config)
|
101
|
+
# # # end
|
102
|
+
# # #
|
103
|
+
# # # if @config.middleware_logging_enabled
|
104
|
+
# # # @middleware_stack << Middleware::Logging.new(@config)
|
105
|
+
# # # end
|
106
|
+
# # # end
|
107
|
+
# # #
|
108
|
+
# # # def establish_connections
|
109
|
+
# # # return unless nats_available?
|
110
|
+
# # #
|
111
|
+
# # # establish_nats_connection
|
112
|
+
# # #
|
113
|
+
# # # if @config.publishing_enabled
|
114
|
+
# # # @publisher = Publisher.new(@config, @client, @middleware_stack)
|
115
|
+
# # # end
|
116
|
+
# # #
|
117
|
+
# # # if @config.subscription_enabled
|
118
|
+
# # # @subscriber = Subscriber.new(@config, @client, @middleware_stack)
|
119
|
+
# # # end
|
120
|
+
# # # end
|
121
|
+
# # #
|
122
|
+
# # # def establish_nats_connection
|
123
|
+
# # # @client = NATS.connect(
|
124
|
+
# # # @config.nats_url,
|
125
|
+
# # # reconnect_time_wait: @config.retry_delay,
|
126
|
+
# # # max_reconnect_attempts: @config.reconnect_attempts
|
127
|
+
# # # )
|
128
|
+
# # # NatsWave.logger.info("Connected to NATS at #{@config.nats_url}")
|
129
|
+
# # # rescue => e
|
130
|
+
# # # NatsWave.logger.error("Failed to connect to NATS: #{e.message}")
|
131
|
+
# # # raise ConnectionError, "Failed to connect to NATS: #{e.message}"
|
132
|
+
# # # end
|
133
|
+
# # #
|
134
|
+
# # # def ensure_connected!
|
135
|
+
# # # raise ConnectionError, "Not connected to NATS" unless connected?
|
136
|
+
# # # end
|
137
|
+
# # #
|
138
|
+
# # # def database_connected?
|
139
|
+
# # # return false unless @subscriber
|
140
|
+
# # # @subscriber.database_connected?
|
141
|
+
# # # end
|
142
|
+
# # #
|
143
|
+
# # # def nats_available?
|
144
|
+
# # # defined?(NATS)
|
145
|
+
# # # rescue LoadError
|
146
|
+
# # # false
|
147
|
+
# # # end
|
148
|
+
# # # end
|
149
|
+
# # # end
|
150
|
+
# #
|
151
|
+
# #
|
152
|
+
# # # frozen_string_literal: true
|
153
|
+
# #
|
154
|
+
# # module NatsWave
|
155
|
+
# # class Client
|
156
|
+
# # attr_reader :config, :publisher, :subscriber, :client
|
157
|
+
# #
|
158
|
+
# # def initialize(options = {})
|
159
|
+
# # @config = Configuration.new(options)
|
160
|
+
# # @client = nil
|
161
|
+
# # @publisher = nil
|
162
|
+
# # @subscriber = nil
|
163
|
+
# # @middleware_stack = []
|
164
|
+
# # @shutdown = false
|
165
|
+
# #
|
166
|
+
# # setup_connection_pool
|
167
|
+
# # setup_middleware
|
168
|
+
# # establish_connections
|
169
|
+
# # setup_signal_handlers
|
170
|
+
# # end
|
171
|
+
# #
|
172
|
+
# # def start_subscriber
|
173
|
+
# # ensure_connected!
|
174
|
+
# # return unless @subscriber
|
175
|
+
# #
|
176
|
+
# # @subscriber.begin
|
177
|
+
# #
|
178
|
+
# # # Keep the main thread alive in console/rake tasks
|
179
|
+
# # if defined?(Rails::Console) || $PROGRAM_NAME.include?('rake')
|
180
|
+
# # keep_main_thread_alive
|
181
|
+
# # end
|
182
|
+
# # end
|
183
|
+
# #
|
184
|
+
# # def disconnect!
|
185
|
+
# # @shutdown = true
|
186
|
+
# #
|
187
|
+
# # NatsWave.logger.info "🛑 Shutting down NatsWave client..."
|
188
|
+
# #
|
189
|
+
# # @subscriber&.reset
|
190
|
+
# # @client&.close if @client&.connected?
|
191
|
+
# # @connection_pool&.shutdown
|
192
|
+
# # @connection_pool&.wait_for_termination(5)
|
193
|
+
# #
|
194
|
+
# # NatsWave.logger.info "✅ NatsWave client shutdown complete"
|
195
|
+
# # end
|
196
|
+
# #
|
197
|
+
# # private
|
198
|
+
# #
|
199
|
+
# # def setup_signal_handlers
|
200
|
+
# # # Handle graceful shutdown
|
201
|
+
# # ['INT', 'TERM'].each do |signal|
|
202
|
+
# # Signal.trap(signal) do
|
203
|
+
# # NatsWave.logger.info "🛑 Received #{signal}, shutting down gracefully..."
|
204
|
+
# # disconnect!
|
205
|
+
# # exit(0)
|
206
|
+
# # end
|
207
|
+
# # end
|
208
|
+
# # end
|
209
|
+
# #
|
210
|
+
# # def keep_main_thread_alive
|
211
|
+
# # # This prevents the main thread from exiting in console/rake tasks
|
212
|
+
# # Thread.new do
|
213
|
+
# # while !@shutdown
|
214
|
+
# # sleep 1
|
215
|
+
# # end
|
216
|
+
# # end.join
|
217
|
+
# # rescue Interrupt
|
218
|
+
# # NatsWave.logger.info "🛑 Interrupted, shutting down..."
|
219
|
+
# # disconnect!
|
220
|
+
# # end
|
221
|
+
# #
|
222
|
+
# # def setup_connection_pool
|
223
|
+
# # return unless defined?(Concurrent)
|
224
|
+
# #
|
225
|
+
# # @connection_pool = Concurrent::ThreadPoolExecutor.new(
|
226
|
+
# # min_threads: 5,
|
227
|
+
# # max_threads: @config.connection_pool_size
|
228
|
+
# # )
|
229
|
+
# # end
|
230
|
+
# #
|
231
|
+
# # def setup_middleware
|
232
|
+
# # @middleware_stack = []
|
233
|
+
# #
|
234
|
+
# # if @config.middleware_authentication_enabled
|
235
|
+
# # @middleware_stack << Middleware::Authentication.new(@config)
|
236
|
+
# # end
|
237
|
+
# #
|
238
|
+
# # if @config.middleware_validation_enabled
|
239
|
+
# # @middleware_stack << Middleware::Validation.new(@config)
|
240
|
+
# # end
|
241
|
+
# #
|
242
|
+
# # if @config.middleware_logging_enabled
|
243
|
+
# # @middleware_stack << Middleware::Logging.new(@config)
|
244
|
+
# # end
|
245
|
+
# # end
|
246
|
+
# #
|
247
|
+
# # def establish_connections
|
248
|
+
# # return unless nats_available?
|
249
|
+
# #
|
250
|
+
# # establish_nats_connection
|
251
|
+
# #
|
252
|
+
# # if @config.publishing_enabled
|
253
|
+
# # @publisher = Publisher.new(@config, @client, @middleware_stack)
|
254
|
+
# # end
|
255
|
+
# #
|
256
|
+
# # if @config.subscription_enabled
|
257
|
+
# # @subscriber = Subscriber.new(@config, @client, @middleware_stack)
|
258
|
+
# # end
|
259
|
+
# # end
|
260
|
+
# #
|
261
|
+
# # def establish_nats_connection
|
262
|
+
# # @client = NATS.connect(
|
263
|
+
# # @config.nats_url,
|
264
|
+
# # reconnect_time_wait: @config.retry_delay,
|
265
|
+
# # max_reconnect_attempts: @config.reconnect_attempts
|
266
|
+
# # )
|
267
|
+
# # NatsWave.logger.info("Connected to NATS at #{@config.nats_url}")
|
268
|
+
# # rescue => e
|
269
|
+
# # NatsWave.logger.error("Failed to connect to NATS: #{e.message}")
|
270
|
+
# # raise ConnectionError, "Failed to connect to NATS: #{e.message}"
|
271
|
+
# # end
|
272
|
+
# #
|
273
|
+
# # def ensure_connected!
|
274
|
+
# # raise ConnectionError, "Not connected to NATS" unless connected?
|
275
|
+
# # end
|
276
|
+
# #
|
277
|
+
# # def database_connected?
|
278
|
+
# # return false unless @subscriber
|
279
|
+
# # @subscriber.database_connected?
|
280
|
+
# # end
|
281
|
+
# #
|
282
|
+
# # def nats_available?
|
283
|
+
# # defined?(NATS)
|
284
|
+
# # rescue LoadError
|
285
|
+
# # false
|
286
|
+
# # end
|
287
|
+
# # end
|
288
|
+
# # end
|
289
|
+
#
|
290
|
+
# # frozen_string_literal: true
|
291
|
+
#
|
292
|
+
# module NatsWave
|
293
|
+
# class Client
|
294
|
+
# attr_reader :config, :publisher, :subscriber, :client
|
295
|
+
#
|
296
|
+
# def initialize(options = {})
|
297
|
+
# @config = Configuration.new(options)
|
298
|
+
# @client = nil
|
299
|
+
# @publisher = nil
|
300
|
+
# @subscriber = nil
|
301
|
+
# @middleware_stack = []
|
302
|
+
# @shutdown = false
|
303
|
+
#
|
304
|
+
# setup_connection_pool
|
305
|
+
# setup_middleware
|
306
|
+
# establish_connections
|
307
|
+
# end
|
308
|
+
#
|
309
|
+
# def publish(subject:, model:, action:, data:, metadata: {})
|
310
|
+
# ensure_connected!
|
311
|
+
# @publisher.publish(
|
312
|
+
# subject: subject,
|
313
|
+
# model: model,
|
314
|
+
# action: action,
|
315
|
+
# data: data,
|
316
|
+
# metadata: metadata
|
317
|
+
# )
|
318
|
+
# end
|
319
|
+
#
|
320
|
+
# def publish_batch(events)
|
321
|
+
# ensure_connected!
|
322
|
+
# @publisher.publish_batch(events)
|
323
|
+
# end
|
324
|
+
#
|
325
|
+
# def subscribe(subjects:, model_mappings: {}, &block)
|
326
|
+
# NatsWave.logger.info "Ensuring connection..."
|
327
|
+
# ensure_connected!
|
328
|
+
# raise "Subscriber not initialized" unless @subscriber
|
329
|
+
#
|
330
|
+
# NatsWave.logger.info "Subscribing to subjects: #{subjects.inspect}"
|
331
|
+
# NatsWave.logger.info "Subscribing with model mapping: #{model_mappings.inspect}"
|
332
|
+
# @subscriber.listen(
|
333
|
+
# subjects: subjects,
|
334
|
+
# model_mappings: model_mappings,
|
335
|
+
# &block
|
336
|
+
# )
|
337
|
+
# end
|
338
|
+
#
|
339
|
+
# def start_subscriber
|
340
|
+
# ensure_connected!
|
341
|
+
# @subscriber.begin if @subscriber
|
342
|
+
# end
|
343
|
+
#
|
344
|
+
# # ✅ ADD THIS METHOD
|
345
|
+
# def connected?
|
346
|
+
# @client && @client.respond_to?(:connected?) && @client.connected?
|
347
|
+
# end
|
348
|
+
#
|
349
|
+
# def health_check
|
350
|
+
# {
|
351
|
+
# nats_connected: connected?,
|
352
|
+
# database_connected: database_connected?,
|
353
|
+
# nats_url: @config.nats_url,
|
354
|
+
# nats_server_url: @config.nats_server_url,
|
355
|
+
# service_name: @config.service_name,
|
356
|
+
# version: @config.version,
|
357
|
+
# instance_id: @config.instance_id,
|
358
|
+
# published_subjects: @config.subject_patterns,
|
359
|
+
# timestamp: Time.current.iso8601,
|
360
|
+
# model_registry_stats: NatsWave::ModelRegistry.subscription_stats
|
361
|
+
# }
|
362
|
+
# end
|
363
|
+
#
|
364
|
+
# def disconnect!
|
365
|
+
# @shutdown = true
|
366
|
+
#
|
367
|
+
# NatsWave.logger.info "🛑 Shutting down NatsWave client..."
|
368
|
+
#
|
369
|
+
# @subscriber&.reset
|
370
|
+
#
|
371
|
+
# if @client&.respond_to?(:close)
|
372
|
+
# @client.close
|
373
|
+
# elsif @client&.respond_to?(:disconnect)
|
374
|
+
# @client.disconnect
|
375
|
+
# end
|
376
|
+
#
|
377
|
+
# @connection_pool&.shutdown
|
378
|
+
# @connection_pool&.wait_for_termination(5)
|
379
|
+
#
|
380
|
+
# NatsWave.logger.info "✅ NatsWave client shutdown complete"
|
381
|
+
# end
|
382
|
+
#
|
383
|
+
# private
|
384
|
+
#
|
385
|
+
# def setup_connection_pool
|
386
|
+
# return unless defined?(Concurrent)
|
387
|
+
#
|
388
|
+
# @connection_pool = Concurrent::ThreadPoolExecutor.new(
|
389
|
+
# min_threads: 5,
|
390
|
+
# max_threads: @config.connection_pool_size
|
391
|
+
# )
|
392
|
+
# end
|
393
|
+
#
|
394
|
+
# def setup_middleware
|
395
|
+
# @middleware_stack = []
|
396
|
+
#
|
397
|
+
# if @config.middleware_authentication_enabled
|
398
|
+
# @middleware_stack << Middleware::Authentication.new(@config)
|
399
|
+
# end
|
400
|
+
#
|
401
|
+
# if @config.middleware_validation_enabled
|
402
|
+
# @middleware_stack << Middleware::Validation.new(@config)
|
403
|
+
# end
|
404
|
+
#
|
405
|
+
# if @config.middleware_logging_enabled
|
406
|
+
# @middleware_stack << Middleware::Logging.new(@config)
|
407
|
+
# end
|
408
|
+
# end
|
409
|
+
#
|
410
|
+
# def establish_connections
|
411
|
+
# return unless nats_available?
|
412
|
+
#
|
413
|
+
# establish_nats_connection
|
414
|
+
#
|
415
|
+
# if @config.publishing_enabled
|
416
|
+
# @publisher = Publisher.new(@config, @client, @middleware_stack)
|
417
|
+
# end
|
418
|
+
#
|
419
|
+
# if @config.subscription_enabled
|
420
|
+
# @subscriber = Subscriber.new(@config, @client, @middleware_stack)
|
421
|
+
# end
|
422
|
+
# end
|
423
|
+
#
|
424
|
+
# def establish_nats_connection
|
425
|
+
# @client = NATS.connect(
|
426
|
+
# @config.nats_url,
|
427
|
+
# reconnect_time_wait: @config.retry_delay,
|
428
|
+
# max_reconnect_attempts: @config.reconnect_attempts
|
429
|
+
# )
|
430
|
+
# NatsWave.logger.info("Connected to NATS at #{@config.nats_url}")
|
431
|
+
# rescue => e
|
432
|
+
# NatsWave.logger.error("Failed to connect to NATS: #{e.message}")
|
433
|
+
# raise ConnectionError, "Failed to connect to NATS: #{e.message}"
|
434
|
+
# end
|
435
|
+
#
|
436
|
+
# # ✅ UPDATE THIS METHOD
|
437
|
+
# def ensure_connected!
|
438
|
+
# unless connected?
|
439
|
+
# raise ConnectionError, "Not connected to NATS. NATS client: #{@client.class rescue 'nil'}"
|
440
|
+
# end
|
441
|
+
# end
|
442
|
+
#
|
443
|
+
# def database_connected?
|
444
|
+
# return false unless @subscriber
|
445
|
+
# @subscriber.database_connected?
|
446
|
+
# end
|
447
|
+
#
|
448
|
+
# def nats_available?
|
449
|
+
# defined?(NATS)
|
450
|
+
# rescue LoadError
|
451
|
+
# false
|
452
|
+
# end
|
453
|
+
# end
|
454
|
+
# end
|
455
|
+
|
1
456
|
# frozen_string_literal: true
|
2
457
|
|
458
|
+
begin
|
459
|
+
require 'nats/client'
|
460
|
+
rescue LoadError => e
|
461
|
+
# NATS gem not available
|
462
|
+
puts "Warning: NATS gem not available: #{e.message}"
|
463
|
+
end
|
464
|
+
|
3
465
|
module NatsWave
|
4
466
|
class Client
|
5
|
-
attr_reader :config, :publisher, :subscriber, :
|
467
|
+
attr_reader :config, :publisher, :subscriber, :client
|
6
468
|
|
7
469
|
def initialize(options = {})
|
8
470
|
@config = Configuration.new(options)
|
9
|
-
@
|
471
|
+
@client = nil
|
10
472
|
@publisher = nil
|
11
473
|
@subscriber = nil
|
12
474
|
@middleware_stack = []
|
475
|
+
@shutdown = false
|
13
476
|
|
14
477
|
setup_connection_pool
|
15
478
|
setup_middleware
|
@@ -33,17 +496,26 @@ module NatsWave
|
|
33
496
|
end
|
34
497
|
|
35
498
|
def subscribe(subjects:, model_mappings: {}, &block)
|
499
|
+
NatsWave.logger.info "Ensuring connection..."
|
36
500
|
ensure_connected!
|
37
|
-
@subscriber
|
501
|
+
raise "Subscriber not initialized" unless @subscriber
|
502
|
+
|
503
|
+
NatsWave.logger.info "Subscribing to subjects: #{subjects.inspect}"
|
504
|
+
NatsWave.logger.info "Subscribing with model mapping: #{model_mappings.inspect}"
|
505
|
+
@subscriber.listen(
|
38
506
|
subjects: subjects,
|
39
507
|
model_mappings: model_mappings,
|
40
|
-
|
508
|
+
&block
|
41
509
|
)
|
42
510
|
end
|
43
511
|
|
44
512
|
def start_subscriber
|
45
513
|
ensure_connected!
|
46
|
-
@subscriber.
|
514
|
+
@subscriber.begin if @subscriber
|
515
|
+
end
|
516
|
+
|
517
|
+
def connected?
|
518
|
+
@client && @client.respond_to?(:connected?) && @client.connected?
|
47
519
|
end
|
48
520
|
|
49
521
|
def health_check
|
@@ -51,24 +523,31 @@ module NatsWave
|
|
51
523
|
nats_connected: connected?,
|
52
524
|
database_connected: database_connected?,
|
53
525
|
nats_url: @config.nats_url,
|
54
|
-
nats_server_url: @config.nats_server_url,
|
55
526
|
service_name: @config.service_name,
|
56
527
|
version: @config.version,
|
57
528
|
instance_id: @config.instance_id,
|
58
|
-
published_subjects: @config.subject_patterns,
|
59
529
|
timestamp: Time.current.iso8601,
|
530
|
+
model_registry_stats: NatsWave::ModelRegistry.subscription_stats
|
60
531
|
}
|
61
532
|
end
|
62
533
|
|
63
|
-
def connected?
|
64
|
-
@nats_client && @nats_client.connected?
|
65
|
-
end
|
66
|
-
|
67
534
|
def disconnect!
|
68
|
-
@
|
69
|
-
|
535
|
+
@shutdown = true
|
536
|
+
|
537
|
+
NatsWave.logger.info "🛑 Shutting down NatsWave client..."
|
538
|
+
|
539
|
+
@subscriber&.reset
|
540
|
+
|
541
|
+
if @client&.respond_to?(:close)
|
542
|
+
@client.close
|
543
|
+
elsif @client&.respond_to?(:disconnect)
|
544
|
+
@client.disconnect
|
545
|
+
end
|
546
|
+
|
70
547
|
@connection_pool&.shutdown
|
71
548
|
@connection_pool&.wait_for_termination(5)
|
549
|
+
|
550
|
+
NatsWave.logger.info "✅ NatsWave client shutdown complete"
|
72
551
|
end
|
73
552
|
|
74
553
|
private
|
@@ -77,7 +556,7 @@ module NatsWave
|
|
77
556
|
return unless defined?(Concurrent)
|
78
557
|
|
79
558
|
@connection_pool = Concurrent::ThreadPoolExecutor.new(
|
80
|
-
min_threads:
|
559
|
+
min_threads: 5,
|
81
560
|
max_threads: @config.connection_pool_size
|
82
561
|
)
|
83
562
|
end
|
@@ -99,33 +578,134 @@ module NatsWave
|
|
99
578
|
end
|
100
579
|
|
101
580
|
def establish_connections
|
102
|
-
|
581
|
+
unless nats_available?
|
582
|
+
NatsWave.logger.warn "NATS gem not available, skipping connection"
|
583
|
+
return
|
584
|
+
end
|
585
|
+
|
586
|
+
unless test_nats_connectivity
|
587
|
+
NatsWave.logger.error "Cannot reach NATS server, skipping connection"
|
588
|
+
return
|
589
|
+
end
|
103
590
|
|
591
|
+
# Always try to establish NATS connection
|
104
592
|
establish_nats_connection
|
105
593
|
|
106
|
-
|
107
|
-
|
594
|
+
# Verify connection was successful before creating publisher/subscriber
|
595
|
+
unless connected?
|
596
|
+
NatsWave.logger.error "NATS connection failed, cannot initialize publisher/subscriber"
|
597
|
+
return
|
598
|
+
end
|
599
|
+
|
600
|
+
# Only create publisher/subscriber if we have a valid connection
|
601
|
+
if @config.publishing_enabled && connected?
|
602
|
+
@publisher = Publisher.new(@config, @client, @middleware_stack)
|
603
|
+
NatsWave.logger.debug "Publisher initialized"
|
108
604
|
end
|
109
605
|
|
110
|
-
if @config.subscription_enabled
|
111
|
-
@subscriber = Subscriber.new(@config, @
|
606
|
+
if @config.subscription_enabled && connected?
|
607
|
+
@subscriber = Subscriber.new(@config, @client, @middleware_stack)
|
608
|
+
NatsWave.logger.debug "Subscriber initialized"
|
112
609
|
end
|
113
610
|
end
|
114
611
|
|
612
|
+
# def establish_nats_connection
|
613
|
+
# NatsWave.logger.info "Attempting to connect to NATS at #{@config.nats_url}"
|
614
|
+
#
|
615
|
+
# @client = NATS.connect(
|
616
|
+
# @config.nats_url,
|
617
|
+
# reconnect_time_wait: @config.retry_delay,
|
618
|
+
# max_reconnect_attempts: @config.reconnect_attempts
|
619
|
+
# )
|
620
|
+
#
|
621
|
+
# # Verify the connection actually worked
|
622
|
+
# if @client&.connected?
|
623
|
+
# NatsWave.logger.info "✅ Successfully connected to NATS at #{@config.nats_url}"
|
624
|
+
# else
|
625
|
+
# NatsWave.logger.error "❌ NATS client created but not connected"
|
626
|
+
# @client = nil
|
627
|
+
# end
|
628
|
+
#
|
629
|
+
# rescue => e
|
630
|
+
# NatsWave.logger.error "❌ Failed to connect to NATS: #{e.message}"
|
631
|
+
# NatsWave.logger.error "NATS URL: #{@config.nats_url}"
|
632
|
+
# NatsWave.logger.error "Error class: #{e.class}"
|
633
|
+
#
|
634
|
+
# @client = nil
|
635
|
+
#
|
636
|
+
# # Re-raise in development to catch issues early
|
637
|
+
# if defined?(Rails) && Rails.env.development?
|
638
|
+
# raise ConnectionError, "Failed to connect to NATS: #{e.message}"
|
639
|
+
# end
|
640
|
+
# end
|
641
|
+
|
115
642
|
def establish_nats_connection
|
116
|
-
|
117
|
-
|
643
|
+
NatsWave.logger.info "Attempting to connect to NATS at #{@config.nats_url}"
|
644
|
+
|
645
|
+
# Add more detailed connection options for debugging
|
646
|
+
connection_options = {
|
118
647
|
reconnect_time_wait: @config.retry_delay,
|
119
|
-
max_reconnect_attempts: @config.reconnect_attempts
|
120
|
-
|
121
|
-
|
648
|
+
max_reconnect_attempts: @config.reconnect_attempts,
|
649
|
+
dont_randomize_servers: true,
|
650
|
+
verbose: true,
|
651
|
+
pedantic: false
|
652
|
+
}
|
653
|
+
|
654
|
+
NatsWave.logger.info "Connection options: #{connection_options}"
|
655
|
+
|
656
|
+
@client = NATS.connect(@config.nats_url, connection_options)
|
657
|
+
|
658
|
+
# Wait a moment for connection to establish
|
659
|
+
sleep 0.5
|
660
|
+
|
661
|
+
# Check connection status with more detail
|
662
|
+
if @client
|
663
|
+
NatsWave.logger.info "NATS client created: #{@client.class}"
|
664
|
+
NatsWave.logger.info "NATS client connected?: #{@client.connected?}"
|
665
|
+
NatsWave.logger.info "NATS client status: #{@client.status rescue 'unknown'}"
|
666
|
+
|
667
|
+
if @client.connected?
|
668
|
+
NatsWave.logger.info "✅ Successfully connected to NATS at #{@config.nats_url}"
|
669
|
+
else
|
670
|
+
NatsWave.logger.error "❌ NATS client created but not connected"
|
671
|
+
NatsWave.logger.error "Client last error: #{@client.last_error rescue 'none'}"
|
672
|
+
@client = nil
|
673
|
+
end
|
674
|
+
else
|
675
|
+
NatsWave.logger.error "❌ NATS client is nil after connection attempt"
|
676
|
+
end
|
677
|
+
|
122
678
|
rescue => e
|
123
|
-
NatsWave.logger.error
|
124
|
-
|
679
|
+
NatsWave.logger.error "❌ Failed to connect to NATS: #{e.message}"
|
680
|
+
NatsWave.logger.error "Error class: #{e.class}"
|
681
|
+
NatsWave.logger.error "Error backtrace: #{e.backtrace.first(5).join('\n')}"
|
682
|
+
NatsWave.logger.error "NATS URL: #{@config.nats_url}"
|
683
|
+
NatsWave.logger.error "Retry delay: #{@config.retry_delay}"
|
684
|
+
NatsWave.logger.error "Max reconnect attempts: #{@config.reconnect_attempts}"
|
685
|
+
|
686
|
+
@client = nil
|
687
|
+
|
688
|
+
# Re-raise in development to catch issues early
|
689
|
+
if defined?(Rails) && Rails.env.development?
|
690
|
+
raise ConnectionError, "Failed to connect to NATS: #{e.message}"
|
691
|
+
end
|
125
692
|
end
|
126
693
|
|
127
694
|
def ensure_connected!
|
128
|
-
|
695
|
+
unless connected?
|
696
|
+
error_msg = if @client.nil?
|
697
|
+
"NATS client is nil - connection failed during initialization"
|
698
|
+
else
|
699
|
+
"NATS client exists but not connected (#{@client.class})"
|
700
|
+
end
|
701
|
+
|
702
|
+
NatsWave.logger.error "🔴 Connection check failed: #{error_msg}"
|
703
|
+
NatsWave.logger.error "🔧 NATS URL: #{@config.nats_url}"
|
704
|
+
NatsWave.logger.error "🔧 Publishing enabled: #{@config.publishing_enabled}"
|
705
|
+
NatsWave.logger.error "🔧 Subscription enabled: #{@config.subscription_enabled}"
|
706
|
+
|
707
|
+
raise ConnectionError, error_msg
|
708
|
+
end
|
129
709
|
end
|
130
710
|
|
131
711
|
def database_connected?
|
@@ -134,9 +714,38 @@ module NatsWave
|
|
134
714
|
end
|
135
715
|
|
136
716
|
def nats_available?
|
717
|
+
# First, try to require the NATS gem if it's not already loaded
|
718
|
+
begin
|
719
|
+
require 'nats/client' unless defined?(NATS)
|
720
|
+
rescue LoadError
|
721
|
+
NatsWave.logger.error "❌ NATS gem not found. Add 'gem \"nats\"' to your Gemfile"
|
722
|
+
return false
|
723
|
+
end
|
724
|
+
|
725
|
+
# Then check if the NATS constant is defined
|
137
726
|
defined?(NATS)
|
138
|
-
|
139
|
-
|
727
|
+
end
|
728
|
+
|
729
|
+
def test_nats_connectivity
|
730
|
+
require 'socket'
|
731
|
+
require 'uri'
|
732
|
+
|
733
|
+
begin
|
734
|
+
uri = URI.parse(@config.nats_url)
|
735
|
+
host = uri.host || 'localhost'
|
736
|
+
port = uri.port || 4222
|
737
|
+
|
738
|
+
NatsWave.logger.info "Testing TCP connectivity to #{host}:#{port}"
|
739
|
+
|
740
|
+
socket = TCPSocket.new(host, port)
|
741
|
+
socket.close
|
742
|
+
|
743
|
+
NatsWave.logger.info "✅ TCP connection to NATS server successful"
|
744
|
+
true
|
745
|
+
rescue => e
|
746
|
+
NatsWave.logger.error "❌ Cannot reach NATS server: #{e.message}"
|
747
|
+
false
|
748
|
+
end
|
140
749
|
end
|
141
750
|
end
|
142
751
|
end
|