jetstream_bridge 5.1.0 → 7.0.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: '087fb2993c66be217aa3b544a333d0b166aec88f9a006051010e46309faac51f'
4
- data.tar.gz: 2489d32b3047955699c11ae640014baec66855cad384543b3220fe778ff2b65a
3
+ metadata.gz: 471b7dd68739c9de058cbaeedb800dbb5c0e1eadaa2d54602eee34d8469f2668
4
+ data.tar.gz: 7d16630ce0e83b6d6fb1ab6f9e6c5151a6aa5df7467810f950fd984e035bb9f0
5
5
  SHA512:
6
- metadata.gz: 88e316fb7cfc4f5048290ca37e3c065fc7cb6e059627f5900455c14adf59957c9f8db8e428b82f879511979db2e52ff1a15decb754c7156f9a51f81d766d30f3
7
- data.tar.gz: 897a1695d32323c7d33dcc8a3811cb9a7952e45088d14b8c81713f08085869805d97a40de34c3c10f33ae8c39861972ff4f9c1cff1af57699c10aa5e8bf05a50
6
+ metadata.gz: d58a47de952678819cd5681b750907ca9d56be3bf9799d5e6f614acd828c9d86da80c7be1006280cbfa42c88ebe8fd6a804da829b5f73672dd59766955df1d16
7
+ data.tar.gz: f963140e8a639fc285b633f112df806ff3638e3d83f1b834702d91a47d4a2f28c704071c8ae2f3d763c6d750aa708e12babe33872dbb3778d53058965cd987e0
data/CHANGELOG.md CHANGED
@@ -5,6 +5,43 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [7.0.0] - 2026-01-30
9
+
10
+ ### Added
11
+
12
+ - Full Rails reference examples (non-restrictive and restrictive) with Docker Compose, provisioner service, and end-to-end test scripts.
13
+ - Architecture and API reference docs covering topology, consumer modes, and public surface.
14
+
15
+ ### Changed
16
+
17
+ - Major refactor separating provisioning from runtime to support least-privilege deployments; `auto_provision=false` now avoids JetStream management APIs at runtime.
18
+ - Config helpers simplified bidirectional setup (`configure_bidirectional`, `setup_rails_lifecycle`) replacing verbose initializer boilerplate.
19
+ - Consumer pipeline hardened: push-consumer mode, safer signal handling, improved drain behavior, and pull subscription shim reliability.
20
+
21
+ ### Fixed
22
+
23
+ - Provisioner keyword argument handling, generator load edge cases, and connection/consumer recovery during JetStream context refresh.
24
+
25
+ ## [5.0.0] - 2026-01-30
26
+
27
+ ### Added
28
+
29
+ - **Push consumer mode** for restricted NATS credentials — `consumer_mode: :push` with optional `delivery_subject`, no `$JS.API.*` permissions required, documented in the restricted permissions guide.
30
+ - **Reference examples**: full Rails apps for non-restrictive (auto-provisioning) and restrictive (least-privilege + provisioner) deployments with Docker Compose and end-to-end test scripts.
31
+ - **ConfigHelpers & docs**: helper to configure bidirectional sync in one call, Rails lifecycle helper, new Architecture/API docs describing topology and public surface.
32
+
33
+ ### Changed
34
+
35
+ - **Provisioning flow** separated from runtime: when `auto_provision=false` runtime skips JetStream management APIs; provisioning handled via rake task/CLI with admin creds. Health/test_connection honor restricted mode.
36
+ - **Consumer reliability**: refactored subscription builder, trap-safe signal handling, push-consumer drain via `next_msg`, sturdier pull subscription shim, JetStream context refresh retries after reconnects.
37
+ - **Quality & coverage**: generator template tweaks, provisioner keyword fixes, and test suite expanded (~96% coverage) across provisioning, consumer, and connection lifecycle paths.
38
+
39
+ ### Fixed
40
+
41
+ - Fixed TypeError/timeout handling in push consumer drain and process loop.
42
+ - Fixed Rails generator load edge case and provisioner keyword argument bugs.
43
+ - Guarded JetStream API calls when constants are private, preserving connection state during JetStream context refresh failures.
44
+
8
45
  ## [4.4.1] - 2026-01-16
9
46
 
10
47
  ### Fixed
