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.
@@ -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
- # Register this mapping globally
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
- # Register subscription
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,
@@ -16,9 +16,9 @@ module NatsWave
16
16
  :subscriptions, :publications
17
17
 
18
18
  def initialize(options = {})
19
- @nats_url = ENV['NATS_URL']
20
- @service_name = ENV['NATS_NATS_SERVICE_NAME']
21
- @version = ENV['NATS_SERVICE_VERSION'] || "1.1.4"
19
+ @nats_url = ENV['NATS_URL'] || "nats://localhost:4222"
20
+ @service_name = ENV['NATS_SERVICE_NAME'] || "purplewave"
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 = [:create, :update, :destroy])
74
+ def add_publication(model_class, actions = %i[create update destroy])
75
75
  @publications << {
76
76
  model: model_class,
77
77
  actions: Array(actions)
@@ -80,6 +80,7 @@ module NatsWave
80
80
 
81
81
  def nats_server_url
82
82
  # This is the URL other teams can use to subscribe to your events
83
+ return nil if @nats_url.nil?
83
84
  uri = URI.parse(@nats_url)
84
85
  "#{uri.scheme}://#{uri.host}:#{uri.port}"
85
86
  end
@@ -58,7 +58,7 @@ module NatsWave
58
58
  private
59
59
 
60
60
  def setup_storage
61
- # For production, you'd want to use Redis, database, or file storage
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 your retry strategy
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, you would:
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, you might move this to a different storage
141
+ # In a real implementation, we might move this to a different storage
142
142
  update(message)
143
143
  end
144
144
  end
@@ -122,4 +122,4 @@ module NatsWave
122
122
  end
123
123
  end
124
124
  end
125
- end
125
+ 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
- # Get all registered subscriptions
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
- # If only one mapping, use it
100
- return local_mappings.keys.first if local_mappings.size == 1
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,
@@ -1,53 +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
- 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
44
+ # frozen_string_literal: true
42
45
 
43
46
  module NatsWave
44
47
  class Publisher
45
- attr_reader :config, :nats_client
48
+ attr_reader :config, :client
46
49
 
47
- def initialize(config, nats_client, middleware_stack)
50
+ def initialize(config, client, middleware_stack = [])
48
51
  @config = config
49
- @nats_client = nats_client
50
- @middleware_stack = middleware_stack
52
+ @client = client
51
53
  @message_transformer = MessageTransformer.new(config)
52
54
  @dead_letter_queue = DeadLetterQueue.new(config) if config.dead_letter_queue
53
55
  end
@@ -56,17 +58,15 @@ module NatsWave
56
58
  return unless @config.publishing_enabled
57
59
 
58
60
  message = build_message(subject, model, action, data, metadata)
59
- processed_message = apply_middleware(message)
60
61
  full_subject = build_full_subject(subject)
61
62
 
62
63
  if @config.async_publishing && defined?(Concurrent)
63
- publish_async(full_subject, processed_message)
64
+ publish_async(full_subject, message)
64
65
  else
65
- publish_sync(full_subject, processed_message)
66
+ publish_sync(full_subject, message)
66
67
  end
67
68
 
68
- Metrics.increment_published_messages(full_subject)
69
- NatsWave.logger.debug("Published message to #{full_subject}")
69
+ # Metrics.increment_published_messages(full_subject)
70
70
  rescue => e
71
71
  NatsWave.logger.error("Failed to publish message: #{e.message}")
72
72
  @dead_letter_queue&.store_failed_message(message, e, 0)
@@ -81,17 +81,16 @@ module NatsWave
81
81
  source: build_source_info
82
82
  }
83
83
 
84
- processed_message = apply_middleware(batch_message)
85
84
  subject = "#{@config.default_subject_prefix}.batch"
86
85
 
87
- @nats_client.publish(subject, processed_message.to_json)
86
+ @client.publish(subject, batch_message.to_json)
88
87
 
89
- Metrics.increment_published_messages(subject)
88
+ # Metrics.increment_published_messages(subject)
90
89
  NatsWave.logger.info("Published batch with #{events.size} events")
91
90
  end
92
91
 
93
92
  def connected?
94
- @nats_client&.connected?
93
+ @client&.connected?
95
94
  end
96
95
 
97
96
  def disconnect
@@ -128,20 +127,16 @@ module NatsWave
128
127
  end
129
128
  end
130
129
 
131
- def apply_middleware(message)
132
- @middleware_stack.reduce(message) do |msg, middleware|
133
- middleware.call(msg)
134
- end
135
- end
136
-
137
130
  def publish_sync(subject, message)
138
- @nats_client.publish(subject, message.to_json)
131
+ @client.publish(subject, message.to_json)
132
+ NatsWave.logger.debug("Published sync message to #{subject}")
139
133
  end
140
134
 
141
135
  def publish_async(subject, message)
142
136
  if defined?(Concurrent)
143
137
  Concurrent::Future.execute do
144
- @nats_client.publish(subject, message.to_json)
138
+ @client.publish(subject, message.to_json)
139
+ NatsWave.logger.debug("Published async message to #{subject}")
145
140
  end
146
141
  else
147
142
  publish_sync(subject, message)
@@ -20,7 +20,9 @@ module NatsWave
20
20
  config_file = Rails.root.join('config', 'nats_wave.yml')
21
21
  if File.exist?(config_file)
22
22
  begin
23
- yaml_config = YAML.load_file(config_file)[Rails.env]
23
+ erb_content = ERB.new(File.read(config_file)).result
24
+ yaml_data = YAML.safe_load(erb_content)
25
+ yaml_config = yaml_data&.dig(Rails.env.to_s) || yaml_data&.dig(Rails.env) || {}
24
26
 
25
27
  NatsWave.configure do |config|
26
28
  # NATS Configuration
@@ -95,11 +97,10 @@ module NatsWave
95
97
  Rails.application.config.after_initialize do
96
98
  if NatsWave.configuration&.publishing_enabled || NatsWave.configuration&.subscription_enabled
97
99
  Thread.new do
98
- sleep 1 # Give Rails time to fully boot
100
+ sleep 2 # Give Rails time to fully boot
99
101
  begin
100
102
  NatsWave.client
101
103
  NatsWave.logger.info "NatsWave client initialized"
102
- NatsWave.logger.info "NATS URL #{@nats_url}"
103
104
  rescue => e
104
105
  NatsWave.logger.error "Failed to initialize NatsWave client: #{e.message}"
105
106
  end