jetstream_bridge 2.7.0 → 2.8.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc39ce8a4d26a2488bcd9bb756a6380d3a5336e2048c2bb5b18082b4e7930e5d
4
- data.tar.gz: f9c0298070c6a25771d8bf9848582bf256d1b7890e7d4944ad3653eead18a96f
3
+ metadata.gz: eeebee439d30bce4eac88df3c7cbc09c2e0af258a071bbd053c0fd10f244c995
4
+ data.tar.gz: de810b9580efcc26b8cb44810994f64e7ae68548c2bf59b01f8a676db8e6d131
5
5
  SHA512:
6
- metadata.gz: c1b927989ccca2615e3e5229ac5679f9e4bfe76ec92d978cafe6cb1c4dcae40ccbecf4d88699020e9c4cebb695a6de7efedd8bfef259053c901db336181ac294
7
- data.tar.gz: 51caa80c8790f79ea2808ab0f85de484b6b17626a08eaf06eb674340a4cead39d8016d2f971b97508e1be45e661490b555360378fcadf7b5c3f407bf30c4453f
6
+ metadata.gz: 0c28944d07ee1399425fb194f4fe58e2ae6006c34f722091f2b88fb959f1012b83008c26d397385eff1d61a2a5df54f85975208ec882bb63f28ef51f77d90f02
7
+ data.tar.gz: 7fdefcc529051eadcccc4d536adc71b7da61c3928adf17ccc7c76c73d9b9c7735bd30626e3a779264a40cd2d5e36044de08075124c16b68bcff01ae302c276c8
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
3
+ require 'oj'
4
4
  require 'securerandom'
5
5
  require_relative '../core/connection'
6
6
  require_relative '../core/duration'
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
3
+ require 'oj'
4
4
  require 'time'
5
5
  require_relative '../core/logging'
6
6
 
@@ -46,7 +46,7 @@ module JetstreamBridge
46
46
  headers['x-dead-letter'] = 'true'
47
47
  headers['x-dlq-reason'] = reason
48
48
  headers['x-deliveries'] = deliveries.to_s
49
- headers['x-dlq-context'] = JSON.generate(envelope)
49
+ headers['x-dlq-context'] = Oj.dump(envelope, mode: :compat)
50
50
  headers
51
51
  end
52
52
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
3
+ require 'oj'
4
4
 
5
5
  module JetstreamBridge
6
6
  # Immutable value object for a single NATS message.
@@ -19,8 +19,8 @@ module JetstreamBridge
19
19
 
20
20
  raw = msg.data
21
21
  body = begin
22
- JSON.parse(raw)
23
- rescue StandardError
22
+ Oj.load(raw, mode: :strict)
23
+ rescue Oj::Error
24
24
  {}
25
25
  end
26
26
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
3
+ require 'oj'
4
4
  require_relative '../core/logging'
5
5
  require_relative 'message_context'
6
6
  require_relative 'dlq_publisher'
@@ -37,12 +37,8 @@ module JetstreamBridge
37
37
 
38
38
  def parse_message(msg, ctx)
39
39
  data = msg.data
40
- if defined?(Oj)
41
- Oj.load(data, mode: :strict)
42
- else
43
- JSON.parse(data)
44
- end
45
- rescue JSON::ParserError => e
40
+ Oj.load(data, mode: :strict)
41
+ rescue Oj::ParseError => e
46
42
  @dlq.publish(msg, ctx,
47
43
  reason: 'malformed_json', error_class: e.class.name, error_message: e.message)
48
44
  msg.ack
@@ -11,6 +11,8 @@ module JetstreamBridge
11
11
  @jts = jts
12
12
  @durable = durable
13
13
  @cfg = cfg
14
+ @desired_cfg = ConsumerConfig.consumer_config(@durable, filter_subject)
15
+ @desired_cfg_norm = normalize_consumer_config(@desired_cfg)
14
16
  end
15
17
 
16
18
  def stream_name
@@ -22,18 +24,18 @@ module JetstreamBridge
22
24
  end
23
25
 
24
26
  def desired_consumer_cfg
25
- ConsumerConfig.consumer_config(@durable, filter_subject)
27
+ @desired_cfg
26
28
  end
27
29
 
28
30
  def ensure_consumer!
29
31
  info = consumer_info_or_nil
30
32
  return create_consumer! unless info
31
33
 
32
- want = desired_consumer_cfg
33
- if consumer_matches?(info, want)
34
+ have_norm = normalize_consumer_config(info.config)
35
+ if have_norm == @desired_cfg_norm
34
36
  log_consumer_ok
35
37
  else
36
- log_consumer_diff(info, want)
38
+ log_consumer_diff(have_norm)
37
39
  recreate_consumer!