data/README.md CHANGED
@@ -20,21 +20,22 @@
20
20
  </a>
21
21
  </p>
22
22
 
23
- Production-ready NATS JetStream bridge for Ruby/Rails with outbox, inbox, DLQ, and overlap-safe stream provisioning.
23
+ Production-ready NATS JetStream bridge for Ruby/Rails with outbox, inbox, DLQ, and overlap-safe stream provisioning.
24
24
 
25
25
  ## Highlights
26
26
 
27
27
  - Transactional outbox and idempotent inbox (optional) for exactly-once pipelines.
28
- - Durable pull consumers with retries, backoff, and DLQ routing.
28
+ - Durable pull (default) or push consumers with retries, backoff, and DLQ routing.
29
29
  - Auto stream/consumer provisioning with overlap protection.
30
30
  - Rails-native: generators, migrations, health check, and eager-loading safety.
31
+ - Least-privilege friendly: run with `auto_provision=false` plus pre-created consumers.
31
32
  - Mock NATS for fast, no-infra testing.
32
33
 
33
34
  ## Quick Start
34
35
 
35
36
  ```ruby
36
37
  # Gemfile
37
- gem "jetstream_bridge", "~> 5.0"
38
+ gem "jetstream_bridge", "~> 7.0"
38
39
  ```
39
40
 
