nats_wave 1.1.5 → 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 +76 -19
- data/lib/nats_wave/adapters/datadog_metrics.rb +1 -1
- data/lib/nats_wave/client.rb +637 -28
- data/lib/nats_wave/concerns/mappable.rb +9 -3
- data/lib/nats_wave/configuration.rb +2 -2
- 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 +50 -47
- data/lib/nats_wave/subscriber.rb +118 -20
- data/lib/nats_wave/version.rb +1 -1
- metadata +2 -2
@@ -22,6 +22,8 @@ module NatsWave
|
|
22
22
|
|
23
23
|
# Configure how external models map to this model AND what subjects to subscribe to
|
24
24
|
def nats_wave_maps_from(external_model, options = {})
|
25
|
+
Rails.logger.debug "🔄 #{self.name}: Setting up mapping from #{external_model} with options: #{options.inspect}" if defined?(Rails)
|
26
|
+
|
25
27
|
mapping = {
|
26
28
|
field_mappings: options[:field_mappings] || {},
|
27
29
|
transformations: options[:transformations] || {},
|
@@ -29,7 +31,6 @@ module NatsWave
|
|
29
31
|
sync_strategy: options[:sync_strategy] || :upsert,
|
30
32
|
unique_fields: options[:unique_fields] || [:id],
|
31
33
|
skip_fields: options[:skip_fields] || [],
|
32
|
-
# NEW: Subscription configuration
|
33
34
|
subjects: options[:subjects] || [],
|
34
35
|
handler: options[:handler],
|
35
36
|
queue_group: options[:queue_group]
|
@@ -37,11 +38,12 @@ module NatsWave
|
|
37
38
|
|
38
39
|
self.nats_wave_mapping_config[external_model] = mapping
|
39
40
|
|
40
|
-
#
|
41
|
+
Rails.logger.debug "🔄 #{self.name}: Registering reverse mapping for #{external_model}" if defined?(Rails)
|
41
42
|
NatsWave::ModelRegistry.register_reverse_mapping(external_model, self.name, mapping)
|
42
43
|
|
43
44
|
# Register subscription if subjects are provided
|
44
45
|
if mapping[:subjects].any?
|
46
|
+
Rails.logger.debug "🔄 #{self.name}: Registering subscription for subjects: #{mapping[:subjects]}" if defined?(Rails)
|
45
47
|
NatsWave::ModelRegistry.register_subscription(
|
46
48
|
subjects: mapping[:subjects],
|
47
49
|
model: self.name,
|
@@ -49,6 +51,8 @@ module NatsWave
|
|
49
51
|
handler: mapping[:handler],
|
50
52
|
queue_group: mapping[:queue_group]
|
51
53
|
)
|
54
|
+
else
|
55
|
+
Rails.logger.debug "🔄 #{self.name}: No subjects provided, skipping subscription registration" if defined?(Rails)
|
52
56
|
end
|
53
57
|
end
|
54
58
|
|
@@ -56,6 +60,8 @@ module NatsWave
|
|
56
60
|
def nats_wave_subscribes_to(*subjects, handler: nil, queue_group: nil, &block)
|
57
61
|
handler ||= block
|
58
62
|
|
63
|
+
Rails.logger.debug "📡 #{self.name}: Setting up subscription to subjects: #{subjects.inspect}" if defined?(Rails)
|
64
|
+
|
59
65
|
subscription_config = {
|
60
66
|
subjects: subjects.flatten,
|
61
67
|
handler: handler,
|
@@ -65,7 +71,7 @@ module NatsWave
|
|
65
71
|
|
66
72
|
self.nats_wave_subscription_config[:custom] = subscription_config
|
67
73
|
|
68
|
-
#
|
74
|
+
Rails.logger.debug "📡 #{self.name}: Registering subscription in ModelRegistry" if defined?(Rails)
|
69
75
|
NatsWave::ModelRegistry.register_subscription(
|
70
76
|
subjects: subjects.flatten,
|
71
77
|
model: self.name,
|
@@ -18,7 +18,7 @@ module NatsWave
|
|
18
18
|
def initialize(options = {})
|
19
19
|
@nats_url = ENV['NATS_URL'] || "nats://localhost:4222"
|
20
20
|
@service_name = ENV['NATS_SERVICE_NAME'] || "purplewave"
|
21
|
-
@version = ENV['NATS_SERVICE_VERSION'] || "1.1.
|
21
|
+
@version = ENV['NATS_SERVICE_VERSION'] || "1.1.7"
|
22
22
|
@instance_id = ENV['NATS_INSTANCE_ID'] || Socket.gethostname
|
23
23
|
@database_url = ENV['NATS_DATABASE_URL'] || nil
|
24
24
|
@connection_pool_size = (ENV['NATS_CONNECTION_POOL_SIZE'] || 10).to_i
|
@@ -71,7 +71,7 @@ module NatsWave
|
|
71
71
|
}
|
72
72
|
end
|
73
73
|
|
74
|
-
def add_publication(model_class, actions = [
|
74
|
+
def add_publication(model_class, actions = %i[create update destroy])
|
75
75
|
@publications << {
|
76
76
|
model: model_class,
|
77
77
|
actions: Array(actions)
|
@@ -58,7 +58,7 @@ module NatsWave
|
|
58
58
|
private
|
59
59
|
|
60
60
|
def setup_storage
|
61
|
-
# For production,
|
61
|
+
# For production, we'd want to use Redis, database, or file storage
|
62
62
|
# For now, use in-memory storage
|
63
63
|
InMemoryStorage.new
|
64
64
|
end
|
@@ -70,11 +70,11 @@ module NatsWave
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def retry_message(failed_message)
|
73
|
-
# This would need to be implemented based on
|
73
|
+
# This would need to be implemented based on wer retry strategy
|
74
74
|
# For now, just log that we would retry
|
75
75
|
NatsWave.logger.info("Retrying message: #{failed_message[:id]}")
|
76
76
|
|
77
|
-
# In a real implementation,
|
77
|
+
# In a real implementation, we would:
|
78
78
|
# 1. Re-parse the message
|
79
79
|
# 2. Re-apply middleware
|
80
80
|
# 3. Re-process the message
|
@@ -138,7 +138,7 @@ module NatsWave
|
|
138
138
|
end
|
139
139
|
|
140
140
|
def mark_permanent_failure(message)
|
141
|
-
# In a real implementation,
|
141
|
+
# In a real implementation, we might move this to a different storage
|
142
142
|
update(message)
|
143
143
|
end
|
144
144
|
end
|
@@ -9,11 +9,13 @@ module NatsWave
|
|
9
9
|
class << self
|
10
10
|
# Register a mapping from local model to external models
|
11
11
|
def register_mapping(local_model, external_mappings)
|
12
|
+
Rails.logger.debug "🗂️ Registering mapping: #{local_model} -> #{external_mappings.inspect}" if defined?(Rails)
|
12
13
|
@mappings[local_model] = external_mappings
|
13
14
|
end
|
14
15
|
|
15
16
|
# Register a reverse mapping from external model to local model
|
16
17
|
def register_reverse_mapping(external_model, local_model, mapping_config)
|
18
|
+
Rails.logger.debug "🔄 Registering reverse mapping: #{external_model} -> #{local_model} with config: #{mapping_config.inspect}" if defined?(Rails)
|
17
19
|
@reverse_mappings[external_model] ||= {}
|
18
20
|
@reverse_mappings[external_model][local_model] = mapping_config
|
19
21
|
end
|
@@ -34,22 +36,52 @@ module NatsWave
|
|
34
36
|
Rails.logger.debug "📡 Registered subscription: #{model} -> #{subjects.join(', ')}" if defined?(Rails)
|
35
37
|
end
|
36
38
|
|
37
|
-
#
|
39
|
+
# Debug method to show all registrations
|
40
|
+
def debug_registrations
|
41
|
+
puts "\n" + "="*80
|
42
|
+
puts "🔍 NATS WAVE DEBUG - ALL REGISTRATIONS"
|
43
|
+
puts "="*80
|
44
|
+
|
45
|
+
puts "\n📡 SUBSCRIPTIONS (#{@subscriptions.size}):"
|
46
|
+
@subscriptions.each_with_index do |sub, i|
|
47
|
+
puts " #{i+1}. Model: #{sub[:model]}"
|
48
|
+
puts " Subjects: #{sub[:subjects].join(', ')}"
|
49
|
+
puts " External Model: #{sub[:external_model] || 'N/A'}"
|
50
|
+
puts " Handler: #{sub[:handler] ? 'Yes' : 'No'}"
|
51
|
+
puts " Queue Group: #{sub[:queue_group] || 'Default'}"
|
52
|
+
puts " Registered: #{sub[:registered_at]}"
|
53
|
+
puts ""
|
54
|
+
end
|
55
|
+
|
56
|
+
puts "\n🗂️ MAPPINGS (#{@mappings.size}):"
|
57
|
+
@mappings.each do |local_model, external_mappings|
|
58
|
+
puts " #{local_model} -> #{external_mappings.inspect}"
|
59
|
+
end
|
60
|
+
|
61
|
+
puts "\n🔄 REVERSE MAPPINGS (#{@reverse_mappings.size}):"
|
62
|
+
@reverse_mappings.each do |external_model, local_mappings|
|
63
|
+
puts " #{external_model}:"
|
64
|
+
local_mappings.each do |local_model, config|
|
65
|
+
puts " -> #{local_model}: #{config.keys.join(', ')}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
puts "="*80 + "\n"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Rest of your existing methods...
|
38
73
|
def subscriptions
|
39
74
|
@subscriptions
|
40
75
|
end
|
41
76
|
|
42
|
-
# Get subscriptions for a specific model
|
43
77
|
def subscriptions_for_model(model_name)
|
44
78
|
@subscriptions.select { |sub| sub[:model] == model_name }
|
45
79
|
end
|
46
80
|
|
47
|
-
# Get all unique subjects to subscribe to
|
48
81
|
def all_subscription_subjects
|
49
82
|
@subscriptions.flat_map { |sub| sub[:subjects] }.uniq
|
50
83
|
end
|
51
84
|
|
52
|
-
# Get subscription handlers grouped by subject pattern
|
53
85
|
def subscription_handlers
|
54
86
|
handlers = {}
|
55
87
|
|
@@ -68,22 +100,18 @@ module NatsWave
|
|
68
100
|
handlers
|
69
101
|
end
|
70
102
|
|
71
|
-
# Get local model mappings for an external model
|
72
103
|
def local_models_for(external_model)
|
73
104
|
@reverse_mappings[external_model] || {}
|
74
105
|
end
|
75
106
|
|
76
|
-
# Get external model mappings for a local model
|
77
107
|
def external_models_for(local_model)
|
78
108
|
@mappings[local_model] || {}
|
79
109
|
end
|
80
110
|
|
81
|
-
# Check if an external model can be synced
|
82
111
|
def can_sync_external_model?(external_model)
|
83
112
|
@reverse_mappings.key?(external_model)
|
84
113
|
end
|
85
114
|
|
86
|
-
# Get all registered mappings
|
87
115
|
def all_mappings
|
88
116
|
{
|
89
117
|
local_to_external: @mappings,
|
@@ -91,15 +119,14 @@ module NatsWave
|
|
91
119
|
}
|
92
120
|
end
|
93
121
|
|
94
|
-
# Find the best local model for an external model
|
95
122
|
def best_local_model_for(external_model, data = {})
|
96
123
|
local_mappings = local_models_for(external_model)
|
97
124
|
return nil if local_mappings.empty?
|
98
125
|
|
99
|
-
|
100
|
-
|
126
|
+
if local_mappings.size == 1
|
127
|
+
return local_mappings.keys.first
|
128
|
+
end
|
101
129
|
|
102
|
-
# If multiple mappings, try to find the best match based on conditions
|
103
130
|
local_mappings.each do |local_model, config|
|
104
131
|
conditions = config[:conditions] || {}
|
105
132
|
|
@@ -108,18 +135,15 @@ module NatsWave
|
|
108
135
|
end
|
109
136
|
end
|
110
137
|
|
111
|
-
# Fallback to first available
|
112
138
|
local_mappings.keys.first
|
113
139
|
end
|
114
140
|
|
115
|
-
# Clear all mappings and subscriptions (useful for testing)
|
116
141
|
def clear!
|
117
142
|
@mappings.clear
|
118
143
|
@reverse_mappings.clear
|
119
144
|
@subscriptions.clear
|
120
145
|
end
|
121
146
|
|
122
|
-
# Get subscription statistics
|
123
147
|
def subscription_stats
|
124
148
|
{
|
125
149
|
total_subscriptions: @subscriptions.size,
|
data/lib/nats_wave/publisher.rb
CHANGED
@@ -1,52 +1,55 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'securerandom'
|
4
|
-
require 'json'
|
1
|
+
# # frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# require 'securerandom'
|
4
|
+
# require 'json'
|
5
|
+
#
|
6
|
+
# # begin
|
7
|
+
# # require 'nats/client'
|
8
|
+
# # rescue LoadError
|
9
|
+
# # # NATS not available - define a mock for testing
|
10
|
+
# # module NATS
|
11
|
+
# # def self.connect(url, options = {})
|
12
|
+
# # NatsClient.new
|
13
|
+
# # end
|
14
|
+
# #
|
15
|
+
# # class NatsClient
|
16
|
+
# # def connected?
|
17
|
+
# # false
|
18
|
+
# # end
|
19
|
+
# #
|
20
|
+
# # def publish(subject, message)
|
21
|
+
# # puts "NATS: Publishing to #{subject}: #{message}"
|
22
|
+
# # end
|
23
|
+
# #
|
24
|
+
# # def subscribe(subject, options = {})
|
25
|
+
# # puts "NATS: Subscribing to #{subject}"
|
26
|
+
# # yield('{"mock": "message"}') if block_given?
|
27
|
+
# # Subscription.new
|
28
|
+
# # end
|
29
|
+
# #
|
30
|
+
# # def close
|
31
|
+
# # true
|
32
|
+
# # end
|
33
|
+
# # end
|
34
|
+
# #
|
35
|
+
# # class Subscription
|
36
|
+
# # def unsubscribe
|
37
|
+
# # true
|
38
|
+
# # end
|
39
|
+
# # end
|
40
|
+
# # end
|
41
|
+
# # end
|
42
|
+
#
|
5
43
|
|
6
|
-
|
7
|
-
require 'nats/client'
|
8
|
-
rescue LoadError
|
9
|
-
# NATS not available - define a mock for testing
|
10
|
-
module NATS
|
11
|
-
def self.connect(url, options = {})
|
12
|
-
NatsClient.new
|
13
|
-
end
|
14
|
-
|
15
|
-
class NatsClient
|
16
|
-
def connected?
|
17
|
-
false
|
18
|
-
end
|
19
|
-
|
20
|
-
def publish(subject, message)
|
21
|
-
puts "NATS: Publishing to #{subject}: #{message}"
|
22
|
-
end
|
23
|
-
|
24
|
-
def subscribe(subject, options = {})
|
25
|
-
puts "NATS: Subscribing to #{subject}"
|
26
|
-
yield('{"mock": "message"}') if block_given?
|
27
|
-
Subscription.new
|
28
|
-
end
|
29
|
-
|
30
|
-
def close
|
31
|
-
true
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
class Subscription
|
36
|
-
def unsubscribe
|
37
|
-
true
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
44
|
+
# frozen_string_literal: true
|
42
45
|
|
43
46
|
module NatsWave
|
44
47
|
class Publisher
|
45
|
-
attr_reader :config, :
|
48
|
+
attr_reader :config, :client
|
46
49
|
|
47
|
-
def initialize(config,
|
50
|
+
def initialize(config, client, middleware_stack = [])
|
48
51
|
@config = config
|
49
|
-
@
|
52
|
+
@client = client
|
50
53
|
@message_transformer = MessageTransformer.new(config)
|
51
54
|
@dead_letter_queue = DeadLetterQueue.new(config) if config.dead_letter_queue
|
52
55
|
end
|
@@ -80,14 +83,14 @@ module NatsWave
|
|
80
83
|
|
81
84
|
subject = "#{@config.default_subject_prefix}.batch"
|
82
85
|
|
83
|
-
@
|
86
|
+
@client.publish(subject, batch_message.to_json)
|
84
87
|
|
85
88
|
# Metrics.increment_published_messages(subject)
|
86
89
|
NatsWave.logger.info("Published batch with #{events.size} events")
|
87
90
|
end
|
88
91
|
|
89
92
|
def connected?
|
90
|
-
@
|
93
|
+
@client&.connected?
|
91
94
|
end
|
92
95
|
|
93
96
|
def disconnect
|
@@ -125,14 +128,14 @@ module NatsWave
|
|
125
128
|
end
|
126
129
|
|
127
130
|
def publish_sync(subject, message)
|
128
|
-
@
|
131
|
+
@client.publish(subject, message.to_json)
|
129
132
|
NatsWave.logger.debug("Published sync message to #{subject}")
|
130
133
|
end
|
131
134
|
|
132
135
|
def publish_async(subject, message)
|
133
136
|
if defined?(Concurrent)
|
134
137
|
Concurrent::Future.execute do
|
135
|
-
@
|
138
|
+
@client.publish(subject, message.to_json)
|
136
139
|
NatsWave.logger.debug("Published async message to #{subject}")
|
137
140
|
end
|
138
141
|
else
|
data/lib/nats_wave/subscriber.rb
CHANGED
@@ -2,27 +2,29 @@
|
|
2
2
|
|
3
3
|
module NatsWave
|
4
4
|
class Subscriber
|
5
|
-
attr_reader :config, :
|
5
|
+
attr_reader :config, :client
|
6
6
|
|
7
|
-
def initialize(config,
|
7
|
+
def initialize(config, client, middleware_stack = [])
|
8
8
|
@config = config
|
9
|
-
@
|
9
|
+
@client = client
|
10
10
|
@database_connector = DatabaseConnector.new(config)
|
11
11
|
@model_mapper = ModelMapper.new(config)
|
12
12
|
@message_transformer = MessageTransformer.new(config)
|
13
13
|
@dead_letter_queue = DeadLetterQueue.new(config)
|
14
14
|
|
15
|
-
|
16
|
-
@
|
17
|
-
@nats_subscriptions = [] # NATS::Subscription objects
|
15
|
+
@registry_subscriptions = ModelRegistry.subscriptions
|
16
|
+
@nats_subscriptions = [] # NATS::Subscription objects
|
18
17
|
@running = false
|
18
|
+
@shutdown = false
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
22
|
-
return if @running
|
21
|
+
def begin
|
22
|
+
return if @running || @shutdown
|
23
23
|
return unless @config.subscription_enabled
|
24
24
|
|
25
25
|
@running = true
|
26
|
+
@shutdown = false
|
27
|
+
|
26
28
|
NatsWave.logger.info "Starting NATS subscriber for #{@config.service_name}"
|
27
29
|
|
28
30
|
# Use ModelRegistry subscriptions
|
@@ -32,10 +34,13 @@ module NatsWave
|
|
32
34
|
end
|
33
35
|
end
|
34
36
|
|
35
|
-
NatsWave.logger.info "Started #{@registry_subscriptions.size} subscriptions from
|
37
|
+
NatsWave.logger.info "Started #{@registry_subscriptions.size} subscriptions from Model Registry"
|
38
|
+
|
39
|
+
# Keep the subscriber alive
|
40
|
+
keep_alive
|
36
41
|
end
|
37
42
|
|
38
|
-
def
|
43
|
+
def listen(subjects:, model_mappings: {}, handler: nil)
|
39
44
|
subjects = Array(subjects)
|
40
45
|
|
41
46
|
subjects.each do |subject|
|
@@ -44,11 +49,27 @@ module NatsWave
|
|
44
49
|
end
|
45
50
|
end
|
46
51
|
|
47
|
-
def
|
48
|
-
|
49
|
-
@nats_subscriptions.each(&:unsubscribe)
|
50
|
-
@nats_subscriptions.clear
|
52
|
+
def reset
|
53
|
+
@shutdown = true
|
51
54
|
@running = false
|
55
|
+
|
56
|
+
# Stop keep alive thread
|
57
|
+
if @keep_alive_thread&.alive?
|
58
|
+
@keep_alive_thread.kill
|
59
|
+
@keep_alive_thread = nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# Unsubscribe from all subscriptions
|
63
|
+
@nats_subscriptions.each do |subscription|
|
64
|
+
begin
|
65
|
+
subscription.unsubscribe if subscription.respond_to?(:unsubscribe)
|
66
|
+
rescue => e
|
67
|
+
NatsWave.logger.error "Error unsubscribing: #{e.message}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
@nats_subscriptions.clear
|
71
|
+
|
72
|
+
NatsWave.logger.info "🛑 Subscriber shutdown complete"
|
52
73
|
end
|
53
74
|
|
54
75
|
def database_connected?
|
@@ -56,7 +77,7 @@ module NatsWave
|
|
56
77
|
end
|
57
78
|
|
58
79
|
def disconnect
|
59
|
-
|
80
|
+
reset
|
60
81
|
end
|
61
82
|
|
62
83
|
private
|
@@ -65,20 +86,30 @@ module NatsWave
|
|
65
86
|
NatsWave.logger.info "🔍 Attempting to subscribe to: #{subject_pattern}"
|
66
87
|
|
67
88
|
# Create the NATS subscription
|
68
|
-
nats_subscription = @
|
89
|
+
nats_subscription = @client.subscribe(
|
69
90
|
subject_pattern,
|
70
91
|
queue: @config.queue_group
|
71
92
|
) do |msg|
|
72
|
-
|
73
|
-
|
93
|
+
begin
|
94
|
+
NatsWave.logger.debug "📨 Received message on #{msg.subject}"
|
95
|
+
process_message(msg.data, custom_handler, model_mappings)
|
96
|
+
rescue => e
|
97
|
+
NatsWave.logger.error "Error in subscription handler: #{e.message}"
|
98
|
+
NatsWave.logger.error e.backtrace.join("\n")
|
99
|
+
# Don't re-raise - this would kill the subscription
|
100
|
+
end
|
74
101
|
end
|
75
102
|
|
76
|
-
# Add to NATS subscriptions array
|
103
|
+
# Add to NATS subscriptions array
|
77
104
|
@nats_subscriptions << nats_subscription
|
78
105
|
NatsWave.logger.info "✅ Successfully subscribed to #{subject_pattern} (total: #{@nats_subscriptions.size})"
|
79
106
|
end
|
80
107
|
|
81
108
|
def process_message(raw_message, custom_handler, model_mappings)
|
109
|
+
return unless should_process_message?(raw_message)
|
110
|
+
|
111
|
+
NatsWave.logger.debug "🔄 Processing message: #{raw_message[0..200]}..."
|
112
|
+
|
82
113
|
message = parse_message(raw_message)
|
83
114
|
|
84
115
|
if custom_handler
|
@@ -87,11 +118,78 @@ module NatsWave
|
|
87
118
|
handle_model_sync(message, model_mappings)
|
88
119
|
end
|
89
120
|
|
90
|
-
NatsWave.logger.debug('Successfully processed message')
|
121
|
+
NatsWave.logger.debug('✅ Successfully processed message')
|
91
122
|
rescue StandardError => e
|
92
123
|
handle_error(e, raw_message, message)
|
124
|
+
# Don't re-raise - this would kill the subscription
|
125
|
+
end
|
126
|
+
|
127
|
+
def should_process_message?(raw_message_data)
|
128
|
+
message = JSON.parse(raw_message_data)
|
129
|
+
source = message['source'] || {}
|
130
|
+
|
131
|
+
# Skip messages from same service instance
|
132
|
+
if source['service'] == @config.service_name && source['instance_id'] == @config.instance_id
|
133
|
+
NatsWave.logger.debug "🔄 Skipping message from same service instance"
|
134
|
+
return false
|
135
|
+
end
|
136
|
+
|
137
|
+
true
|
138
|
+
rescue JSON::ParserError => e
|
139
|
+
NatsWave.logger.error "Failed to parse message for filtering: #{e.message}"
|
140
|
+
false
|
141
|
+
end
|
142
|
+
|
143
|
+
def keep_alive
|
144
|
+
Rails.logger.info "🔄 Starting keep-alive thread for persistent JetStream connection"
|
145
|
+
|
146
|
+
@keep_alive_thread = Thread.new do
|
147
|
+
while @running && !@shutdown
|
148
|
+
begin
|
149
|
+
sleep 30 # Check every 30 seconds
|
150
|
+
|
151
|
+
if @client&.connected?
|
152
|
+
Rails.logger.debug "💓 Subscriber connection healthy - #{@nats_subscriptions.size} active subscriptions"
|
153
|
+
else
|
154
|
+
Rails.logger.error "❌ Subscriber connection lost! Attempting reconnection..."
|
155
|
+
attempt_reconnection
|
156
|
+
end
|
157
|
+
|
158
|
+
rescue => e
|
159
|
+
Rails.logger.error "Error in keep_alive thread: #{e.message}"
|
160
|
+
sleep 10
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
Rails.logger.info "🛑 Keep-alive thread shutting down"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def attempt_reconnection
|
169
|
+
return if @shutdown
|
170
|
+
|
171
|
+
NatsWave.logger.info "🔄 Attempting to reconnect to NATS..."
|
172
|
+
|
173
|
+
# Reset subscriptions
|
174
|
+
@nats_subscriptions.clear
|
175
|
+
|
176
|
+
# Try to reestablish subscriptions
|
177
|
+
sleep 5 # Wait before reconnecting
|
178
|
+
|
179
|
+
if @client&.connected?
|
180
|
+
NatsWave.logger.info "✅ NATS reconnected, reestablishing subscriptions"
|
181
|
+
|
182
|
+
@registry_subscriptions.each do |subscription|
|
183
|
+
subscription[:subjects].each do |subject|
|
184
|
+
subscribe_to_subject(subject, subscription[:handler])
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
rescue => e
|
189
|
+
NatsWave.logger.error "Failed to reconnect: #{e.message}"
|
93
190
|
end
|
94
191
|
|
192
|
+
# ... rest of your existing methods (parse_message, handle_model_sync, handle_error)
|
95
193
|
def parse_message(raw_message)
|
96
194
|
@message_transformer.parse_message(raw_message)
|
97
195
|
end
|
data/lib/nats_wave/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nats_wave
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeffrey Dabo
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-07-
|
11
|
+
date: 2025-07-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nats-pure
|