jetstream_bridge 2.10.0 → 3.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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +164 -0
  3. data/LICENSE +21 -0
  4. data/README.md +379 -0
  5. data/lib/generators/jetstream_bridge/health_check/health_check_generator.rb +65 -0
  6. data/lib/generators/jetstream_bridge/health_check/templates/health_controller.rb +38 -0
  7. data/lib/generators/jetstream_bridge/initializer/templates/jetstream_bridge.rb +58 -13
  8. data/lib/jetstream_bridge/consumer/consumer.rb +43 -0
  9. data/lib/jetstream_bridge/consumer/dlq_publisher.rb +4 -1
  10. data/lib/jetstream_bridge/consumer/inbox/inbox_message.rb +3 -1
  11. data/lib/jetstream_bridge/consumer/inbox/inbox_repository.rb +37 -31
  12. data/lib/jetstream_bridge/consumer/message_processor.rb +65 -31
  13. data/lib/jetstream_bridge/core/config.rb +35 -0
  14. data/lib/jetstream_bridge/core/connection.rb +80 -3
  15. data/lib/jetstream_bridge/core/connection_factory.rb +102 -0
  16. data/lib/jetstream_bridge/core/debug_helper.rb +107 -0
  17. data/lib/jetstream_bridge/core/duration.rb +8 -1
  18. data/lib/jetstream_bridge/core/retry_strategy.rb +135 -0
  19. data/lib/jetstream_bridge/errors.rb +39 -0
  20. data/lib/jetstream_bridge/models/event_envelope.rb +133 -0
  21. data/lib/jetstream_bridge/models/subject.rb +94 -0
  22. data/lib/jetstream_bridge/publisher/outbox_repository.rb +47 -28
  23. data/lib/jetstream_bridge/publisher/publisher.rb +12 -35
  24. data/lib/jetstream_bridge/railtie.rb +35 -1
  25. data/lib/jetstream_bridge/tasks/install.rake +99 -0
  26. data/lib/jetstream_bridge/topology/overlap_guard.rb +15 -1
  27. data/lib/jetstream_bridge/topology/stream.rb +15 -5
  28. data/lib/jetstream_bridge/version.rb +1 -1
  29. data/lib/jetstream_bridge.rb +65 -0
  30. metadata +51 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 15fe547a615edbbd3bbeafb5bf5193bfdc4f1bd9911d7497803cf5d331ced36b
4
- data.tar.gz: 5c0b98f2cbbff19762e98abcb39fa7c5f006cbfcd951295020adbcc08da80d49
3
+ metadata.gz: d47bc85ddad1450a71960670d94969ec5f6416dc98c07c8409e4d7243ec92f3e
4
+ data.tar.gz: b9130b48f7129d6c61c9f2cddc61a2e292c906c282925a3a550f79c24cc7eff4
5
5
  SHA512:
6
- metadata.gz: 4135f2c9287c5ddc0716f9eb7f3bcfeab104072c0ed59bdf41ff7a3df07dd5254240147e6a176d80f3a93198240ee633ff24ed742caf6142c859d6f259110c56
7
- data.tar.gz: d341432852e94c1983128f5d0b0094e812c867f7133c609f609fb4606a4945ee2e5129cc20a24ef166e9e36dbd73a06e6bcb3b3a6d8c19752906a26756883345
6
+ metadata.gz: 430fa83af6f8b1d9eb22fdf8edfbd2787c4a0609c6abf6626239a05132f1732bf9ecd8f0e6ad92cab7e53ffd234d0c3df5e626f06c332680a99b84849bdf7bc0
7
+ data.tar.gz: b17ccb8f7bf525aa5f21917c87932115a11ccb4058d79c81af741ee985203c97d326b3c7cd8960819c4a93f5d088a8d0b39e1f9bbef108e2619e7114e621f74a
data/CHANGELOG.md ADDED
@@ -0,0 +1,164 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [3.0.0] - 2025-11-23
9
+
10
+ ### Added
11
+
12
+ #### Production-Ready Features
13
+
14
+ - **Health checks** - Comprehensive health check API via `JetstreamBridge.health_check`
15
+ - NATS connection status monitoring
16
+ - Stream existence and subject verification
17
+ - Configuration validation
18
+ - Returns structured health data for monitoring systems
19
+ - **Health check generator** - Rails generator for creating health check endpoints
20
+ - `rails g jetstream_bridge:health_check` creates controller and route
21
+ - Automatic route injection into `config/routes.rb`
22
+ - Returns appropriate HTTP status codes (200/503)
23
+ - **Auto-reconnection** - Automatic recovery from connection failures
24
+ - Exponential backoff retry strategy
25
+ - Configurable retry limits and delays
26
+ - Connection state tracking with timestamps
27
+ - **Connection factory** - Centralized connection management
28
+ - Singleton pattern for connection handling
29
+ - Thread-safe connection access
30
+ - Public `connected?` and `connected_at` accessors
31
+
32
+ #### Error Handling
33
+
34
+ - **Comprehensive error hierarchy** - Well-organized exception classes
35
+ - `ConfigurationError` - Base for configuration issues
36
+ - `ConnectionError` - Base for connection problems
37
+ - `PublishError` - Base for publishing failures
38
+ - `ConsumerError` - Base for consumption issues
39
+ - `TopologyError` - Base for stream/subject topology errors
40
+ - `DlqError` - Base for dead-letter queue operations
41
+ - All inherit from `JetstreamBridge::Error`
42
+
43
+ #### Developer Experience
44
+
45
+ - **Debug helper** - Comprehensive debugging utility via `JetstreamBridge::DebugHelper`
46
+ - Configuration dump
47
+ - Connection status
48
+ - Stream information
49
+ - Subject validation
50
+ - Model availability checks
51
+ - **Rake tasks** - CLI tools for operations
52
+ - `rake jetstream_bridge:health` - Check health and connection status
53
+ - `rake jetstream_bridge:validate` - Validate configuration
54
+ - `rake jetstream_bridge:test_connection` - Test NATS connection
55
+ - `rake jetstream_bridge:debug` - Show comprehensive debug information
56
+ - **Value objects** - Type-safe domain models
57
+ - `EventEnvelope` - Structured event representation
58
+ - `Subject` - Subject pattern validation and parsing
59
+
60
+ #### Testing & Quality
61
+
62
+ - **Comprehensive test suite** - 248 RSpec tests covering all core functionality
63
+ - Configuration validation specs
64
+ - Connection factory specs with edge cases (48 new tests added)
65
+ - Server URL normalization (single, comma-separated, arrays, whitespace)
66
+ - Authentication options (user, pass, token)
67
+ - NATS URL validation (nil, empty, whitespace-only)
68
+ - JetStream context creation and nc accessor
69
+ - Event envelope specs with full coverage (18 new tests added)
70
+ - Initialization edge cases (trace ID, Time objects, resource ID extraction)
71
+ - Hash serialization and deserialization
72
+ - Deep immutability validation
73
+ - Equality and hashing behavior
74
+ - Error handling for invalid timestamps
75
+ - Retry strategy specs
76
+ - Model specs
77
+ - Error hierarchy specs
78
+ - **Subject validation** - Prevents NATS wildcards in configuration
79
+ - Validates `env`, `app_name`, and `destination_app` don't contain `.`, `*`, or `>`
80
+ - Clear error messages for invalid subjects
81
+ - **Bug fixes in implementation** - Fixed `EventEnvelope.from_h` to properly handle deserialization
82
+ - Removed invalid `schema_version` parameter
83
+ - Added missing `resource_id` parameter
84
+
85
+ ### Changed
86
+
87
+ #### Architecture Improvements
88
+
89
+ - **Consolidated configuration** - Streamlined `Config` class
90
+ - Removed redundant configuration classes
91
+ - Centralized validation logic
92
+ - Better default values
93
+ - **Enhanced generator templates** - Improved initializer template
94
+ - Better documentation and comments
95
+ - Organized sections (Connection, Consumer, Reliability, Models, Logging)
96
+ - Clear indication of required vs optional settings
97
+ - **Simplified consumer initialization** - Cleaner API
98
+ - Sensible defaults for `durable_name` and `batch_size`
99
+ - Removed unnecessary configuration classes
100
+ - **Better logging** - Configurable logger with sensible defaults
101
+ - Respects `config.logger` setting
102
+ - Falls back to `Rails.logger` in Rails apps
103
+ - Uses `STDOUT` logger otherwise
104
+
105
+ #### Bug Fixes
106
+
107
+ - **Fixed duration parsing** - Correct integer conversion for duration strings
108
+ - Handles edge cases properly
109
+ - Added comprehensive tests
110
+ - **Fixed subject format** - Corrected DLQ subject pattern
111
+ - Changed from `{env}.data.sync.dlq` to `{env}.sync.dlq`
112
+ - Updated all documentation
113
+ - **Repository improvements**
114
+ - Transaction safety for all database operations
115
+ - Pessimistic locking for outbox operations
116
+ - Race condition protection
117
+ - Better error handling and logging
118
+
119
+ ### Refactored
120
+
121
+ - **Consumer architecture** - Consolidated consumer support classes
122
+ - Merged related functionality
123
+ - Reduced file count
124
+ - Clearer separation of concerns
125
+ - **Model handling** - Streamlined inbox/outbox models
126
+ - Graceful column detection without connection boot
127
+ - Better validation guards
128
+ - Simplified attribute handling
129
+ - **JSON handling** - Switched to Oj for performance
130
+ - Faster JSON parsing/serialization
131
+ - Consistent JSON handling across the gem
132
+ - **Topology management** - Enhanced stream and subject handling
133
+ - Improved overlap detection
134
+ - Better error messages
135
+ - More robust subject matching
136
+
137
+ ### Documentation
138
+
139
+ - **Updated README** - Comprehensive documentation updates
140
+ - Added section for Rails generators and rake tasks
141
+ - Documented all new health check features
142
+ - Fixed incorrect subject patterns
143
+ - Added operations guide
144
+ - Improved getting started section
145
+ - **Enhanced code comments** - Better inline documentation
146
+ - YARD-style documentation for public APIs
147
+ - Clear explanations of complex logic
148
+ - Usage examples in comments
149
+ - **Generator improvements** - Better user guidance
150
+ - Clear success messages
151
+ - Usage instructions after generation
152
+ - Example configurations
153
+
154
+ ### Internal
155
+
156
+ - **Removed deprecated code**
157
+ - Cleaned up unused classes and methods
158
+ - Removed `BackoffStrategy` (consolidated into retry strategy)
159
+ - Removed `ConsumerConfig` (merged into main config)
160
+ - Removed `MessageContext` (functionality integrated)
161
+
162
+ ## [2.10.0] and earlier
163
+
164
+ See git history for changes in earlier versions.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mike Attara
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,379 @@
1
+ <p align="center">
2
+ <img src="logo.svg" alt="JetStream Bridge Logo" width="200"/>
3
+ </p>
4
+
5
+ <h1 align="center">JetStream Bridge</h1>
6
+
7
+ <p align="center">
8
+ <strong>Production-safe realtime data bridge</strong> between systems using <strong>NATS JetStream</strong>
9
+ </p>
10
+
11
+ <p align="center">
12
+ Includes durable consumers, backpressure, retries, <strong>DLQ</strong>, optional <strong>Inbox/Outbox</strong>, and <strong>overlap-safe stream provisioning</strong>
13
+ </p>
14
+
15
+ <p align="center">
16
+ <a href="#-features">Features</a> •
17
+ <a href="#-install">Install</a> •
18
+ <a href="#-getting-started">Getting Started</a> •
19
+ <a href="#-operations-guide">Operations</a>
20
+ </p>
21
+
22
+ ---
23
+
24
+ ## ✨ Features
25
+
26
+ ### Core Capabilities
27
+
28
+ * 🔌 Simple **Publisher** and **Consumer** interfaces
29
+ * 🛡 **Outbox** (reliable send) & **Inbox** (idempotent receive), opt-in
30
+ * 🧨 **DLQ** for poison messages
31
+ * ⚙️ Durable `pull_subscribe` with backoff & `max_deliver`
32
+ * 🎯 Clear **source/destination** subject conventions
33
+ * 🧱 **Overlap-safe stream ensure** (prevents "subjects overlap" BadRequest)
34
+ * 🚂 **Rails generators** for initializer & migrations, plus an install **rake task**
35
+ * ⚡️ **Eager-loaded models** via Railtie (production)
36
+ * 📊 Configurable logging with sensible defaults
37
+
38
+ ### Production-Ready Features (v2.10+)
39
+
40
+ * 🏥 **Health checks** - Monitor NATS connection and stream status
41
+ * 🔄 **Auto-reconnection** - Automatic recovery from connection failures
42
+ * 🔒 **Race condition protection** - Pessimistic locking for outbox operations
43
+ * 🛡️ **Transaction safety** - All database operations wrapped in transactions
44
+ * 🎯 **Subject validation** - Prevents NATS wildcards in configuration
45
+ * 🚦 **Graceful shutdown** - Signal handlers and message draining
46
+ * 📈 **Retry strategies** - Pluggable exponential/linear backoff algorithms
47
+ * 🎨 **Value objects** - Type-safe domain models for events and subjects
48
+ * 🏗️ **SOLID architecture** - Clean separation of concerns and dependency injection
49
+
50
+ ---
51
+
52
+ ## 📦 Install
53
+
54
+ ```ruby
55
+ # Gemfile
56
+ gem "jetstream_bridge", "~> 2.10"
57
+ ```
58
+
59
+ ```bash
60
+ bundle install
61
+ ```
62
+
63
+ ---
64
+
65
+ ## 🧰 Rails Generators & Rake Tasks
66
+
67
+ ### Installation
68
+
69
+ From your Rails app:
70
+
71
+ ```bash
72
+ # Create initializer + migrations
73
+ bin/rails g jetstream_bridge:install
74
+
75
+ # Or run them separately:
76
+ bin/rails g jetstream_bridge:initializer
77
+ bin/rails g jetstream_bridge:migrations
78
+
79
+ # Create health check endpoint
80
+ bin/rails g jetstream_bridge:health_check
81
+ ```
82
+
83
+ Then:
84
+
85
+ ```bash
86
+ bin/rails db:migrate
87
+ ```
88
+
89
+ > The generators create:
90
+ >
91
+ > * `config/initializers/jetstream_bridge.rb`
92
+ > * `db/migrate/*_create_jetstream_outbox_events.rb`
93
+ > * `db/migrate/*_create_jetstream_inbox_events.rb`
94
+ > * `app/controllers/jetstream_health_controller.rb` (if health_check generator used)
95
+
96
+ ### Rake Tasks
97
+
98
+ ```bash
99
+ # Check health and connection status
100
+ bin/rake jetstream_bridge:health
101
+
102
+ # Validate configuration
103
+ bin/rake jetstream_bridge:validate
104
+
105
+ # Test NATS connection
106
+ bin/rake jetstream_bridge:test_connection
107
+
108
+ # Show comprehensive debug information
109
+ bin/rake jetstream_bridge:debug
110
+ ```
111
+
112
+ ---
113
+
114
+ ## 🔧 Configure (Rails)
115
+
116
+ ```ruby
117
+ # config/initializers/jetstream_bridge.rb
118
+ JetstreamBridge.configure do |config|
119
+ # NATS connection
120
+ config.nats_urls = ENV.fetch("NATS_URLS", "nats://localhost:4222")
121
+ config.env = ENV.fetch("NATS_ENV", "development")
122
+ config.app_name = ENV.fetch("APP_NAME", "app")
123
+ config.destination_app = ENV["DESTINATION_APP"] # required
124
+
125
+ # Consumer tuning
126
+ config.max_deliver = 5
127
+ config.ack_wait = "30s"
128
+ config.backoff = %w[1s 5s 15s 30s 60s]
129
+
130
+ # Reliability features (opt-in)
131
+ config.use_outbox = true
132
+ config.use_inbox = true
133
+ config.use_dlq = true
134
+
135
+ # Models (override if you use custom AR classes/table names)
136
+ config.outbox_model = "JetstreamBridge::OutboxEvent"
137
+ config.inbox_model = "JetstreamBridge::InboxEvent"
138
+
139
+ # Logging
140
+ # config.logger = Rails.logger
141
+ end
142
+ ```
143
+
144
+ > **Defaults:**
145
+ >
146
+ > * `stream_name` → `#{env}-jetstream-bridge-stream`
147
+ > * `dlq_subject` → `#{env}.sync.dlq`
148
+
149
+ ### Logging
150
+
151
+ JetstreamBridge logs through `config.logger` when set, falling back to `Rails.logger` or STDOUT. Provide any `Logger`-compatible instance in the initializer to integrate with your application's logging setup.
152
+
153
+ ---
154
+
155
+ ## 📡 Subject Conventions
156
+
157
+ | Direction | Subject Pattern |
158
+ |---------------|---------------------------|
159
+ | **Publish** | `{env}.{app}.sync.{dest}` |
160
+ | **Subscribe** | `{env}.{dest}.sync.{app}` |
161
+ | **DLQ** | `{env}.sync.dlq` |
162
+
163
+ * `{app}`: `app_name`
164
+ * `{dest}`: `destination_app`
165
+ * `{env}`: `env`
166
+
167
+ ---
168
+
169
+ ## 🧱 Stream Topology (auto-ensure and overlap-safe)
170
+
171
+ On first connection, Jetstream Bridge **ensures** a single stream exists for your `env` and that it covers:
172
+
173
+ * `source_subject` (`{env}.{app}.sync.{dest}`)
174
+ * `destination_subject` (`{env}.{dest}.sync.{app}`)
175
+ * `dlq_subject` (if enabled)
176
+
177
+ It’s **overlap-safe**:
178
+
179
+ * Skips adding subjects already covered by existing wildcards
180
+ * Pre-filters subjects owned by other streams to avoid `BadRequest: subjects overlap with an existing stream`
181
+ * Retries once on concurrent races, then logs and continues safely
182
+
183
+ ---
184
+
185
+ ## 🗃 Database Setup (Inbox / Outbox)
186
+
187
+ Inbox/Outbox are **optional**. The library detects columns at runtime and only sets what exists, so you can start minimal and evolve later.
188
+
189
+ ### Generator-created tables (recommended)
190
+
191
+ ```ruby
192
+ # jetstream_outbox_events
193
+ create_table :jetstream_outbox_events do |t|
194
+ t.string :event_id, null: false
195
+ t.string :subject, null: false
196
+ t.jsonb :payload, null: false, default: {}
197
+ t.jsonb :headers, null: false, default: {}
198
+ t.string :status, null: false, default: "pending" # pending|publishing|sent|failed
199
+ t.integer :attempts, null: false, default: 0
200
+ t.text :last_error
201
+ t.datetime :enqueued_at
202
+ t.datetime :sent_at
203
+ t.timestamps
204
+ end
205
+ add_index :jetstream_outbox_events, :event_id, unique: true
206
+ add_index :jetstream_outbox_events, :status
207
+
208
+ # jetstream_inbox_events
209
+ create_table :jetstream_inbox_events do |t|
210
+ t.string :event_id # preferred dedupe key
211
+ t.string :subject, null: false
212
+ t.jsonb :payload, null: false, default: {}
213
+ t.jsonb :headers, null: false, default: {}
214
+ t.string :stream
215
+ t.bigint :stream_seq
216
+ t.integer :deliveries
217
+ t.string :status, null: false, default: "received" # received|processing|processed|failed
218
+ t.text :last_error
219
+ t.datetime :received_at
220
+ t.datetime :processed_at
221
+ t.timestamps
222
+ end
223
+ add_index :jetstream_inbox_events, :event_id, unique: true, where: 'event_id IS NOT NULL'
224
+ add_index :jetstream_inbox_events, [:stream, :stream_seq], unique: true, where: 'stream IS NOT NULL AND stream_seq IS NOT NULL'
225
+ add_index :jetstream_inbox_events, :status
226
+ ```
227
+
228
+ > Already have different table names? Point the config to your AR classes via `config.outbox_model` / `config.inbox_model`.
229
+
230
+ ---
231
+
232
+ ## 📤 Publish Events
233
+
234
+ ```ruby
235
+ publisher = JetstreamBridge::Publisher.new
236
+ publisher.publish(
237
+ resource_type: "user",
238
+ event_type: "created",
239
+ payload: { id: "01H...", name: "Ada" }, # resource_id inferred from payload[:id] / payload["id"]
240
+ # optional:
241
+ # event_id: "uuid-or-ulid",
242
+ # trace_id: "hex",
243
+ # occurred_at: Time.now.utc
244
+ )
245
+ ```
246
+
247
+ If **Outbox** is enabled, the publish call:
248
+
249
+ * Upserts an outbox row by `event_id`
250
+ * Publishes with `nats-msg-id` (idempotent)
251
+ * Marks status `sent` or records `failed` with `last_error`
252
+
253
+ ---
254
+
255
+ ## 📥 Consume Events
256
+
257
+ ```ruby
258
+ JetstreamBridge::Consumer.new do |event, subject, deliveries|
259
+ # Your idempotent domain logic here
260
+ # `event` is the parsed envelope hash
261
+ UserCreatedHandler.call(event["payload"])
262
+ end.run!
263
+ ```
264
+
265
+ `durable_name` and `batch_size` default to the configured values and can be
266
+ overridden if needed:
267
+
268
+ ```ruby
269
+ JetstreamBridge::Consumer.new(durable_name: 'my-durable', batch_size: 10) do |event, subject, deliveries|
270
+ # ...
271
+ end.run!
272
+ ```
273
+
274
+ If **Inbox** is enabled, the consumer:
275
+
276
+ * Dedupes by `event_id` (falls back to stream sequence if needed)
277
+ * Records processing state, errors, and timestamps
278
+ * Skips already-processed messages (acks immediately)
279
+
280
+ ---
281
+
282
+ ## 📬 Envelope Format
283
+
284
+ ```json
285
+ {
286
+ "event_id": "01H1234567890ABCDEF",
287
+ "schema_version": 1,
288
+ "event_type": "created",
289
+ "producer": "myapp",
290
+ "resource_type": "user",
291
+ "resource_id": "01H1234567890ABCDEF",
292
+ "occurred_at": "2025-08-13T21:00:00Z",
293
+ "trace_id": "abc123",
294
+ "payload": { "id": "01H...", "name": "Ada" }
295
+ }
296
+ ```
297
+
298
+ * `resource_id` is inferred from `payload.id` when publishing.
299
+
300
+ ---
301
+
302
+ ## 🧨 Dead-Letter Queue (DLQ)
303
+
304
+ When enabled, the topology ensures the DLQ subject exists:
305
+ **`{env}.sync.dlq`**
306
+
307
+ You may run a separate process to subscribe and triage messages that exceed `max_deliver` or are NAK'ed to the DLQ.
308
+
309
+ ---
310
+
311
+ ## 🛠 Operations Guide
312
+
313
+ ### Monitoring
314
+
315
+ * **Consumer lag**: `nats consumer info <stream> <durable>`
316
+ * **DLQ volume**: subscribe/metrics on `{env}.sync.dlq`
317
+ * **Outbox backlog**: alert on `jetstream_outbox_events` with `status != 'sent'` and growing count
318
+
319
+ ### Scaling
320
+
321
+ * Run consumers in **separate processes/containers**
322
+ * Scale consumers independently of web
323
+ * Tune `batch_size`, `ack_wait`, `max_deliver`, and `backoff`
324
+
325
+ ### Health Checks
326
+
327
+ The gem provides built-in health check functionality for monitoring:
328
+
329
+ ```ruby
330
+ # Get comprehensive health status
331
+ health = JetstreamBridge.health_check
332
+ # => {
333
+ # healthy: true,
334
+ # nats_connected: true,
335
+ # connected_at: "2025-11-22T20:00:00Z",
336
+ # stream: { exists: true, name: "...", ... },
337
+ # config: { env: "production", ... },
338
+ # version: "2.10.0"
339
+ # }
340
+
341
+ # Force-connect & ensure topology at boot or in a check
342
+ JetstreamBridge.ensure_topology!
343
+
344
+ # Debug helper for troubleshooting
345
+ JetstreamBridge::DebugHelper.debug_info
346
+ ```
347
+
348
+ ### When to Use
349
+
350
+ * **Inbox**: you need idempotent processing and replay safety
351
+ * **Outbox**: you want “DB commit ⇒ event published (or recorded for retry)” guarantees
352
+
353
+ ---
354
+
355
+ ## 🧩 Troubleshooting
356
+
357
+ * **`subjects overlap with an existing stream`**
358
+ The library pre-filters overlapping subjects and retries once. If another team owns a broad wildcard (e.g., `env.data.sync.>`), coordinate subject boundaries.
359
+
360
+ * **Consumer exists with mismatched filter**
361
+ The library detects and recreates the durable with the desired filter subject.
362
+
363
+ * **Repeated redeliveries**
364
+ Increase `ack_wait`, review handler acks/NACKs, or move poison messages to DLQ.
365
+
366
+ ---
367
+
368
+ ## 🚀 Getting Started
369
+
370
+ 1. Add the gem & run `bundle install`
371
+ 2. `bin/rails g jetstream_bridge:install`
372
+ 3. `bin/rails db:migrate`
373
+ 4. Start publishing/consuming!
374
+
375
+ ---
376
+
377
+ ## 📄 License
378
+
379
+ [MIT License](LICENSE)
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ module JetstreamBridge
6
+ module Generators
7
+ class HealthCheckGenerator < Rails::Generators::Base
8
+ source_root File.expand_path('templates', __dir__)
9
+ desc 'Creates a health check endpoint for JetStream Bridge monitoring'
10
+
11
+ def create_controller
12
+ template 'health_controller.rb', 'app/controllers/jetstream_health_controller.rb'
13
+ end
14
+
15
+ def add_route
16
+ route_content = " # JetStream Bridge health check endpoint\n" \
17
+ " get '/health/jetstream', to: 'jetstream_health#show'"
18
+
19
+ if File.exist?('config/routes.rb')
20
+ inject_into_file 'config/routes.rb', after: /Rails\.application\.routes\.draw do\n/ do
21
+ "#{route_content}\n"
22
+ end
23
+ say 'Added health check route to config/routes.rb', :green
24
+ else
25
+ say 'Could not find config/routes.rb - please add route manually:', :yellow
26
+ say route_content, :yellow
27
+ end
28
+ end
29
+
30
+ def show_usage
31
+ say "\n" + '=' * 70, :green
32
+ say 'Health Check Endpoint Created!', :green
33
+ say '=' * 70, :green
34
+ say "\nThe health check endpoint is now available at:"
35
+ say " GET /health/jetstream", :cyan
36
+ say "\nExample response:"
37
+ say <<~EXAMPLE, :white
38
+ {
39
+ "healthy": true,
40
+ "nats_connected": true,
41
+ "connected_at": "2025-11-22T20:00:00Z",
42
+ "stream": {
43
+ "exists": true,
44
+ "name": "development-jetstream-bridge-stream",
45
+ "subjects": ["dev.app1.sync.app2"],
46
+ "messages": 42
47
+ },
48
+ "config": {
49
+ "env": "development",
50
+ "app_name": "my_app",
51
+ "destination_app": "other_app"
52
+ },
53
+ "version": "2.10.0"
54
+ }
55
+ EXAMPLE
56
+ say "\nUse this endpoint for:"
57
+ say " • Kubernetes liveness/readiness probes", :white
58
+ say " • Docker health checks", :white
59
+ say " • Monitoring and alerting", :white
60
+ say " • Load balancer health checks", :white
61
+ say '=' * 70, :green
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Health check controller for JetStream Bridge monitoring
4
+ #
5
+ # This controller provides a health check endpoint for monitoring the JetStream
6
+ # connection status. Use it for Kubernetes liveness/readiness probes, Docker
7
+ # health checks, or load balancer health checks.
8
+ #
9
+ # Example usage:
10
+ # GET /health/jetstream
11
+ #
12
+ # Returns:
13
+ # 200 OK - when healthy
14
+ # 503 Service Unavailable - when unhealthy
15
+ class JetstreamHealthController < ActionController::API
16
+ # GET /health/jetstream
17
+ #
18
+ # Returns comprehensive health status including:
19
+ # - NATS connection status
20
+ # - JetStream stream information
21
+ # - Configuration details
22
+ # - Gem version
23
+ def show
24
+ health = JetstreamBridge.health_check
25
+
26
+ if health[:healthy]
27
+ render json: health, status: :ok
28
+ else
29
+ render json: health, status: :service_unavailable
30
+ end
31
+ rescue StandardError => e
32
+ # Ensure we always return a valid JSON response
33
+ render json: {
34
+ healthy: false,
35
+ error: "#{e.class}: #{e.message}"
36
+ }, status: :service_unavailable
37
+ end
38
+ end