40
41
  ```bash
@@ -63,10 +64,12 @@ consumer.run!
63
64
  ## Documentation
64
65
 
65
66
  - [Getting Started](docs/GETTING_STARTED.md) - Setup, configuration, and basic usage
67
+ - [API Reference](docs/API.md) - Complete API documentation for all public methods
66
68
  - [Architecture & Topology](docs/ARCHITECTURE.md) - Internal architecture, message flow, and patterns
67
69
  - [Production Guide](docs/PRODUCTION.md) - Production deployment and monitoring
68
70
  - [Restricted Permissions & Provisioning](docs/RESTRICTED_PERMISSIONS.md) - Manual provisioning and security
69
71
  - [Testing with Mock NATS](docs/TESTING.md) - Fast, no-infra testing
72
+ - [Reference Examples (Rails 7)](examples/README.md) - Non-restrictive and restrictive Dockerized examples with E2E tests
70
73
 
71
74
  ## License
72
75
 
data/docs/API.md ADDED
@@ -0,0 +1,395 @@
1
+ # API Reference
2
+
3
+ Complete reference for JetstreamBridge public API.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Configuration](#configuration)
8
+ - [Lifecycle Methods](#lifecycle-methods)
9
+ - [Publishing](#publishing)
10
+ - [Consuming](#consuming)
11
+ - [Provisioning](#provisioning)
12
+ - [Health & Diagnostics](#health--diagnostics)
13
+ - [Models](#models)
14
+
15
+ ## Configuration
16
+
17
+ ### `JetstreamBridge.configure`
18
+
19
+ Configure the library. Must be called before connecting.
20
+
21
+ ```ruby
22
+ JetstreamBridge.configure do |config|
23
+ # Required
24
+ config.app_name = "my_app"
25
+ config.destination_app = "other_app"
26
+
27
+ # Connection
28
+ config.nats_urls = "nats://localhost:4222" # or array of URLs
29
+ config.stream_name = "jetstream-bridge-stream"
30
+
31
+ # Features
32
+ config.use_outbox = true # Transactional publish (requires ActiveRecord)
33
+ config.use_inbox = true # Idempotent consume (requires ActiveRecord)
34
+ config.use_dlq = true # Dead letter queue for poison messages
35
+
36
+ # Consumer settings
37
+ config.durable_name = "#{app_name}-workers"
38
+ config.max_deliver = 5 # Max delivery attempts
39
+ config.ack_wait = "30s" # Time to wait for ACK
40
+ config.backoff = ["1s", "5s", "15s", "30s", "60s"]
41
+
42
+ # Provisioning
43
+ config.auto_provision = true # Auto-create stream/consumer on startup
44
+
45
+ # Connection behavior
46
+ config.lazy_connect = false # Set true to skip autostart
47
+ config.connect_retry_attempts = 3
48
+ config.connect_retry_delay = 1 # seconds
49
+ end
50
+ ```
51
+
52
+ ### `JetstreamBridge.config`
53
+
54
+ Returns the current configuration object (read-only).
55
+
56
+ ```ruby
57
+ stream_name = JetstreamBridge.config.stream_name
58
+ ```
59
+
60
+ ## Lifecycle Methods
61
+
62
+ ### `JetstreamBridge.startup!`
63
+
64
+ Explicitly start the connection and provision topology (if `auto_provision=true`).
65
+
66
+ ```ruby
67
+ JetstreamBridge.startup!
68
+ ```
69
+
70
+ **Note:** Rails applications auto-start after initialization. Non-Rails apps should call this manually or rely on auto-connect on first publish/subscribe.
71
+
72
+ ### `JetstreamBridge.shutdown!`
73
+
74
+ Gracefully close the NATS connection.
75
+
76
+ ```ruby
77
+ JetstreamBridge.shutdown!
78
+ ```
79
+
80
+ ### `JetstreamBridge.reset!`
81
+
82
+ Reset all internal state (for testing).
83
+
84
+ ```ruby
85
+ JetstreamBridge.reset!
86
+ ```
87
+
88
+ ## Publishing
89
+
90
+ ### `JetstreamBridge.publish`
91
+
92
+ Publish an event to the destination app.
93
+
94
+ ```ruby
95
+ JetstreamBridge.publish(
96
+ event_type: "user.created", # Required
97
+ resource_type: "user", # Required
98
+ resource_id: user.id, # Optional
99
+ payload: { id: user.id, email: user.email },
100
+ headers: { correlation_id: "..." }, # Optional
101
+ event_id: "custom-uuid" # Optional (auto-generated)
102
+ )
103
+ ```
104
+
105
+ **Returns:** `JetstreamBridge::PublishResult`
106
+
107
+ **With Outbox:**
108
+
109
+ ```ruby
110
+ # Transactional publish (commits with your DB transaction)
111
+ User.transaction do
112
+ user.save!
113
+ JetstreamBridge.publish(event_type: "user.created", resource_type: "user", payload: user)
114
+ end
115
+ ```
116
+
117
+ ### `JetstreamBridge.publish!`
118
+
119
+ Like `publish` but raises on error.
120
+
121
+ ```ruby
122
+ JetstreamBridge.publish!(event_type: "user.created", resource_type: "user", payload: data)
123
+ ```
124
+
125
+ **Raises:** `JetstreamBridge::PublishError` on failure
126
+
127
+ ### `JetstreamBridge.publish_batch`
128
+
129
+ Publish multiple events efficiently.
130
+
131
+ ```ruby
132
+ results = JetstreamBridge.publish_batch do |batch|
133
+ users.each do |user|
134
+ batch.publish(event_type: "user.created", resource_type: "user", payload: user)
135
+ end
136
+ end
137
+
138
+ puts "Published: #{results.success_count}, Failed: #{results.failure_count}"
139
+ ```
140
+
141
+ ## Consuming
142
+
143
+ ### `JetstreamBridge::Consumer.new`
144
+
145
+ Create a consumer to process incoming events.
146
+
147
+ ```ruby
148
+ consumer = JetstreamBridge::Consumer.new do |event|
149
+ # Process event
150
+ User.upsert({ id: event.payload["id"], email: event.payload["email"] })
151
+ end
152
+ ```
153
+
154
+ **Options:**
155
+
156
+ ```ruby
157
+ consumer = JetstreamBridge::Consumer.new(
158
+ batch_size: 10, # Process up to 10 messages at once
159
+ error_handler: ->(error, event) { logger.error(error) }
160
+ ) do |event|
161
+ # ...
162
+ end
163
+ ```
164
+
165
+ ### `Consumer#run!`
166
+
167
+ Start consuming messages (blocks until interrupted).
168
+
169
+ ```ruby
170
+ consumer.run!
171
+ ```
172
+
173
+ ### `Consumer#stop!`
174
+
175
+ Gracefully stop the consumer.
176
+
177
+ ```ruby
178
+ consumer.stop!
179
+ ```
180
+
181
+ ### Event Object
182
+
183
+ The event object passed to your handler:
184
+
185
+ ```ruby
186
+ event.event_id # => "evt_123"
187
+ event.event_type # => "user.created"
188
+ event.resource_type # => "user"
189
+ event.resource_id # => "456"
190
+ event.payload # => { "id" => 456, "email" => "..." }
191
+ event.headers # => { "correlation_id" => "..." }
192
+ event.subject # => "source_app.sync.my_app"
193
+ event.stream # => "jetstream-bridge-stream"
194
+ event.seq # => 123
195
+ event.deliveries # => 1
196
+ ```
197
+
198
+ ## Provisioning
199
+
200
+ ### `JetstreamBridge.provision!`
201
+
202
+ Manually provision stream and consumer.
203
+
204
+ ```ruby
205
+ # Provision both stream and consumer
206
+ JetstreamBridge.provision!
207
+
208
+ # Provision stream only
209
+ JetstreamBridge.provision!(provision_consumer: false)
210
+ ```
211
+
212
+ ### `JetstreamBridge::Provisioner`
213
+
214
+ Dedicated provisioning class for advanced use cases.
215
+
216
+ ```ruby
217
+ provisioner = JetstreamBridge::Provisioner.new
218
+
219
+ # Provision everything
220
+ provisioner.provision!
221
+
222
+ # Or separately
223
+ provisioner.provision_stream!
224
+ provisioner.provision_consumer!
225
+ ```
226
+
227
+ ## Health & Diagnostics
228
+
229
+ ### `JetstreamBridge.health_check`
230
+
231
+ Get comprehensive health status.
232
+
233
+ ```ruby
234
+ health = JetstreamBridge.health_check
235
+
236
+ health[:status] # => "healthy" | "unhealthy"
237
+ health[:connected] # => true/false
238
+ health[:stream_exists] # => true/false
239
+ health[:messages] # => 123
240
+ health[:consumers] # => 2
241
+ health[:nats_rtt_ms] # => 1.2
242
+ health[:version] # => "7.0.0"
243
+ ```
244
+
245
+ ### `JetstreamBridge.stream_info`
246
+
247
+ Get detailed stream information.
248
+
249
+ ```ruby
250
+ info = JetstreamBridge.stream_info
251
+
252
+ info[:name] # => "jetstream-bridge-stream"
253
+ info[:subjects] # => ["app1.sync.app2", ...]
254
+ info[:messages] # => 1000
255
+ info[:bytes] # => 204800
256
+ info[:first_seq] # => 1
257
+ info[:last_seq] # => 1000
258
+ info[:consumer_count] # => 2
259
+ ```
260
+
261
+ ### `JetstreamBridge.connection_info`
262
+
263
+ Get NATS connection details.
264
+
265
+ ```ruby
266
+ info = JetstreamBridge.connection_info
267
+
268
+ info[:connected] # => true
269
+ info[:servers] # => ["nats://localhost:4222"]
270
+ info[:connected_at] # => 2024-01-29 12:00:00 UTC
271
+ ```
272
+
273
+ ## Models
274
+
275
+ ### `JetstreamBridge::OutboxEvent`
276
+
277
+ ActiveRecord model for outbox events (when `use_outbox=true`).
278
+
279
+ ```ruby
280
+ # Create outbox event
281
+ event = JetstreamBridge::OutboxEvent.create!(
282
+ event_id: SecureRandom.uuid,
283
+ event_type: "user.created",
284
+ resource_type: "user",
285
+ resource_id: "123",
286
+ payload: { id: 123, email: "user@example.com" },
287
+ subject: "my_app.sync.other_app",
288
+ status: "pending"
289
+ )
290
+
291
+ # Query
292
+ JetstreamBridge::OutboxEvent.pending.limit(100)
293
+ JetstreamBridge::OutboxEvent.failed
294
+
295
+ # Mark as published
296
+ event.mark_published!
297
+
298
+ # Cleanup old events
299
+ JetstreamBridge::OutboxEvent.cleanup_published(older_than: 7.days)
300
+ ```
301
+
302
+ ### `JetstreamBridge::InboxEvent`
303
+
304
+ ActiveRecord model for inbox events (when `use_inbox=true`).
305
+
306
+ ```ruby
307
+ # Find by event_id
308
+ event = JetstreamBridge::InboxEvent.find_by(event_id: "evt_123")
309
+
310
+ # Query
311
+ JetstreamBridge::InboxEvent.received
312
+ JetstreamBridge::InboxEvent.processing
313
+ JetstreamBridge::InboxEvent.processed
314
+ JetstreamBridge::InboxEvent.failed
315
+ JetstreamBridge::InboxEvent.recent(100)
316
+
317
+ # Mark as processed
318
+ event.mark_processed!
319
+
320
+ # Mark as failed
321
+ event.mark_failed!("Error message")
322
+
323
+ # Cleanup old events
324
+ JetstreamBridge::InboxEvent.cleanup_processed(older_than: 30.days)
325
+
326
+ # Statistics
327
+ stats = JetstreamBridge::InboxEvent.processing_stats
328
+ stats[:total] # => 1000
329
+ stats[:processed] # => 950
330
+ stats[:failed] # => 30
331
+ stats[:pending] # => 20
332
+ ```
333
+
334
+ ## Error Handling
335
+
336
+ ### `JetstreamBridge::PublishError`
337
+
338
+ Raised by `publish!` when publishing fails.
339
+
340
+ ```ruby
341
+ begin
342
+ JetstreamBridge.publish!(event_type: "test", resource_type: "test", payload: {})
343
+ rescue JetstreamBridge::PublishError => e
344
+ logger.error("Publish failed: #{e.message}")
345
+ logger.error("Event ID: #{e.event_id}")
346
+ logger.error("Subject: #{e.subject}")
347
+ end
348
+ ```
349
+
350
+ ### Custom Error Handler
351
+
352
+ ```ruby
353
+ consumer = JetstreamBridge::Consumer.new(
354
+ error_handler: lambda { |error, event|
355
+ logger.error("Failed to process event #{event.event_id}: #{error.message}")
356
+ Sentry.capture_exception(error, extra: { event_id: event.event_id })
357
+ }
358
+ ) do |event|
359
+ # Process event
360
+ end
361
+ ```
362
+
363
+ ## Testing
364
+
365
+ ### Test Mode
366
+
367
+ Enable mock NATS for testing without infrastructure.
368
+
369
+ ```ruby
370
+ # RSpec
371
+ RSpec.configure do |config|
372
+ config.before(:each, :jetstream) do
373
+ JetstreamBridge::TestHelpers.enable_test_mode!
374
+ end
375
+
376
+ config.after(:each, :jetstream) do
377
+ JetstreamBridge::TestHelpers.reset_test_mode!
378
+ end
379
+ end
380
+
381
+ # Test
382
+ it "publishes events", :jetstream do
383
+ result = JetstreamBridge.publish(event_type: "test", resource_type: "test", payload: {})
384
+ expect(result).to be_success
385
+ end
386
+ ```
387
+
388
+ See [TESTING.md](TESTING.md) for comprehensive testing documentation.
389
+
390
+ ## See Also
391
+
392
+ - [Getting Started](GETTING_STARTED.md) - Setup and basic usage
393
+ - [Architecture](ARCHITECTURE.md) - Internal architecture and patterns
394
+ - [Production Guide](PRODUCTION.md) - Production deployment
395
+ - [Testing Guide](TESTING.md) - Testing with Mock NATS
data/docs/ARCHITECTURE.md CHANGED
@@ -331,7 +331,7 @@ Overlap detection ensures messages route to exactly one stream.
331
331
 
