jetstream_bridge 5.1.0 → 7.0.1
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/CHANGELOG.md +37 -0
- data/README.md +6 -3
- data/docs/API.md +395 -0
- data/docs/ARCHITECTURE.md +123 -171
- data/docs/GETTING_STARTED.md +72 -1
- data/docs/PRODUCTION.md +10 -3
- data/docs/RESTRICTED_PERMISSIONS.md +7 -14
- data/docs/TESTING.md +3 -3
- data/lib/generators/jetstream_bridge/migrations/templates/create_jetstream_inbox_events.rb.erb +29 -13
- data/lib/jetstream_bridge/config_helpers/lifecycle.rb +34 -0
- data/lib/jetstream_bridge/config_helpers.rb +118 -0
- data/lib/jetstream_bridge/consumer/consumer.rb +131 -41
- data/lib/jetstream_bridge/consumer/consumer_state.rb +58 -0
- data/lib/jetstream_bridge/consumer/inbox/inbox_repository.rb +12 -2
- data/lib/jetstream_bridge/consumer/pull_subscription_builder.rb +6 -6
- data/lib/jetstream_bridge/consumer/subscription_manager.rb +72 -110
- data/lib/jetstream_bridge/core/config.rb +31 -0
- data/lib/jetstream_bridge/core/connection.rb +97 -31
- data/lib/jetstream_bridge/core/consumer_mode_resolver.rb +64 -0
- data/lib/jetstream_bridge/core/duration.rb +30 -0
- data/lib/jetstream_bridge/models/inbox_event.rb +1 -1
- data/lib/jetstream_bridge/models/outbox_event.rb +1 -1
- data/lib/jetstream_bridge/provisioner.rb +108 -13
- data/lib/jetstream_bridge/publisher/outbox_repository.rb +35 -20
- data/lib/jetstream_bridge/publisher/publisher.rb +4 -4
- data/lib/jetstream_bridge/tasks/install.rake +2 -2
- data/lib/jetstream_bridge/topology/stream.rb +6 -1
- data/lib/jetstream_bridge/topology/topology.rb +1 -1
- data/lib/jetstream_bridge/version.rb +1 -1
- data/lib/jetstream_bridge.rb +8 -12
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c1fefccd3db6dc8c8a197300fdb40f2240248aad44357e42a920b0a6b58bf4b5
|
|
4
|
+
data.tar.gz: c83639e8450df86ae9ccc0de89dd54e0e6ffa280b39a276105e9c94fd7b36b5b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ca4d68e73467236d2b8c38f4d6edbaea1d1a0c335fb19e7eb85a25ebba8e9adab5ddd8df4bf990bfca326ae7b03f7d7138b7289df86cf3a5709a614434ad46ad
|
|
7
|
+
data.tar.gz: 47cab006718f6e1e75f531db8d996e7a2d18e065949996ebd40f12db57bcb90d8329a05381a64292953c218b4076b1e747894c81e7344d5dc1606623b093bf55
|
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", "~>
|
|
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
|