38
40
  end
39
41
  end
@@ -58,15 +60,8 @@ module JetstreamBridge
58
60
 
59
61
  # ---- comparison ----
60
62
 
61
- def consumer_matches?(info, want)
62
- have_norm = normalize_consumer_config(info.config)
63
- want_norm = normalize_consumer_config(want)
64
- have_norm == want_norm
65
- end
66
-
67
- def log_consumer_diff(info, want)
68
- have_norm = normalize_consumer_config(info.config)
69
- want_norm = normalize_consumer_config(want)
63
+ def log_consumer_diff(have_norm)
64
+ want_norm = @desired_cfg_norm
70
65
 
71
66
  diffs = {}
72
67
  (have_norm.keys | want_norm.keys).each do |k|
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'nats/io/client'
4
4
  require 'singleton'
5
- require 'json'
5
+ require 'oj'
6
6
  require_relative 'duration'
7
7
  require_relative 'logging'
8
8
  require_relative 'config'
@@ -14,6 +14,7 @@ module JetstreamBridge
14
14
  # Examples:
15
15
  # Duration.to_millis(30) #=> 30000 (auto)
16
16
  # Duration.to_millis(1500) #=> 1500 (auto)
17
+ # Duration.to_millis("1500") #=> 1500 (auto)
17
18
  # Duration.to_millis(1500, default_unit: :s) #=> 1_500_000
18
19
  # Duration.to_millis("30s") #=> 30000
19
20
  # Duration.to_millis("500ms") #=> 500
@@ -48,7 +49,7 @@ module JetstreamBridge
48
49
  case val
49
50
  when Integer then int_to_ms(val, default_unit: default_unit)
50
51
  when Float then float_to_ms(val, default_unit: default_unit)
51
- when String then string_to_ms(val, default_unit: default_unit == :auto ? :s : default_unit)
52
+ when String then string_to_ms(val, default_unit: default_unit)
52
53
  else
53
54
  raise ArgumentError, "invalid duration type: #{val.class}" unless val.respond_to?(:to_f)
54
55
 
@@ -59,7 +60,10 @@ module JetstreamBridge
59
60
 
60
61
  # Normalize an array of durations into integer milliseconds.
61
62
  def normalize_list_to_millis(values, default_unit: :auto)
62
- Array(values).map { |v| to_millis(v, default_unit: default_unit) }
63
+ vals = Array(values)
64
+ return [] if vals.empty?
65
+
66
+ vals.map { |v| to_millis(v, default_unit: default_unit) }
63
67
  end
64
68
 
65
69
  # --- internal helpers ---
@@ -70,7 +74,7 @@ module JetstreamBridge
70
74
  # Preserve existing heuristic for compatibility
71
75
  num >= 1_000 ? num : num * 1_000
72
76
  else
73
- coerce_numeric_to_ms(i.to_f, default_unit)
77
+ coerce_numeric_to_ms(num.to_f, default_unit)
74
78
  end
75
79
  end
76
80
 
@@ -80,8 +84,9 @@ module JetstreamBridge
80
84
 
81
85
  def string_to_ms(str, default_unit:)
82
86
  s = str.strip
83
- # Plain number string => use default_unit explicitly (not heuristic)
84
- return coerce_numeric_to_ms(s.delete('_').to_f, default_unit) if NUMBER_RE.match?(s)
87
+ # Plain number strings are treated like integers so the :auto
88
+ # heuristic still applies (<1000 => seconds, >=1000 => ms).
89
+ return int_to_ms(s.delete('_').to_i, default_unit: default_unit) if NUMBER_RE.match?(s)
85
90
 
86
91
  m = TOKEN_RE.match(s)
87
92
  raise ArgumentError, "invalid duration: #{str.inspect}" unless m
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'oj'
4
+
3
5
  module JetstreamBridge
4
6
  module ModelCodecSetup
5
7
  module_function
@@ -17,7 +19,7 @@ module JetstreamBridge
17
19
  next unless column?(klass, attr)
18
20
  next if json_column?(klass, attr) || already_serialized?(klass, attr)
19
21
 
20
- klass.serialize attr.to_sym, coder: JSON
22
+ klass.serialize attr.to_sym, coder: Oj
21
23
  end
22
24
  rescue ActiveRecord::StatementInvalid, ActiveRecord::ConnectionNotEstablished, ActiveRecord::NoDatabaseError
23
25
  # ignore when schema isn’t available yet
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'oj'
4
+
3
5
  module JetstreamBridge
4
6
  module ModelUtils
5
7
  module_function
@@ -36,15 +38,18 @@ module JetstreamBridge
36
38
  end
37
39
 
38
40
  def json_dump(obj)
