jetstream_bridge 4.5.1 → 4.5.3
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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eda1d1166867a08139af3cb85b6d9aca0765d5444d4a44a37bbb42e8bb519a4f
|
|
4
|
+
data.tar.gz: 267dba8c88cc5fbff41323ef05b74d3cb6cb9491de38d68b95d2d0fd52a4be03
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1614bfc73e26c5e21820a0dab70ff61f61e3d33cbfbe2fec5e60bea44b9b83d377e694ade633e0e495d105bef18ab9d5782911d5f05a8bd3356f8e9cbecf7779
|
|
7
|
+
data.tar.gz: ab40c30a9f1566cd2bdc7c41a4721b950041052d7365540d517b154bcd34d63cc2daf5dd1042982161a279c8d2940bb7d3df3d536c4a9a796d0abcb907f4a46d
|
|
@@ -34,7 +34,7 @@ When you cannot modify NATS server permissions, you need to:
|
|
|
34
34
|
## Runtime requirements (least privilege)
|
|
35
35
|
|
|
36
36
|
- Config: `config.auto_provision = false`, `config.stream_name` set explicitly.
|
|
37
|
-
- Topology: stream
|
|
37
|
+
- Topology: **one shared stream per app pair**, with one durable consumer per app (each filters the opposite direction). Pre-provision via `bundle exec rake jetstream_bridge:provision` or NATS CLI.
|
|
38
38
|
- NATS permissions for runtime creds:
|
|
39
39
|
- publish allow: `">"` (or narrowed to your business subjects) and `$JS.API.CONSUMER.MSG.NEXT.{stream_name}.{app_name}-workers`
|
|
40
40
|
- subscribe allow: `">"` (or narrowed) and `_INBOX.>` (responses for pull consumers)
|
|
@@ -51,13 +51,25 @@ When you cannot modify NATS server permissions, you need to:
|
|
|
51
51
|
|
|
52
52
|
---
|
|
53
53
|
|
|
54
|
+
### Connectivity check with restricted creds
|
|
55
|
+
|
|
56
|
+
After setting `config.auto_provision = false`, you can still confirm basic connectivity without touching `$JS.API.*` by running:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
bundle exec rake jetstream_bridge:test_connection
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
In this mode the task performs a NATS ping and JetStream client setup only; it assumes the stream + consumer are already provisioned with admin credentials. If you see a permissions violation for `$JS.API.*`, re-run with a privileged user to provision or set `auto_provision=false` and pre-create the stream/consumer first.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
54
66
|
## Option A: Provision with the built-in task (creates stream + consumer)
|
|
55
67
|
|
|
56
68
|
Run this from CI/deploy with admin NATS credentials:
|
|
57
69
|
|
|
58
70
|
```bash
|
|
59
71
|
# Uses your configured stream/app/destination to create the stream + durable consumer
|
|
60
|
-
NATS_URLS="nats://admin:pass@
|
|
72
|
+
NATS_URLS="nats://admin:pass@nats.example.com:4222" \
|
|
61
73
|
bundle exec rake jetstream_bridge:provision
|
|
62
74
|
```
|
|
63
75
|
|
|
@@ -77,28 +89,28 @@ brew install nats-io/nats-tools/nats
|
|
|
77
89
|
|
|
78
90
|
### Create the Consumer
|
|
79
91
|
|
|
80
|
-
You need to create a durable pull consumer with the exact configuration your app expects.
|
|
92
|
+
You need to create a durable pull consumer with the exact configuration your app expects. Both apps share a single stream; create one consumer per app (each filters the opposite direction).
|
|
81
93
|
|
|
82
94
|
**Required values from your JetStream Bridge config:**
|
|
83
95
|
|
|
84
|
-
- **Stream name**: `JETSTREAM_STREAM_NAME` (e.g., `
|
|
96
|
+
- **Stream name**: `JETSTREAM_STREAM_NAME` (e.g., `pwas-heavyworth-sync`)
|
|
85
97
|
- **Consumer name**: `{app_name}-workers` (e.g., `pwas-workers`)
|
|
86
|
-
- **Filter subject**: `{
|
|
98
|
+
- **Filter subject**: `{destination_app}.sync.{app_name}` (e.g., `heavyworth.sync.pwas`)
|
|
87
99
|
|
|
88
100
|
**Create consumer command:**
|
|
89
101
|
|
|
90
102
|
```bash
|
|
91
103
|
# Connect using a privileged NATS account
|
|
92
104
|
nats context save admin \
|
|
93
|
-
--server=nats://admin-user:admin-pass@
|
|
105
|
+
--server=nats://admin-user:admin-pass@nats.example.com:4222 \
|
|
94
106
|
--description="Admin account for consumer creation"
|
|
95
107
|
|
|
96
108
|
# Select the context
|
|
97
109
|
nats context select admin
|
|
98
110
|
|
|
99
|
-
# Create the consumer
|
|
100
|
-
nats consumer add
|
|
101
|
-
--filter "
|
|
111
|
+
# Create the consumer (replace stream/app/destination with yours)
|
|
112
|
+
nats consumer add pwas-heavyworth-sync pwas-workers \
|
|
113
|
+
--filter "heavyworth.sync.pwas" \
|
|
102
114
|
--ack explicit \
|
|
103
115
|
--pull \
|
|
104
116
|
--deliver all \
|
|
@@ -111,8 +123,36 @@ nats consumer add production-jetstream-bridge-stream production-pwas-workers \
|
|
|
111
123
|
**With backoff (recommended for production):**
|
|
112
124
|
|
|
113
125
|
```bash
|
|
114
|
-
nats consumer add
|
|
115
|
-
--filter "
|
|
126
|
+
nats consumer add pwas-heavyworth-sync pwas-workers \
|
|
127
|
+
--filter "heavyworth.sync.pwas" \
|
|
128
|
+
--ack explicit \
|
|
129
|
+
--pull \
|
|
130
|
+
--deliver all \
|
|
131
|
+
--max-deliver 5 \
|
|
132
|
+
--ack-wait 30s \
|
|
133
|
+
--backoff 1s,5s,15s,30s,60s \
|
|
134
|
+
--replay instant \
|
|
135
|
+
--max-pending 25000
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Example using your observed stream (`pwas-heavyworth-sync`) and defaults (shared stream, two consumers):**
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Admin context
|
|
142
|
+
nats context save admin \
|
|
143
|
+
--server=nats://admin-user:admin-pass@nats.example.com:4222 \
|
|
144
|
+
--description="Admin account for jetstream_bridge provisioning"
|
|
145
|
+
nats context select admin
|
|
146
|
+
|
|
147
|
+
# Create stream with bridge subjects + DLQ
|
|
148
|
+
nats stream add pwas-heavyworth-sync \
|
|
149
|
+
--subjects "pwas.sync.heavyworth" "heavyworth.sync.pwas" "pwas.sync.dlq" \
|
|
150
|
+
--retention workqueue \
|
|
151
|
+
--storage file
|
|
152
|
+
|
|
153
|
+
# Create durable pull consumer (must match JetstreamBridge config)
|
|
154
|
+
nats consumer add pwas-heavyworth-sync pwas-workers \
|
|
155
|
+
--filter "heavyworth.sync.pwas" \
|
|
116
156
|
--ack explicit \
|
|
117
157
|
--pull \
|
|
118
158
|
--deliver all \
|
|
@@ -121,23 +161,90 @@ nats consumer add production-jetstream-bridge-stream production-pwas-workers \
|
|
|
121
161
|
--backoff 1s,5s,15s,30s,60s \
|
|
122
162
|
--replay instant \
|
|
123
163
|
--max-pending 25000
|
|
164
|
+
|
|
165
|
+
# Consumer for heavyworth (receives pwas -> heavyworth)
|
|
166
|
+
nats consumer add pwas-heavyworth-sync heavyworth-workers \
|
|
167
|
+
--filter "pwas.sync.heavyworth" \
|
|
168
|
+
--ack explicit \
|
|
169
|
+
--pull \
|
|
170
|
+
--deliver all \
|
|
171
|
+
--max-deliver 5 \
|
|
172
|
+
--ack-wait 30s \
|
|
173
|
+
--backoff 1s,5s,15s,30s,60s \
|
|
174
|
+
--replay instant \
|
|
175
|
+
--max-pending 25000
|
|
176
|
+
|
|
177
|
+
# Verify
|
|
178
|
+
nats stream info pwas-heavyworth-sync
|
|
179
|
+
nats consumer info pwas-heavyworth-sync heavyworth-workers
|
|
180
|
+
nats consumer info pwas-heavyworth-sync pwas-workers
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Provision both apps (shared stream + two consumers)
|
|
184
|
+
|
|
185
|
+
If both apps share one stream, create it once and add a durable consumer for each side:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
STREAM="appA-appB-sync"
|
|
189
|
+
APP_A="appA" # first app name
|
|
190
|
+
APP_B="appB" # second app name
|
|
191
|
+
|
|
192
|
+
# Admin context (update server/creds as needed)
|
|
193
|
+
nats context save admin \
|
|
194
|
+
--server=nats://admin-user:admin-pass@nats.example.com:4222 \
|
|
195
|
+
--description="Admin account for jetstream_bridge provisioning"
|
|
196
|
+
nats context select admin
|
|
197
|
+
|
|
198
|
+
# Stream covers both publish directions + both DLQs
|
|
199
|
+
nats stream add "$STREAM" \
|
|
200
|
+
--subjects "$APP_A.sync.$APP_B" "$APP_B.sync.$APP_A" "$APP_A.sync.dlq" "$APP_B.sync.dlq" \
|
|
201
|
+
--retention workqueue \
|
|
202
|
+
--storage file
|
|
203
|
+
|
|
204
|
+
# Consumer for APP_A (receives messages destined to APP_A)
|
|
205
|
+
nats consumer add "$STREAM" "$APP_A-workers" \
|
|
206
|
+
--filter "$APP_B.sync.$APP_A" \
|
|
207
|
+
--ack explicit \
|
|
208
|
+
--pull \
|
|
209
|
+
--deliver all \
|
|
210
|
+
--max-deliver 5 \
|
|
211
|
+
--ack-wait 30s \
|
|
212
|
+
--backoff 1s,5s,15s,30s,60s \
|
|
213
|
+
--replay instant \
|
|
214
|
+
--max-pending 25000
|
|
215
|
+
|
|
216
|
+
# Consumer for APP_B (receives messages destined to APP_B)
|
|
217
|
+
nats consumer add "$STREAM" "$APP_B-workers" \
|
|
218
|
+
--filter "$APP_A.sync.$APP_B" \
|
|
219
|
+
--ack explicit \
|
|
220
|
+
--pull \
|
|
221
|
+
--deliver all \
|
|
222
|
+
--max-deliver 5 \
|
|
223
|
+
--ack-wait 30s \
|
|
224
|
+
--backoff 1s,5s,15s,30s,60s \
|
|
225
|
+
--replay instant \
|
|
226
|
+
--max-pending 25000
|
|
227
|
+
|
|
228
|
+
# Verify both
|
|
229
|
+
nats consumer info "$STREAM" "$APP_A-workers"
|
|
230
|
+
nats consumer info "$STREAM" "$APP_B-workers"
|
|
124
231
|
```
|
|
125
232
|
|
|
126
233
|
### Verify Consumer Creation
|
|
127
234
|
|
|
128
235
|
```bash
|
|
129
|
-
nats consumer info
|
|
236
|
+
nats consumer info pwas-heavyworth-sync pwas-workers
|
|
130
237
|
```
|
|
131
238
|
|
|
132
239
|
Expected output:
|
|
133
240
|
|
|
134
241
|
```bash
|
|
135
|
-
Information for Consumer
|
|
242
|
+
Information for Consumer pwas-heavyworth-sync > pwas-workers
|
|
136
243
|
|
|
137
244
|
Configuration:
|
|
138
245
|
|
|
139
|
-
Durable Name:
|
|
140
|
-
Filter Subject:
|
|
246
|
+
Durable Name: pwas-workers
|
|
247
|
+
Filter Subject: heavyworth.sync.pwas
|
|
141
248
|
Ack Policy: explicit
|
|
142
249
|
Ack Wait: 30s
|
|
143
250
|
Replay Policy: instant
|
|
@@ -162,8 +269,8 @@ Configure JetStream Bridge to avoid JetStream management APIs at runtime (pre-pr
|
|
|
162
269
|
# config/initializers/jetstream_bridge.rb
|
|
163
270
|
JetstreamBridge.configure do |config|
|
|
164
271
|
config.nats_urls = ENV.fetch("NATS_URLS")
|
|
165
|
-
config.stream_name = "
|
|
166
|
-
config.app_name = "pwas
|
|
272
|
+
config.stream_name = "pwas-heavyworth-sync"
|
|
273
|
+
config.app_name = "pwas"
|
|
167
274
|
config.destination_app = "heavyworth"
|
|
168
275
|
config.auto_provision = false
|
|
169
276
|
|
|
@@ -222,7 +329,7 @@ sudo journalctl -u pwas_production_sync -f
|
|
|
222
329
|
|
|
223
330
|
```bash
|
|
224
331
|
INFO -- : [JetstreamBridge::ConnectionManager] Connected to NATS (1 server): nats://pwas:***@10.199.12.34:4222
|
|
225
|
-
INFO -- : [DataSync] Consumer starting (durable=
|
|
332
|
+
INFO -- : [DataSync] Consumer starting (durable=pwas-workers, batch=25, dest="heavyworth")
|
|
226
333
|
INFO -- : [DataSync] run! started successfully
|
|
227
334
|
```
|
|
228
335
|
|
|
@@ -233,7 +340,7 @@ Health checks will report connectivity only when `auto_provision=false` (stream
|
|
|
233
340
|
1. **Timeout during subscribe** - The pre-created consumer name/filter might not match. Verify:
|
|
234
341
|
|
|
235
342
|
```bash
|
|
236
|
-
nats consumer ls
|
|
343
|
+
nats consumer ls pwas-heavyworth-sync
|
|
237
344
|
```
|
|
238
345
|
|
|
239
346
|
2. **Permission violations on fetch** - The NATS user also needs permission to fetch messages. Minimum required:
|
|
@@ -259,23 +366,23 @@ If you need to change consumer settings (e.g., increase `max_deliver`):
|
|
|
259
366
|
2. **Delete the old consumer** (using privileged account)
|
|
260
367
|
|
|
261
368
|
```bash
|
|
262
|
-
nats consumer rm
|
|
369
|
+
nats consumer rm pwas-heavyworth-sync pwas-workers -f
|
|
263
370
|
```
|
|
264
371
|
|
|
265
372
|
3. **Create the new consumer** with updated settings
|
|
266
373
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
374
|
+
```bash
|
|
375
|
+
nats consumer add pwas-heavyworth-sync pwas-workers \
|
|
376
|
+
--filter "heavyworth.sync.pwas" \
|
|
377
|
+
--ack explicit \
|
|
378
|
+
--pull \
|
|
379
|
+
--deliver all \
|
|
380
|
+
--max-deliver 10 \
|
|
381
|
+
--ack-wait 60s \
|
|
382
|
+
--backoff 2s,10s,30s,60s,120s \
|
|
383
|
+
--replay instant \
|
|
384
|
+
--max-pending 25000
|
|
385
|
+
```
|
|
279
386
|
|
|
280
387
|
4. **Update your application config** to match
|
|
281
388
|
|
|
@@ -312,7 +419,7 @@ end
|
|
|
312
419
|
**Check stream has messages:**
|
|
313
420
|
|
|
314
421
|
```bash
|
|
315
|
-
nats stream info
|
|
422
|
+
nats stream info pwas-heavyworth-sync
|
|
316
423
|
```
|
|
317
424
|
|
|
318
425
|
**Verify filter subject matches your topology:**
|
|
@@ -347,24 +454,43 @@ Check if the issue is:
|
|
|
347
454
|
Based on your logs, here's the exact setup:
|
|
348
455
|
|
|
349
456
|
```bash
|
|
350
|
-
# 1.
|
|
351
|
-
nats
|
|
352
|
-
--
|
|
457
|
+
# 1. Provision stream and both consumers (as admin)
|
|
458
|
+
nats stream add pwas-heavyworth-sync \
|
|
459
|
+
--subjects "pwas.sync.heavyworth" "heavyworth.sync.pwas" "pwas.sync.dlq" "heavyworth.sync.dlq" \
|
|
460
|
+
--retention workqueue \
|
|
461
|
+
--storage file
|
|
462
|
+
|
|
463
|
+
# Consumer for pwas (receives heavyworth -> pwas)
|
|
464
|
+
nats consumer add pwas-heavyworth-sync pwas-workers \
|
|
465
|
+
--filter "heavyworth.sync.pwas" \
|
|
466
|
+
--ack explicit \
|
|
467
|
+
--pull \
|
|
468
|
+
--deliver all \
|
|
469
|
+
--max-deliver 5 \
|
|
470
|
+
--ack-wait 30s \
|
|
471
|
+
--backoff 1s,5s,15s,30s,60s \
|
|
472
|
+
--replay instant \
|
|
473
|
+
--max-pending 25000
|
|
474
|
+
|
|
475
|
+
# Consumer for heavyworth (receives pwas -> heavyworth)
|
|
476
|
+
nats consumer add pwas-heavyworth-sync heavyworth-workers \
|
|
477
|
+
--filter "pwas.sync.heavyworth" \
|
|
353
478
|
--ack explicit \
|
|
354
479
|
--pull \
|
|
355
480
|
--deliver all \
|
|
356
481
|
--max-deliver 5 \
|
|
357
482
|
--ack-wait 30s \
|
|
358
483
|
--backoff 1s,5s,15s,30s,60s \
|
|
359
|
-
--replay instant
|
|
484
|
+
--replay instant \
|
|
485
|
+
--max-pending 25000
|
|
360
486
|
```
|
|
361
487
|
|
|
362
488
|
```ruby
|
|
363
489
|
# 2. Update config/initializers/jetstream_bridge.rb
|
|
364
490
|
JetstreamBridge.configure do |config|
|
|
365
|
-
config.nats_urls = "nats://pwas:***@10.199.12.34:4222
|
|
366
|
-
config.stream_name = "
|
|
367
|
-
config.app_name = "pwas
|
|
491
|
+
config.nats_urls = ENV.fetch("NATS_URLS") # e.g., nats://pwas:***@10.199.12.34:4222
|
|
492
|
+
config.stream_name = "pwas-heavyworth-sync"
|
|
493
|
+
config.app_name = "pwas"
|
|
368
494
|
config.destination_app = "heavyworth"
|
|
369
495
|
config.max_deliver = 5
|
|
370
496
|
config.ack_wait = "30s"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'json'
|
|
3
4
|
require_relative '../core/logging'
|
|
4
5
|
require_relative '../core/duration'
|
|
5
6
|
require_relative '../errors'
|
|
@@ -47,32 +48,7 @@ module JetstreamBridge
|
|
|
47
48
|
# This bypasses the permission check in nats-pure's pull_subscribe
|
|
48
49
|
nc = resolve_nc
|
|
49
50
|
|
|
50
|
-
if nc.respond_to?(:new_inbox) && nc.respond_to?(:subscribe)
|
|
51
|
-
prefix = @jts.instance_variable_get(:@prefix) || '$JS.API'
|
|
52
|
-
deliver = nc.new_inbox
|
|
53
|
-
sub = nc.subscribe(deliver)
|
|
54
|
-
|
|
55
|
-
# Extend with PullSubscription module to add fetch methods
|
|
56
|
-
sub.extend(NATS::JetStream::PullSubscription)
|
|
57
|
-
|
|
58
|
-
# Set up the JSI (JetStream Info) struct that PullSubscription expects
|
|
59
|
-
# This matches what nats-pure does in pull_subscribe
|
|
60
|
-
subject = "#{prefix}.CONSUMER.MSG.NEXT.#{stream_name}.#{@durable}"
|
|
61
|
-
sub.jsi = NATS::JetStream::JS::Sub.new(
|
|
62
|
-
js: @jts,
|
|
63
|
-
stream: stream_name,
|
|
64
|
-
consumer: @durable,
|
|
65
|
-
nms: subject
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
Logging.info(
|
|
69
|
-
"Created pull subscription without verification for consumer #{@durable} " \
|
|
70
|
-
"(stream=#{stream_name}, filter=#{filter_subject})",
|
|
71
|
-
tag: 'JetstreamBridge::Consumer'
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
return sub
|
|
75
|
-
end
|
|
51
|
+
return build_pull_subscription(nc) if nc.respond_to?(:new_inbox) && nc.respond_to?(:subscribe)
|
|
76
52
|
|
|
77
53
|
# Fallback for environments (mocks/tests) where low-level NATS client is unavailable.
|
|
78
54
|
if @jts.respond_to?(:pull_subscribe)
|
|
@@ -193,5 +169,76 @@ module JetstreamBridge
|
|
|
193
169
|
|
|
194
170
|
nil
|
|
195
171
|
end
|
|
172
|
+
|
|
173
|
+
def build_pull_subscription(nats_client)
|
|
174
|
+
prefix = @jts.instance_variable_get(:@prefix) || '$JS.API'
|
|
175
|
+
deliver = nats_client.new_inbox
|
|
176
|
+
sub = nats_client.subscribe(deliver)
|
|
177
|
+
sub.instance_variable_set(:@_jsb_nc, nats_client)
|
|
178
|
+
sub.instance_variable_set(:@_jsb_deliver, deliver)
|
|
179
|
+
sub.instance_variable_set(:@_jsb_next_subject, "#{prefix}.CONSUMER.MSG.NEXT.#{stream_name}.#{@durable}")
|
|
180
|
+
|
|
181
|
+
extend_pull_subscription(sub)
|
|
182
|
+
attach_jsi(sub)
|
|
183
|
+
|
|
184
|
+
Logging.info(
|
|
185
|
+
"Created pull subscription without verification for consumer #{@durable} " \
|
|
186
|
+
"(stream=#{stream_name}, filter=#{filter_subject})",
|
|
187
|
+
tag: 'JetstreamBridge::Consumer'
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
sub
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def extend_pull_subscription(sub)
|
|
194
|
+
pull_mod = begin
|
|
195
|
+
NATS::JetStream.const_get(:PullSubscription)
|
|
196
|
+
rescue NameError
|
|
197
|
+
nil
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
sub.extend(pull_mod) if pull_mod
|
|
201
|
+
shim_fetch(sub) unless pull_mod
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def shim_fetch(sub)
|
|
205
|
+
Logging.warn(
|
|
206
|
+
'PullSubscription mixin unavailable; using shim fetch implementation',
|
|
207
|
+
tag: 'JetstreamBridge::Consumer'
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
sub.define_singleton_method(:fetch) do |batch_size, timeout: nil|
|
|
211
|
+
nc_handle = instance_variable_get(:@_jsb_nc)
|
|
212
|
+
deliver_subject = instance_variable_get(:@_jsb_deliver)
|
|
213
|
+
next_subject = instance_variable_get(:@_jsb_next_subject)
|
|
214
|
+
unless nc_handle && deliver_subject && next_subject
|
|
215
|
+
raise JetstreamBridge::ConnectionError, 'Missing NATS handles for fetch'
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
expires_ns = ((timeout || 5).to_f * 1_000_000_000).to_i
|
|
219
|
+
payload = { batch: batch_size, expires: expires_ns }.to_json
|
|
220
|
+
|
|
221
|
+
nc_handle.publish(next_subject, payload, deliver_subject)
|
|
222
|
+
nc_handle.flush
|
|
223
|
+
|
|
224
|
+
messages = []
|
|
225
|
+
batch_size.times do
|
|
226
|
+
msg = next_msg(timeout || 5)
|
|
227
|
+
messages << msg if msg
|
|
228
|
+
rescue NATS::IO::Timeout, NATS::Timeout
|
|
229
|
+
break
|
|
230
|
+
end
|
|
231
|
+
messages
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def attach_jsi(sub)
|
|
236
|
+
sub.jsi = NATS::JetStream::JS::Sub.new(
|
|
237
|
+
js: @jts,
|
|
238
|
+
stream: stream_name,
|
|
239
|
+
consumer: @durable,
|
|
240
|
+
nms: sub.instance_variable_get(:@_jsb_next_subject)
|
|
241
|
+
)
|
|
242
|
+
end
|
|
196
243
|
end
|
|
197
244
|
end
|
|
@@ -99,23 +99,54 @@ namespace :jetstream_bridge do
|
|
|
99
99
|
task test_connection: :environment do
|
|
100
100
|
puts '[jetstream_bridge] Testing NATS connection...'
|
|
101
101
|
|
|
102
|
+
permission_violation = lambda do |err|
|
|
103
|
+
current = err
|
|
104
|
+
while current
|
|
105
|
+
message = current.message.to_s
|
|
106
|
+
return true if message.include?('Permissions Violation for Publish to "$JS.API')
|
|
107
|
+
return true if message.match?(/permission(?:s)? violation/i)
|
|
108
|
+
|
|
109
|
+
current = current.cause if current.respond_to?(:cause)
|
|
110
|
+
end
|
|
111
|
+
false
|
|
112
|
+
end
|
|
113
|
+
|
|
102
114
|
begin
|
|
115
|
+
provision_enabled = JetstreamBridge.config.auto_provision
|
|
103
116
|
jts = JetstreamBridge.connect_and_ensure_stream!
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
|
|
118
|
+
if provision_enabled
|
|
119
|
+
puts '✓ Successfully connected to NATS'
|
|
120
|
+
puts '✓ JetStream is available'
|
|
121
|
+
puts '✓ Stream topology ensured'
|
|
122
|
+
|
|
123
|
+
# Check if we can get account info
|
|
124
|
+
info = jts.account_info
|
|
125
|
+
puts "\nAccount Info:"
|
|
126
|
+
puts " Memory: #{info.memory}"
|
|
127
|
+
puts " Storage: #{info.storage}"
|
|
128
|
+
puts " Streams: #{info.streams}"
|
|
129
|
+
puts " Consumers: #{info.consumers}"
|
|
130
|
+
else
|
|
131
|
+
nc = jts.nc if jts.respond_to?(:nc)
|
|
132
|
+
nc ||= JetstreamBridge::Connection.nc
|
|
133
|
+
nc&.flush(0.5)
|
|
134
|
+
|
|
135
|
+
puts '✓ Successfully connected to NATS (ping-only: auto_provision=false)'
|
|
136
|
+
puts '✓ JetStream client initialized (skipped $JS.API.* calls)'
|
|
137
|
+
puts "✓ Stream provisioning/verification skipped for '#{JetstreamBridge.config.stream_name}' " \
|
|
138
|
+
'(assumes pre-provisioned via admin credentials)'
|
|
139
|
+
end
|
|
115
140
|
|
|
116
141
|
exit 0
|
|
117
142
|
rescue StandardError => e
|
|
118
143
|
puts "✗ Connection failed: #{e.message}"
|
|
144
|
+
if permission_violation.call(e)
|
|
145
|
+
puts "\nHint: current NATS credentials cannot call JetStream API subjects ($JS.API.*). " \
|
|
146
|
+
'Set config.auto_provision = false and pre-provision using ' \
|
|
147
|
+
'`bundle exec rake jetstream_bridge:provision` with admin credentials. ' \
|
|
148
|
+
'See docs/RESTRICTED_PERMISSIONS.md.'
|
|
149
|
+
end
|
|
119
150
|
puts "\nBacktrace:" if ENV['VERBOSE']
|
|
120
151
|
puts e.backtrace.first(10).map { |line| " #{line}" }.join("\n") if ENV['VERBOSE']
|
|
121
152
|
exit 1
|