jetstream_bridge 2.2.1 → 2.4.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 +4 -4
- data/lib/jetstream_bridge/consumer/backoff_strategy.rb +24 -0
- data/lib/jetstream_bridge/consumer/consumer.rb +16 -11
- data/lib/jetstream_bridge/consumer/consumer_config.rb +4 -10
- data/lib/jetstream_bridge/consumer/dlq_publisher.rb +53 -0
- data/lib/jetstream_bridge/consumer/message_context.rb +22 -0
- data/lib/jetstream_bridge/consumer/message_processor.rb +76 -30
- data/lib/jetstream_bridge/consumer/subscription_manager.rb +116 -19
- data/lib/jetstream_bridge/core/connection.rb +2 -7
- data/lib/jetstream_bridge/core/duration.rb +82 -20
- data/lib/jetstream_bridge/publisher/publisher.rb +21 -10
- data/lib/jetstream_bridge/topology/stream.rb +132 -75
- data/lib/jetstream_bridge/version.rb +1 -1
- data/lib/jetstream_bridge.rb +1 -1
- metadata +24 -35
- data/.github/workflows/release.yml +0 -150
- data/.gitignore +0 -56
- data/.idea/.gitignore +0 -8
- data/.idea/dictionaries/project.xml +0 -16
- data/.idea/jetstream_bridge.iml +0 -102
- data/.idea/misc.xml +0 -4
- data/.idea/modules.xml +0 -8
- data/.idea/vcs.xml +0 -6
- data/.rubocop.yml +0 -98
- data/Gemfile +0 -5
- data/Gemfile.lock +0 -268
- data/LICENSE +0 -21
- data/README.md +0 -302
- data/jetstream_bridge.gemspec +0 -60
@@ -4,43 +4,105 @@
|
|
4
4
|
#
|
5
5
|
module JetstreamBridge
|
6
6
|
# Utility for parsing human-friendly durations into milliseconds.
|
7
|
+
#
|
8
|
+
# Defaults to an :auto heuristic for Integer/Float values to preserve
|
9
|
+
# backward compatibility:
|
10
|
+
# - Integers < 1000 are treated as seconds (e.g., 30 -> 30_000ms)
|
11
|
+
# - Integers >= 1000 are treated as milliseconds (e.g., 1500 -> 1500ms)
|
12
|
+
# Prefer setting `default_unit:` to :s or :ms for unambiguous behavior.
|
13
|
+
#
|
7
14
|
# Examples:
|
8
|
-
# Duration.to_millis(30)
|
9
|
-
# Duration.to_millis(
|
10
|
-
# Duration.to_millis(
|
11
|
-
# Duration.to_millis(
|
15
|
+
# Duration.to_millis(30) #=> 30000 (auto)
|
16
|
+
# Duration.to_millis(1500) #=> 1500 (auto)
|
17
|
+
# Duration.to_millis(1500, default_unit: :s) #=> 1_500_000
|
18
|
+
# Duration.to_millis("30s") #=> 30000
|
19
|
+
# Duration.to_millis("500ms") #=> 500
|
20
|
+
# Duration.to_millis("250us") #=> 0
|
21
|
+
# Duration.to_millis("1h") #=> 3_600_000
|
22
|
+
# Duration.to_millis(1_500_000_000, default_unit: :ns) #=> 1500
|
23
|
+
#
|
24
|
+
# Also:
|
25
|
+
# Duration.normalize_list_to_millis(%w[1s 5s 15s]) #=> [1000, 5000, 15000]
|
12
26
|
module Duration
|
13
|
-
|
14
|
-
|
15
|
-
|
27
|
+
# multipliers to convert 1 unit into milliseconds
|
28
|
+
MULTIPLIER_MS = {
|
29
|
+
'ns' => 1.0e-6, # nanoseconds to ms
|
30
|
+
'us' => 1.0e-3, # microseconds to ms
|
31
|
+
'µs' => 1.0e-3, # alt microseconds symbol
|
32
|
+
'ms' => 1, # milliseconds to ms
|
33
|
+
's' => 1_000, # seconds to ms
|
34
|
+
'm' => 60_000, # minutes to ms
|
35
|
+
'h' => 3_600_000, # hours to ms
|
36
|
+
'd' => 86_400_000 # days to ms
|
37
|
+
}.freeze
|
38
|
+
|
39
|
+
NUMBER_RE = /\A\d[\d_]*\z/.freeze
|
40
|
+
TOKEN_RE = /\A(\d[\d_]*(?:\.\d+)?)\s*(ns|us|µs|ms|s|m|h|d)\z/i.freeze
|
16
41
|
|
17
42
|
module_function
|
18
43
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
44
|
+
# default_unit:
|
45
|
+
# :auto (heuristic: int<1000 -> seconds, >=1000 -> ms)
|
46
|
+
# :ns, :us, :ms, :s, :m, :h, :d (explicit)
|
47
|
+
def to_millis(val, default_unit: :auto)
|
48
|
+
case val
|
49
|
+
when Integer then int_to_ms(val, default_unit: default_unit)
|
50
|
+
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
|
+
else
|
53
|
+
raise ArgumentError, "invalid duration type: #{val.class}" unless val.respond_to?(:to_f)
|
54
|
+
|
55
|
+
float_to_ms(val.to_f, default_unit: default_unit)
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
24
59
|
|
25
|
-
|
60
|
+
# Normalize an array of durations into integer milliseconds.
|
61
|
+
def normalize_list_to_millis(values, default_unit: :auto)
|
62
|
+
Array(values).map { |v| to_millis(v, default_unit: default_unit) }
|
26
63
|
end
|
27
64
|
|
28
|
-
|
29
|
-
|
65
|
+
# --- internal helpers ---
|
66
|
+
|
67
|
+
def int_to_ms(num, default_unit:)
|
68
|
+
case default_unit
|
69
|
+
when :auto
|
70
|
+
# Preserve existing heuristic for compatibility
|
71
|
+
i >= 1_000 ? num : num * 1_000
|
72
|
+
else
|
73
|
+
coerce_numeric_to_ms(i.to_f, default_unit)
|
74
|
+
end
|
30
75
|
end
|
31
76
|
|
32
|
-
def float_to_ms(
|
33
|
-
(
|
77
|
+
def float_to_ms(flt, default_unit:)
|
78
|
+
coerce_numeric_to_ms(flt, default_unit)
|
34
79
|
end
|
35
80
|
|
36
|
-
def string_to_ms(str)
|
81
|
+
def string_to_ms(str, default_unit:)
|
37
82
|
s = str.strip
|
38
|
-
|
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)
|
39
85
|
|
40
86
|
m = TOKEN_RE.match(s)
|
41
87
|
raise ArgumentError, "invalid duration: #{str.inspect}" unless m
|
42
88
|
|
43
|
-
|
89
|
+
num = m[1].delete('_').to_f
|
90
|
+
unit = m[2].downcase
|
91
|
+
(num * MULTIPLIER_MS.fetch(unit)).round
|
92
|
+
end
|
93
|
+
|
94
|
+
def coerce_numeric_to_ms(num, unit)
|
95
|
+
case unit
|
96
|
+
when :auto
|
97
|
+
# For floats, :auto treats as seconds (common developer intent)
|
98
|
+
(num * 1_000).round
|
99
|
+
else
|
100
|
+
u = unit.to_s
|
101
|
+
mult = MULTIPLIER_MS[u]
|
102
|
+
raise ArgumentError, "invalid unit for default_unit: #{unit.inspect}" unless mult
|
103
|
+
|
104
|
+
(num * mult).round
|
105
|
+
end
|
44
106
|
end
|
45
107
|
end
|
46
108
|
end
|
@@ -49,9 +49,12 @@ module JetstreamBridge
|
|
49
49
|
|
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)
|
53
|
-
Logging.info(
|
54
|
-
|
54
|
+
Logging.info(
|
55
|
+
"Published #{subject} event_id=#{envelope['event_id']}",
|
56
|
+
tag: 'JetstreamBridge::Publisher'
|
57
|
+
)
|
55
58
|
true
|
56
59
|
end
|
57
60
|
|
@@ -60,8 +63,10 @@ module JetstreamBridge
|
|
60
63
|
klass = ModelUtils.constantize(JetstreamBridge.config.outbox_model)
|
61
64
|
|
62
65
|
unless ModelUtils.ar_class?(klass)
|
63
|
-
Logging.warn(
|
64
|
-
|
66
|
+
Logging.warn(
|
67
|
+
"Outbox model #{klass} is not an ActiveRecord model; publishing directly.",
|
68
|
+
tag: 'JetstreamBridge::Publisher'
|
69
|
+
)
|
65
70
|
return with_retries { do_publish?(subject, envelope) }
|
66
71
|
end
|
67
72
|
|
@@ -70,8 +75,10 @@ module JetstreamBridge
|
|
70
75
|
record = repo.find_or_build(event_id)
|
71
76
|
|
72
77
|
if repo.already_sent?(record)
|
73
|
-
Logging.info(
|
74
|
-
|
78
|
+
Logging.info(
|
79
|
+
"Outbox already sent event_id=#{event_id}; skipping publish.",
|
80
|
+
tag: 'JetstreamBridge::Publisher'
|
81
|
+
)
|
75
82
|
return true
|
76
83
|
end
|
77
84
|
|
@@ -102,14 +109,18 @@ module JetstreamBridge
|
|
102
109
|
|
103
110
|
def backoff(attempts, error)
|
104
111
|
delay = RETRY_BACKOFFS[attempts - 1] || RETRY_BACKOFFS.last
|
105
|
-
Logging.warn(
|
106
|
-
|
112
|
+
Logging.warn(
|
113
|
+
"Publish retry #{attempts} after #{error.class}: #{error.message}",
|
114
|
+
tag: 'JetstreamBridge::Publisher'
|
115
|
+
)
|
107
116
|
sleep delay
|
108
117
|
end
|
109
118
|
|
110
119
|
def log_error(val, exc)
|
111
|
-
Logging.error(
|
112
|
-
|
120
|
+
Logging.error(
|
121
|
+
"Publish failed: #{exc.class} #{exc.message}",
|
122
|
+
tag: 'JetstreamBridge::Publisher'
|
123
|
+
)
|
113
124
|
val
|
114
125
|
end
|
115
126
|
|
@@ -5,11 +5,82 @@ require_relative 'overlap_guard'
|
|
5
5
|
require_relative 'subject_matcher'
|
6
6
|
|
7
7
|
module JetstreamBridge
|
8
|
-
|
8
|
+
module StreamSupport
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def normalize_subjects(list)
|
12
|
+
Array(list).flatten.compact.map!(&:to_s).reject(&:empty?).uniq
|
13
|
+
end
|
14
|
+
|
15
|
+
def missing_subjects(existing, desired)
|
16
|
+
desired.reject { |d| SubjectMatcher.covered?(existing, d) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def stream_not_found?(error)
|
20
|
+
msg = error.message.to_s
|
21
|
+
msg =~ /stream\s+not\s+found/i || msg =~ /\b404\b/
|
22
|
+
end
|
23
|
+
|
24
|
+
def overlap_error?(error)
|
25
|
+
msg = error.message.to_s
|
26
|
+
msg =~ /subjects?\s+overlap/i || msg =~ /\berr_code=10065\b/ || msg =~ /\b400\b/
|
27
|
+
end
|
28
|
+
|
29
|
+
# ---- Logging ----
|
30
|
+
def log_already_covered(name)
|
31
|
+
Logging.info(
|
32
|
+
"Stream #{name} exists; subjects and config already covered.",
|
33
|
+
tag: 'JetstreamBridge::Stream'
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def log_all_blocked(name, blocked)
|
38
|
+
if blocked.any?
|
39
|
+
Logging.warn(
|
40
|
+
"Stream #{name}: all missing subjects belong to other streams; unchanged. " \
|
41
|
+
"blocked=#{blocked.inspect}",
|
42
|
+
tag: 'JetstreamBridge::Stream'
|
43
|
+
)
|
44
|
+
else
|
45
|
+
Logging.info("Stream #{name} exists; nothing to add.", tag: 'JetstreamBridge::Stream')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def log_updated(name, added, blocked)
|
50
|
+
msg = "Updated stream #{name}; added subjects=#{added.inspect}"
|
51
|
+
msg += " (skipped overlapped=#{blocked.inspect})" if blocked.any?
|
52
|
+
Logging.info(msg, tag: 'JetstreamBridge::Stream')
|
53
|
+
end
|
54
|
+
|
55
|
+
def log_not_created(name, blocked)
|
56
|
+
Logging.warn(
|
57
|
+
"Not creating stream #{name}: all desired subjects belong to other streams. " \
|
58
|
+
"blocked=#{blocked.inspect}",
|
59
|
+
tag: 'JetstreamBridge::Stream'
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def log_created(name, allowed, blocked, retention, storage)
|
64
|
+
msg = [
|
65
|
+
"Created stream #{name}",
|
66
|
+
"subjects=#{allowed.inspect}",
|
67
|
+
"retention=#{retention.inspect}",
|
68
|
+
"storage=#{storage.inspect}"
|
69
|
+
].join(' ')
|
70
|
+
msg += " (skipped overlapped=#{blocked.inspect})" if blocked.any?
|
71
|
+
Logging.info(msg, tag: 'JetstreamBridge::Stream')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Ensures a stream exists and updates only uncovered subjects, using work-queue semantics:
|
76
|
+
# Persist with zero consumers; delete after the first ack (retention: 'workqueue').
|
9
77
|
class Stream
|
78
|
+
RETENTION = 'workqueue'
|
79
|
+
STORAGE = 'file'
|
80
|
+
|
10
81
|
class << self
|
11
82
|
def ensure!(jts, name, subjects)
|
12
|
-
desired = normalize_subjects(subjects)
|
83
|
+
desired = StreamSupport.normalize_subjects(subjects)
|
13
84
|
raise ArgumentError, 'subjects must not be empty' if desired.empty?
|
14
85
|
|
15
86
|
attempts = 0
|
@@ -17,14 +88,18 @@ module JetstreamBridge
|
|
17
88
|
info = safe_stream_info(jts, name)
|
18
89
|
info ? ensure_update(jts, name, info, desired) : ensure_create(jts, name, desired)
|
19
90
|
rescue NATS::JetStream::Error => e
|
20
|
-
if overlap_error?(e) && (attempts += 1) <= 1
|
21
|
-
Logging.warn(
|
91
|
+
if StreamSupport.overlap_error?(e) && (attempts += 1) <= 1
|
92
|
+
Logging.warn(
|
93
|
+
"Overlap race while ensuring #{name}; retrying once...",
|
94
|
+
tag: 'JetstreamBridge::Stream'
|
95
|
+
)
|
22
96
|
sleep(0.05)
|
23
97
|
retry
|
24
|
-
elsif overlap_error?(e)
|
98
|
+
elsif StreamSupport.overlap_error?(e)
|
25
99
|
Logging.warn(
|
26
100
|
"Overlap persists ensuring #{name}; leaving unchanged. err=#{e.message.inspect}",
|
27
|
-
tag: 'JetstreamBridge::Stream'
|
101
|
+
tag: 'JetstreamBridge::Stream'
|
102
|
+
)
|
28
103
|
nil
|
29
104
|
else
|
30
105
|
raise
|
@@ -34,95 +109,77 @@ module JetstreamBridge
|
|
34
109
|
|
35
110
|
private
|
36
111
|
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
to_add = missing_subjects(existing, desired)
|
42
|
-
return log_already_covered(name) if to_add.empty?
|
43
|
-
|
44
|
-
allowed, blocked = OverlapGuard.partition_allowed(jts, name, to_add)
|
45
|
-
return log_all_blocked(name, blocked) if allowed.empty?
|
46
|
-
|
47
|
-
target = (existing + allowed).uniq
|
48
|
-
OverlapGuard.check!(jts, name, target)
|
49
|
-
jts.update_stream(name: name, subjects: target)
|
50
|
-
log_updated(name, allowed, blocked)
|
51
|
-
end
|
112
|
+
# ---- keep ensure_update small (<=20 lines, lower ABC) ----
|
113
|
+
def ensure_update(jts, name, info, desired_subjects)
|
114
|
+
existing = StreamSupport.normalize_subjects(info.config.subjects || [])
|
115
|
+
to_add = StreamSupport.missing_subjects(existing, desired_subjects)
|
52
116
|
|
53
|
-
|
117
|
+
return add_subjects(jts, name, existing, to_add) if to_add.any?
|
54
118
|
|
55
|
-
|
56
|
-
|
57
|
-
|
119
|
+
if config_needs_update?(info)
|
120
|
+
apply_update(jts, name, existing)
|
121
|
+
return log_config_updated(name)
|
122
|
+
end
|
58
123
|
|
59
|
-
|
60
|
-
log_created(name, allowed, blocked)
|
124
|
+
StreamSupport.log_already_covered(name)
|
61
125
|
end
|
62
126
|
|
63
|
-
#
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
rescue NATS::JetStream::Error => e
|
68
|
-
return nil if stream_not_found?(e)
|
69
|
-
raise
|
70
|
-
end
|
71
|
-
|
72
|
-
def missing_subjects(existing, desired)
|
73
|
-
desired.reject { |d| SubjectMatcher.covered?(existing, d) }
|
74
|
-
end
|
127
|
+
# ---- tiny helpers extracted to reduce ABC ----
|
128
|
+
def add_subjects(jts, name, existing, to_add)
|
129
|
+
allowed, blocked = OverlapGuard.partition_allowed(jts, name, to_add)
|
130
|
+
return StreamSupport.log_all_blocked(name, blocked) if allowed.empty?
|
75
131
|
|
76
|
-
|
77
|
-
|
132
|
+
target = merge_subjects(existing, allowed)
|
133
|
+
OverlapGuard.check!(jts, name, target)
|
134
|
+
apply_update(jts, name, target)
|
135
|
+
StreamSupport.log_updated(name, allowed, blocked)
|
78
136
|
end
|
79
137
|
|
80
|
-
def
|
81
|
-
|
82
|
-
msg =~ /stream\s+not\s+found/i || msg =~ /\b404\b/
|
138
|
+
def merge_subjects(existing, allowed)
|
139
|
+
(existing + allowed).uniq
|
83
140
|
end
|
84
141
|
|
85
|
-
def
|
86
|
-
|
87
|
-
|
142
|
+
def config_needs_update?(info)
|
143
|
+
info.config.retention.to_s.downcase != RETENTION ||
|
144
|
+
info.config.storage.to_s.downcase != STORAGE
|
88
145
|
end
|
89
146
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
147
|
+
def apply_update(jts, name, subjects)
|
148
|
+
jts.update_stream(
|
149
|
+
name: name,
|
150
|
+
subjects: subjects,
|
151
|
+
retention: RETENTION,
|
152
|
+
storage: STORAGE
|
153
|
+
)
|
94
154
|
end
|
95
155
|
|
96
|
-
def
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
)
|
103
|
-
else
|
104
|
-
Logging.info("Stream #{name} exists; nothing to add.", tag: 'JetstreamBridge::Stream')
|
105
|
-
end
|
156
|
+
def log_config_updated(name)
|
157
|
+
Logging.info(
|
158
|
+
"Updated stream #{name} config; retention=#{RETENTION.inspect} " \
|
159
|
+
"storage=#{STORAGE.inspect}",
|
160
|
+
tag: 'JetstreamBridge::Stream'
|
161
|
+
)
|
106
162
|
end
|
107
163
|
|
108
|
-
def
|
109
|
-
|
110
|
-
|
111
|
-
Logging.info(msg, tag: 'JetstreamBridge::Stream')
|
112
|
-
end
|
164
|
+
def ensure_create(jts, name, desired_subjects)
|
165
|
+
allowed, blocked = OverlapGuard.partition_allowed(jts, name, desired_subjects)
|
166
|
+
return StreamSupport.log_not_created(name, blocked) if allowed.empty?
|
113
167
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
168
|
+
jts.add_stream(
|
169
|
+
name: name,
|
170
|
+
subjects: allowed,
|
171
|
+
retention: RETENTION,
|
172
|
+
storage: STORAGE
|
119
173
|
)
|
174
|
+
StreamSupport.log_created(name, allowed, blocked, RETENTION, STORAGE)
|
120
175
|
end
|
121
176
|
|
122
|
-
def
|
123
|
-
|
124
|
-
|
125
|
-
|
177
|
+
def safe_stream_info(jts, name)
|
178
|
+
jts.stream_info(name)
|
179
|
+
rescue NATS::JetStream::Error => e
|
180
|
+
return nil if StreamSupport.stream_not_found?(e)
|
181
|
+
|
182
|
+
raise
|
126
183
|
end
|
127
184
|
end
|
128
185
|
end
|
data/lib/jetstream_bridge.rb
CHANGED
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jetstream_bridge
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Attara
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-08-
|
11
|
+
date: 2025-08-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: activesupport
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '6.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: activerecord
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -53,33 +53,19 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '2.4'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: oj
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '3.16'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: bundler-audit
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: 0.9.1
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: 0.9.1
|
68
|
+
version: '3.16'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: rake
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -150,6 +136,20 @@ dependencies:
|
|
150
136
|
- - "~>"
|
151
137
|
- !ruby/object:Gem::Version
|
152
138
|
version: '1.21'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: bundler-audit
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 0.9.1
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 0.9.1
|
153
153
|
description: |-
|
154
154
|
Publisher/Consumer utilities for NATS JetStream with environment-scoped subjects,
|
155
155
|
overlap guards, DLQ routing, retries/backoff, and optional Inbox/Outbox patterns.
|
@@ -160,20 +160,6 @@ executables: []
|
|
160
160
|
extensions: []
|
161
161
|
extra_rdoc_files: []
|
162
162
|
files:
|
163
|
-
- ".github/workflows/release.yml"
|
164
|
-
- ".gitignore"
|
165
|
-
- ".idea/.gitignore"
|
166
|
-
- ".idea/dictionaries/project.xml"
|
167
|
-
- ".idea/jetstream_bridge.iml"
|
168
|
-
- ".idea/misc.xml"
|
169
|
-
- ".idea/modules.xml"
|
170
|
-
- ".idea/vcs.xml"
|
171
|
-
- ".rubocop.yml"
|
172
|
-
- Gemfile
|
173
|
-
- Gemfile.lock
|
174
|
-
- LICENSE
|
175
|
-
- README.md
|
176
|
-
- jetstream_bridge.gemspec
|
177
163
|
- lib/generators/jetstream_bridge/initializer/initializer_generator.rb
|
178
164
|
- lib/generators/jetstream_bridge/initializer/templates/jetstream_bridge.rb
|
179
165
|
- lib/generators/jetstream_bridge/install/install_generator.rb
|
@@ -181,11 +167,14 @@ files:
|
|
181
167
|
- lib/generators/jetstream_bridge/migrations/templates/create_jetstream_inbox_events.rb.erb
|
182
168
|
- lib/generators/jetstream_bridge/migrations/templates/create_jetstream_outbox_events.rb.erb
|
183
169
|
- lib/jetstream_bridge.rb
|
170
|
+
- lib/jetstream_bridge/consumer/backoff_strategy.rb
|
184
171
|
- lib/jetstream_bridge/consumer/consumer.rb
|
185
172
|
- lib/jetstream_bridge/consumer/consumer_config.rb
|
173
|
+
- lib/jetstream_bridge/consumer/dlq_publisher.rb
|
186
174
|
- lib/jetstream_bridge/consumer/inbox/inbox_message.rb
|
187
175
|
- lib/jetstream_bridge/consumer/inbox/inbox_processor.rb
|
188
176
|
- lib/jetstream_bridge/consumer/inbox/inbox_repository.rb
|
177
|
+
- lib/jetstream_bridge/consumer/message_context.rb
|
189
178
|
- lib/jetstream_bridge/consumer/message_processor.rb
|
190
179
|
- lib/jetstream_bridge/consumer/subscription_manager.rb
|
191
180
|
- lib/jetstream_bridge/core/config.rb
|