332
332
  ┌──────────────────────────────────────────────────────────────┐
333
333
  │ 3. [OPTIONAL] Outbox pattern │
334
- │ - OutboxRepository.persist_pre()
334
+ │ - OutboxRepository.record_publish_attempt()
335
335
  │ - State: "publishing" │
336
336
  │ - Database transaction commits │
337
337
  └──────────────────────┬───────────────────────────────────────┘
@@ -347,8 +347,8 @@ Overlap detection ensures messages route to exactly one stream.
347
347
 
348
348
  ┌──────────────────────────────────────────────────────────────┐
349
349
  │ 5. [OPTIONAL] Outbox update │
350
- │ - Success: OutboxRepository.persist_success()
351
- │ - Failure: OutboxRepository.persist_failure()
350
+ │ - Success: OutboxRepository.record_publish_success()
351
+ │ - Failure: OutboxRepository.record_publish_failure()
352
352
  └──────────────────────┬───────────────────────────────────────┘
353
353
 
354
354
 
@@ -437,7 +437,7 @@ Overlap detection ensures messages route to exactly one stream.
437
437
 
438
438
  ### Outbox Pattern (Publisher Side)
439
439
 
440
- **Purpose:** Guarantee at-least-once delivery by persisting events to database before publishing.
440
+ **Purpose:** Guarantee at-most-once delivery by persisting events to database before publishing.
441
441
 
