jetstream_bridge 4.5.1 → 4.5.2

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: 56e6cc6b519fe3d4ea947454cd57073bffe5a7929256dfafcd600eb6e5ae51ae
4
- data.tar.gz: 6ffe8856a86778a6b4ae9f693745167a70b51d655fa1fac8c119ec0ce1122860
3
+ metadata.gz: '08ea80256539bb801e52672dc900d0e858b4a22fcc76e0958dfa1341e908dcc8'
4
+ data.tar.gz: 6fd4e80d372f8c9c022dfe0992356a73784f517d0a18d78e65ec78a01652443d
5
5
  SHA512:
6
- metadata.gz: 48af0a3efcea9a94f7093e791a6d02520b6b748c706760809a46438709d877c11662fcd92a5dd6715a0999678904af9e6a417c5cf577f755b0aecf9b9c2a3504
7
- data.tar.gz: 0b151d22b8bd14d38cc2f20ec969b0892794abe74475dc1ce3b76a6773757bd64f72269e8478281e0a971ae7523e34bff808c452745fa07d1e8866d708928a64
6
+ metadata.gz: 5b3a853557c531acac60a559673f95e2daab985ec43adf9b711108d2541fef59e109b894e070459a9259a4a3ccceedc15812e1768ce3a41d47b0087ec40a4a65
7
+ data.tar.gz: 76de67fbf04fdc62bab5c952238403a91f430396a20c738d9863aae4dbc2faed364cf9686791ddc566ffb7fb9a82acb2afef780f3dbbcbdb62d9287d6b58fea6
@@ -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 + durable consumer must be pre-provisioned (via `bundle exec rake jetstream_bridge:provision` or NATS CLI).
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@10.199.12.34:4222" \
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., `jetstream-bridge-stream`)
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**: `{app_name}.sync.{destination_app}` (e.g., `pwas-workers.sync.heavyworth`)
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@10.199.12.34:4222 \
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 production-jetstream-bridge-stream production-pwas-workers \
101
- --filter "production.pwas-workers.sync.heavyworth" \
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 production-jetstream-bridge-stream production-pwas-workers \
115
- --filter "production.pwas-workers.sync.heavyworth" \
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 production-jetstream-bridge-stream production-pwas-workers
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 production-jetstream-bridge-stream > production-pwas-workers
242
+ Information for Consumer pwas-heavyworth-sync > pwas-workers
136
243
 
137
244
  Configuration:
138
245
 
139
- Durable Name: production-pwas-workers
140
- Filter Subject: production.pwas-workers.sync.heavyworth
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 = "jetstream-bridge-stream"
166
- config.app_name = "pwas-workers"
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=production-pwas-workers, batch=25, dest="heavyworth")
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 production-jetstream-bridge-stream
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 production-jetstream-bridge-stream production-pwas-workers -f
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
- ```bash
268
- nats consumer add production-jetstream-bridge-stream production-pwas-workers \
269
- --filter "production.pwas-workers.sync.heavyworth" \
270
- --ack explicit \
271
- --pull \
272
- --deliver all \
273
- --max-deliver 10 \
274
- --ack-wait 60s \
275
- --backoff 2s,10s,30s,60s,120s \
276
- --replay instant \
277
- --max-pending 25000
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 production-jetstream-bridge-stream
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. Pre-create the consumer (as admin)
351
- nats consumer add pwas-heavyworth-sync production-pwas-workers \
352
- --filter "production.pwas-workers.sync.heavyworth" \
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 = "jetstream-bridge-stream"
367
- config.app_name = "pwas-workers"
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"
@@ -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
- puts '✓ Successfully connected to NATS'
105
- puts '✓ JetStream is available'
106
- puts '✓ Stream topology ensured'
107
-
108
- # Check if we can get account info
109
- info = jts.account_info
110
- puts "\nAccount Info:"
111
- puts " Memory: #{info.memory}"
112
- puts " Storage: #{info.storage}"
113
- puts " Streams: #{info.streams}"
114
- puts " Consumers: #{info.consumers}"
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
@@ -4,5 +4,5 @@
4
4
  #
5
5
  # Version constant for the gem.
6
6
  module JetstreamBridge
7
- VERSION = '4.5.1'
7
+ VERSION = '4.5.2'
8
8
  end
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: 4.5.1
4
+ version: 4.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Attara