jetstream_bridge 2.0.0 β 2.2.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/Gemfile.lock +1 -3
- data/README.md +9 -9
- data/lib/jetstream_bridge/consumer/consumer.rb +1 -1
- data/lib/jetstream_bridge/core/config.rb +5 -5
- data/lib/jetstream_bridge/core/connection.rb +36 -2
- data/lib/jetstream_bridge/publisher/publisher.rb +6 -6
- data/lib/jetstream_bridge/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d4dcab294bcdbc3c618eef156debf4d7d5e567a105c7795b509cc68543cb592
|
4
|
+
data.tar.gz: de590eb069ca8f09a17b69c53cdc01c4730563ce51f9f43f937cff66d41df700
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '09c98872c6174c4c5a71d1d94d049ce0e554a0667e9a0db864a0a245a6d1cb5865db9b9a979de3d39a2948a7943da260e8f2c9d90fcd74f3eee65490e2d63968'
|
7
|
+
data.tar.gz: 4405598ee5e9050c1f344ed00e388974da0959775a9cd07460ec2f6dbf5c730f562210c2c35e16e5b0b2e3eac61bc3905e294dd3066c61f7bce12a4108000ff7
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
jetstream_bridge (2.
|
4
|
+
jetstream_bridge (2.2.0)
|
5
5
|
activerecord (>= 6.0)
|
6
6
|
activesupport (>= 6.0)
|
7
7
|
nats-pure (~> 2.4)
|
@@ -142,8 +142,6 @@ GEM
|
|
142
142
|
racc (~> 1.4)
|
143
143
|
nokogiri (1.18.7-arm64-darwin)
|
144
144
|
racc (~> 1.4)
|
145
|
-
nokogiri (1.18.7-x86_64-linux-gnu)
|
146
|
-
racc (~> 1.4)
|
147
145
|
parallel (1.27.0)
|
148
146
|
parser (3.3.9.0)
|
149
147
|
ast (~> 2.4.1)
|
data/README.md
CHANGED
@@ -98,11 +98,11 @@ end
|
|
98
98
|
|
99
99
|
## π‘ Subject Conventions
|
100
100
|
|
101
|
-
| Direction | Subject Pattern
|
102
|
-
|
103
|
-
| **Publish** | `{env}.
|
104
|
-
| **Subscribe** | `{env}.
|
105
|
-
| **DLQ** | `{env}.
|
101
|
+
| Direction | Subject Pattern |
|
102
|
+
|---------------|---------------------------|
|
103
|
+
| **Publish** | `{env}.{app}.sync.{dest}` |
|
104
|
+
| **Subscribe** | `{env}.{dest}.sync.{app}` |
|
105
|
+
| **DLQ** | `{env}.sync.dlq` |
|
106
106
|
|
107
107
|
* `{app}`: `app_name`
|
108
108
|
* `{dest}`: `destination_app`
|
@@ -110,12 +110,12 @@ end
|
|
110
110
|
|
111
111
|
---
|
112
112
|
|
113
|
-
## π§± Stream Topology (auto-ensure
|
113
|
+
## π§± Stream Topology (auto-ensure and overlap-safe)
|
114
114
|
|
115
115
|
On first connection, Jetstream Bridge **ensures** a single stream exists for your `env` and that it covers:
|
116
116
|
|
117
|
-
* `source_subject` (`{env}.
|
118
|
-
* `destination_subject` (`{env}.
|
117
|
+
* `source_subject` (`{env}.{app}.sync.{dest}`)
|
118
|
+
* `destination_subject` (`{env}.{dest}.sync.{app}`)
|
119
119
|
* `dlq_subject` (if enabled)
|
120
120
|
|
121
121
|
Itβs **overlap-safe**:
|
@@ -200,7 +200,7 @@ If **Outbox** is enabled, the publish call:
|
|
200
200
|
|
201
201
|
```ruby
|
202
202
|
JetstreamBridge::Consumer.new(
|
203
|
-
durable_name: "#{Rails.env}-
|
203
|
+
durable_name: "#{Rails.env}-#{app_name}-workers",
|
204
204
|
batch_size: 25
|
205
205
|
) do |event, subject, deliveries|
|
206
206
|
# Your idempotent domain logic here
|
@@ -13,7 +13,7 @@ require_relative 'subscription_manager'
|
|
13
13
|
require_relative 'inbox/inbox_processor'
|
14
14
|
|
15
15
|
module JetstreamBridge
|
16
|
-
# Subscribes to "{env}.
|
16
|
+
# Subscribes to "{env}.{dest}.sync.{app}" and processes messages.
|
17
17
|
class Consumer
|
18
18
|
DEFAULT_BATCH_SIZE = 25
|
19
19
|
FETCH_TIMEOUT_SECS = 5
|
@@ -30,19 +30,19 @@ module JetstreamBridge
|
|
30
30
|
end
|
31
31
|
|
32
32
|
# Base subjects
|
33
|
-
# Producer publishes to: {env}.
|
34
|
-
# Consumer subscribes to: {env}.
|
33
|
+
# Producer publishes to: {env}.{app}.sync.{dest}
|
34
|
+
# Consumer subscribes to: {env}.{dest}.sync.{app}
|
35
35
|
def source_subject
|
36
|
-
"#{env}
|
36
|
+
"#{env}.#{app_name}.sync.#{destination_app}"
|
37
37
|
end
|
38
38
|
|
39
39
|
def destination_subject
|
40
|
-
"#{env}
|
40
|
+
"#{env}.#{destination_app}.sync.#{app_name}"
|
41
41
|
end
|
42
42
|
|
43
43
|
# DLQ
|
44
44
|
def dlq_subject
|
45
|
-
"#{env}.
|
45
|
+
"#{env}.sync.dlq"
|
46
46
|
end
|
47
47
|
|
48
48
|
def durable_name
|
@@ -21,11 +21,21 @@ module JetstreamBridge
|
|
21
21
|
}.freeze
|
22
22
|
|
23
23
|
class << self
|
24
|
-
# Thread-safe delegator to the singleton instance
|
24
|
+
# Thread-safe delegator to the singleton instance.
|
25
|
+
# Returns a live JetStream context.
|
25
26
|
def connect!
|
26
27
|
@__mutex ||= Mutex.new
|
27
28
|
@__mutex.synchronize { instance.connect! }
|
28
29
|
end
|
30
|
+
|
31
|
+
# Optional accessors if callers need raw handles
|
32
|
+
def nc
|
33
|
+
instance.__send__(:nc)
|
34
|
+
end
|
35
|
+
|
36
|
+
def jetstream
|
37
|
+
instance.__send__(:jetstream)
|
38
|
+
end
|
29
39
|
end
|
30
40
|
|
31
41
|
# Idempotent: returns an existing, healthy JetStream context or establishes one.
|
@@ -36,12 +46,17 @@ module JetstreamBridge
|
|
36
46
|
raise 'No NATS URLs configured' if servers.empty?
|
37
47
|
|
38
48
|
establish_connection(servers)
|
49
|
+
|
39
50
|
Logging.info(
|
40
|
-
"Connected to NATS (#{servers.size} server#{servers.size == 1
|
51
|
+
"Connected to NATS (#{servers.size} server#{unless servers.size == 1
|
52
|
+
's'
|
53
|
+
end}): #{sanitize_urls(servers).join(', ')}",
|
41
54
|
tag: 'JetstreamBridge::Connection'
|
42
55
|
)
|
43
56
|
|
57
|
+
# Ensure topology (streams, subjects, overlap guard, etc.)
|
44
58
|
Topology.ensure!(@jts)
|
59
|
+
|
45
60
|
@jts
|
46
61
|
end
|
47
62
|
|
@@ -62,7 +77,26 @@ module JetstreamBridge
|
|
62
77
|
def establish_connection(servers)
|
63
78
|
@nc = NATS::IO::Client.new
|
64
79
|
@nc.connect({ servers: servers }.merge(DEFAULT_CONN_OPTS))
|
80
|
+
|
81
|
+
# Create JetStream context
|
65
82
|
@jts = @nc.jetstream
|
83
|
+
|
84
|
+
# --- Compatibility shim: ensure JetStream responds to #nc for older/newer clients ---
|
85
|
+
# Some versions of the NATS Ruby client don't expose nc on the JetStream object.
|
86
|
+
# We attach a singleton method, so code expecting `js.nc` continues to work.
|
87
|
+
return if @jts.respond_to?(:nc)
|
88
|
+
|
89
|
+
nc_ref = @nc
|
90
|
+
@jts.define_singleton_method(:nc) { nc_ref }
|
91
|
+
|
92
|
+
# ------------------------------------------------------------------------------------
|
93
|
+
end
|
94
|
+
|
95
|
+
# Expose for class-level helpers (not part of public API)
|
96
|
+
attr_reader :nc
|
97
|
+
|
98
|
+
def jetstream
|
99
|
+
@jts
|
66
100
|
end
|
67
101
|
|
68
102
|
# Mask credentials in NATS URLs:
|
@@ -9,7 +9,7 @@ require_relative '../core/model_utils'
|
|
9
9
|
require_relative 'outbox_repository'
|
10
10
|
|
11
11
|
module JetstreamBridge
|
12
|
-
# Publishes to "{env}.
|
12
|
+
# Publishes to "{env}.{app}.sync.{dest}".
|
13
13
|
class Publisher
|
14
14
|
DEFAULT_RETRIES = 2
|
15
15
|
RETRY_BACKOFFS = [0.25, 1.0].freeze
|
@@ -33,7 +33,7 @@ module JetstreamBridge
|
|
33
33
|
if JetstreamBridge.config.use_outbox
|
34
34
|
publish_via_outbox(subject, envelope)
|
35
35
|
else
|
36
|
-
with_retries { do_publish(subject, envelope) }
|
36
|
+
with_retries { do_publish?(subject, envelope) }
|
37
37
|
end
|
38
38
|
rescue StandardError => e
|
39
39
|
log_error(false, e)
|
@@ -47,8 +47,8 @@ module JetstreamBridge
|
|
47
47
|
raise ArgumentError, 'destination_app must be configured'
|
48
48
|
end
|
49
49
|
|
50
|
-
def do_publish(subject, envelope)
|
51
|
-
headers = { '
|
50
|
+
def do_publish?(subject, envelope)
|
51
|
+
headers = { 'nats-msg-id' => envelope['event_id'] }
|
52
52
|
@jts.publish(subject, JSON.generate(envelope), header: headers)
|
53
53
|
Logging.info("Published #{subject} event_id=#{envelope['event_id']}",
|
54
54
|
tag: 'JetstreamBridge::Publisher')
|
@@ -62,7 +62,7 @@ module JetstreamBridge
|
|
62
62
|
unless ModelUtils.ar_class?(klass)
|
63
63
|
Logging.warn("Outbox model #{klass} is not an ActiveRecord model; publishing directly.",
|
64
64
|
tag: 'JetstreamBridge::Publisher')
|
65
|
-
return with_retries { do_publish(subject, envelope) }
|
65
|
+
return with_retries { do_publish?(subject, envelope) }
|
66
66
|
end
|
67
67
|
|
68
68
|
repo = OutboxRepository.new(klass)
|
@@ -77,7 +77,7 @@ module JetstreamBridge
|
|
77
77
|
|
78
78
|
repo.persist_pre(record, subject, envelope)
|
79
79
|
|
80
|
-
ok = with_retries { do_publish(subject, envelope) }
|
80
|
+
ok = with_retries { do_publish?(subject, envelope) }
|
81
81
|
ok ? repo.persist_success(record) : repo.persist_failure(record, 'Publish returned false')
|
82
82
|
ok
|
83
83
|
rescue StandardError => e
|