39
- obj.is_a?(String) ? obj : JSON.generate(obj)
40
- rescue
41
+ return obj if obj.is_a?(String)
42
+
43
+ Oj.dump(obj, mode: :compat)
44
+ rescue Oj::Error, TypeError
41
45
  obj.to_s
42
46
  end
43
47
 
44
48
  def json_load(str)
45
49
  return str if str.is_a?(Hash)
46
- JSON.parse(str.to_s)
47
- rescue
50
+
51
+ Oj.load(str.to_s, mode: :strict)
52
+ rescue Oj::Error
48
53
  {}
49
54
  end
50
55
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'oj'
4
+
3
5
  begin
4
6
  require 'active_record'
5
7
  rescue LoadError
@@ -84,8 +86,8 @@ module JetstreamBridge
84
86
  v = self[:payload]
85
87
  case v
86
88
  when String then begin
87
- JSON.parse(v)
88
- rescue StandardError
89
+ Oj.load(v, mode: :strict)
90
+ rescue Oj::Error
89
91
  {}
90
92
  end
91
93
  when Hash then v
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
3
+ require 'oj'
4
4
 
5
5
  begin
6
6
  require 'active_record'
@@ -93,8 +93,8 @@ module JetstreamBridge
93
93
  v = self[:payload]
94
94
  case v
95
95
  when String then begin
96
- JSON.parse(v)
97
- rescue StandardError
96
+ Oj.load(v, mode: :strict)
97
+ rescue Oj::Error
98
98
  {}
99
99
  end
100
100
  when Hash then v
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
3
+ require 'oj'
4
4
  require 'securerandom'
5
5
  require_relative '../core/connection'
6
6
  require_relative '../core/logging'
@@ -50,12 +50,21 @@ module JetstreamBridge
50
50
  def do_publish?(subject, envelope)
51
51
  headers = { 'nats-msg-id' => envelope['event_id'] }
52
52
 
53
- @jts.publish(subject, JSON.generate(envelope), header: headers)
54
- Logging.info(
55
- "Published #{subject} event_id=#{envelope['event_id']}",
56
- tag: 'JetstreamBridge::Publisher'
57
- )
58
- true
53
+ ack = @jts.publish(subject, Oj.dump(envelope, mode: :compat), header: headers)
54
+ duplicate = ack.respond_to?(:duplicate?) && ack.duplicate?
55
+ msg = "Published #{subject} event_id=#{envelope['event_id']}"
56
+ msg += ' (duplicate)' if duplicate
57
+
58
+ Logging.info(msg, tag: 'JetstreamBridge::Publisher')
59
+
60
+ if ack.respond_to?(:error) && ack.error
61
+ Logging.error(
62
+ "Publish ack error: #{ack.error}",
63
+ tag: 'JetstreamBridge::Publisher'
64
+ )
65
+ end
66
+
67
+ !ack.respond_to?(:error) || ack.error.nil?
59
68
  end
60
69
 
61
70
  # ---- Outbox path ----
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
3
+ require 'oj'
4
4
  require_relative 'subject_matcher'
5
5
  require_relative '../core/logging'
6
6
 
@@ -68,8 +68,8 @@ module JetstreamBridge
68
68
 
69
69
  def js_api_request(jts, subject, payload = {})
70
70
  # JetStream client should expose the underlying NATS client as `nc`
71
- msg = jts.nc.request(subject, JSON.dump(payload))
72
- JSON.parse(msg.data)
71
+ msg = jts.nc.request(subject, Oj.dump(payload, mode: :compat))
72
+ Oj.load(msg.data, mode: :strict)
73
73
  end
74
74
 
75
75
  def conflict_message(target, conflicts)
@@ -4,5 +4,5 @@
4
4
  #
5
5
  # Version constant for the gem.
6
6
  module JetstreamBridge
7
- VERSION = '2.7.0'
7
+ VERSION = '2.8.0'
8
8
  end
@@ -46,9 +46,19 @@ module JetstreamBridge
46
46
  config.use_dlq
47
47
  end
48
48
 
49
- def ensure_topology?
49
+ # Establishes a connection and ensures stream topology.
50
+ #
51
+ # @return [Object] JetStream context
52
+ def ensure_topology!
50
53
  Connection.connect!
51
- true
54
+ Connection.jetstream
55
+ end
56
+
57
+ # @deprecated Use {ensure_topology!} instead. This method will be removed
58
+ # in a future version.
59
+ def ensure_topology?
60
+ Logging.warn('ensure_topology? is deprecated; use ensure_topology! instead', tag: 'JetstreamBridge')
61
+ !!ensure_topology!
52
62
  end
53
63
 
54
64
  private
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jetstream_bridge
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.0
4
+ version: 2.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Attara