442
442
  **Configuration:**
443
443
 
@@ -486,6 +486,50 @@ config.inbox_model = 'JetstreamBridge::InboxEvent'
486
486
  - `processed` - Successfully processed
487
487
  - `failed` - Failed processing
488
488
 
489
+ **Schema Requirements:**
490
+
491
+ Generate the inbox events table migration:
492
+
493
+ ```bash
494
+ rails generate jetstream_bridge:migration
495
+ rails db:migrate
496
+ ```
497
+
498
+ **Required Fields:**
499
+
500
+ | Field | Type | Nullable | Description |
501
+ | ----- | ---- | -------- | ----------- |
502
+ | `event_id` | string | NO | Unique event identifier for deduplication |
503
+ | `event_type` | string | NO | Type of event (e.g., 'created', 'updated') |
504
+ | `payload` | text | NO | Full event payload as JSON |
505
+ | `status` | string | NO | Processing status (received/processing/processed/failed) |
506
+ | `processing_attempts` | integer | NO | Number of processing attempts (default: 0) |
507
+ | `created_at` | timestamp | NO | When the record was created |
508
+ | `updated_at` | timestamp | NO | When the record was last updated |
509
+
510
+ **Optional Fields (useful for debugging and querying):**
511
+
512
+ | Field | Type | Nullable | Description |
513
+ | ----- | ---- | -------- | ----------- |
514
+ | `resource_type` | string | YES | Type of resource (e.g., 'organization', 'user') |
515
+ | `resource_id` | string | YES | ID of the resource being synced |
516
+ | `subject` | string | YES | NATS subject the message was received on |
517
+ | `headers` | jsonb | YES | NATS message headers |
518
+ | `stream` | string | YES | JetStream stream name |
519
+ | `stream_seq` | bigint | YES | Stream sequence number (fallback deduplication key) |
520
+ | `deliveries` | integer | YES | Number of delivery attempts from NATS |
521
+ | `error_message` | text | YES | Error message if processing failed |
522
+ | `received_at` | timestamp | YES | When the event was first received |
523
+ | `processed_at` | timestamp | YES | When the event was successfully processed |
524
+ | `failed_at` | timestamp | YES | When the event failed processing |
525
+
526
+ **Indexes:**
527
+
528
+ - `event_id` - Unique index for fast deduplication
529
+ - `status` - Index for querying by processing status
530
+ - `created_at` - Index for time-based queries
531
+ - `(stream, stream_seq)` - Unique composite index for fallback deduplication
532
+
489
533
  **Deduplication:**
490
534
 
491
535
  - Uses `event_id` for primary deduplication
@@ -508,6 +552,24 @@ inbox.processed_at # => 2024-01-01 00:00:00
508
552
  # Skip processing, already done
509
553
  ```
510
554
 
555
+ **Field Population:**
556
+
557
+ The InboxRepository automatically extracts and sets fields from the message payload:
558
+
559
+ ```ruby
560
+ # Extracted from message body
561
+ event_type: msg.body['type'] || msg.body['event_type']
562
+ resource_type: msg.body['resource_type']
563
+ resource_id: msg.body['resource_id']
564
+
565
+ # NATS metadata
566
+ subject: msg.subject
567
+ headers: msg.headers
568
+ stream: msg.stream
569
+ stream_seq: msg.seq
570
+ deliveries: msg.deliveries
571
+ ```
572
+
511
573
  ### Dead Letter Queue (DLQ)
512
574
 
513
575
  **Purpose:** Route unrecoverable messages to separate subject for manual intervention.
@@ -6,7 +6,7 @@ This guide covers installation, Rails setup, configuration, and basic publish/co
6
6
 
7
7
  ```ruby
8
8
  # Gemfile
9
- gem "jetstream_bridge", "~> 5.0"
9
+ gem "jetstream_bridge", "~> 7.0"
10
10
  ```
11
11
 
12
12
  ```bash
@@ -33,6 +33,64 @@ Generators create:
33
33
  - `db/migrate/*_create_jetstream_inbox_events.rb`
34
34
  - `app/controllers/jetstream_health_controller.rb` (health check)
35
35
 
36
+ ### Database Migrations
37
+
38
+ The generated migrations create tables for inbox and outbox patterns:
39
+
40
+ **Outbox Events** (`jetstream_bridge_outbox_events`):
41
+
42
+ ```ruby
43
+ create_table :jetstream_bridge_outbox_events do |t|
44
+ t.string :event_id, null: false, index: { unique: true }
45
+ t.string :event_type, null: false
46
+ t.string :resource_type, null: false
47
+ t.string :resource_id
48
+ t.jsonb :payload, default: {}, null: false
49
+ t.string :subject, null: false
50
+ t.jsonb :headers, default: {}
51
+ t.string :status, default: "pending", null: false, index: true
52
+ t.text :error_message
53
+ t.integer :publish_attempts, default: 0
54
+ t.datetime :published_at, index: true
55
+ t.datetime :failed_at
56
+ t.timestamps
57
+ end
58
+ ```
59
+
60
+ **Inbox Events** (`jetstream_bridge_inbox_events`):
61
+
62
+ ```ruby
63
+ create_table :jetstream_bridge_inbox_events do |t|
64
+ t.string :event_id, null: false, index: { unique: true }
65
+ t.string :event_type
66
+ t.string :resource_type
67
+ t.string :resource_id
68
+ t.jsonb :payload, default: {}, null: false
69
+ t.string :subject, null: false
70
+ t.jsonb :headers, default: {}
71
+ t.string :stream
72
+ t.bigint :stream_seq, index: true
73
+ t.integer :deliveries, default: 0
74
+ t.string :status, default: "received", null: false, index: true
75
+ t.text :error_message
76
+ t.integer :processing_attempts, default: 0
77
+ t.datetime :received_at, index: true
78
+ t.datetime :processed_at, index: true
79
+ t.datetime :failed_at
80
+ t.timestamps
81
+ end
82
+ ```
83
+
84
+ **Key Fields:**
85
+
86
+ - `event_id`: Unique identifier for deduplication
87
+ - `event_type`: Type of event (e.g., "user.created")
88
+ - `resource_type`/`resource_id`: Entity being synchronized
89
+ - `payload`: Event data (JSON)
90
+ - `status`: Event lifecycle state (pending/processing/processed/failed)
91
+ - `stream_seq`: NATS JetStream sequence number (inbox only)
92
+ - `deliveries`: Delivery attempt count (inbox only)
93
+
36
94
  ## Configuration
37
95
 
38
96
  ```ruby
@@ -61,6 +119,19 @@ end
61
119
 
62
120
  Rails autostart runs after initialization (including in console). You can opt out for rake tasks or other tooling with `config.lazy_connect = true` or `JETSTREAM_BRIDGE_DISABLE_AUTOSTART=1`; it will then connect on first publish/subscribe.
63
121
 
122
+ ### Push consumer mode (restricted credentials)
123
+
124
+ If your NATS user cannot publish to `$JS.API.*`, switch to push consumers and pre-create the durable consumer:
125
+
126
+ ```ruby
127
+ config.consumer_mode = :push
128
+ # Optional: override delivery subject (defaults to "#{config.destination_subject}.worker")
129
+ # config.delivery_subject = "worker.sync.my_app.worker"
130
+ config.auto_provision = false # pre-create stream/consumer with admin creds
131
+ ```
132
+
133
+ Provision the consumer with NATS CLI (`--deliver <subject>`) or `bundle exec rake jetstream_bridge:provision` using admin credentials. See [docs/RESTRICTED_PERMISSIONS.md](RESTRICTED_PERMISSIONS.md) for the full least-privilege guide.
134
+
64
135
  ## Publish
65
136
 
66
137
  ```ruby
data/docs/PRODUCTION.md CHANGED
@@ -204,7 +204,7 @@ end
204
204
  "use_inbox": true,
205
205
  "use_dlq": true
206
206
  },
207
- "version": "4.0.3"
207
+ "version": "7.0.0"
208
208
  }
209
209
  ```
210
210