jetstream_bridge 4.4.0 → 4.5.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 +4 -4
- data/CHANGELOG.md +92 -337
- data/README.md +1 -5
- data/docs/GETTING_STARTED.md +11 -7
- data/docs/PRODUCTION.md +51 -11
- data/docs/TESTING.md +24 -35
- data/lib/generators/jetstream_bridge/health_check/health_check_generator.rb +1 -1
- data/lib/generators/jetstream_bridge/initializer/initializer_generator.rb +1 -1
- data/lib/generators/jetstream_bridge/initializer/templates/jetstream_bridge.rb +0 -4
- data/lib/generators/jetstream_bridge/install/install_generator.rb +5 -5
- data/lib/generators/jetstream_bridge/migrations/migrations_generator.rb +2 -2
- data/lib/jetstream_bridge/consumer/consumer.rb +34 -96
- data/lib/jetstream_bridge/consumer/health_monitor.rb +107 -0
- data/lib/jetstream_bridge/consumer/message_processor.rb +1 -1
- data/lib/jetstream_bridge/consumer/subscription_manager.rb +51 -34
- data/lib/jetstream_bridge/core/config.rb +153 -46
- data/lib/jetstream_bridge/core/connection_manager.rb +513 -0
- data/lib/jetstream_bridge/core/debug_helper.rb +9 -3
- data/lib/jetstream_bridge/core/health_checker.rb +184 -0
- data/lib/jetstream_bridge/core.rb +0 -2
- data/lib/jetstream_bridge/facade.rb +212 -0
- data/lib/jetstream_bridge/publisher/event_envelope_builder.rb +110 -0
- data/lib/jetstream_bridge/publisher/publisher.rb +87 -117
- data/lib/jetstream_bridge/rails/integration.rb +8 -5
- data/lib/jetstream_bridge/rails/railtie.rb +4 -3
- data/lib/jetstream_bridge/tasks/install.rake +0 -1
- data/lib/jetstream_bridge/topology/topology.rb +6 -1
- data/lib/jetstream_bridge/version.rb +1 -1
- data/lib/jetstream_bridge.rb +206 -297
- metadata +7 -5
- data/lib/jetstream_bridge/core/bridge_helpers.rb +0 -109
- data/lib/jetstream_bridge/core/connection.rb +0 -464
- data/lib/jetstream_bridge/core/connection_factory.rb +0 -100
data/lib/jetstream_bridge.rb
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'jetstream_bridge/version'
|
|
4
4
|
require_relative 'jetstream_bridge/core'
|
|
5
|
+
require_relative 'jetstream_bridge/core/connection_manager'
|
|
6
|
+
require_relative 'jetstream_bridge/publisher/event_envelope_builder'
|
|
5
7
|
require_relative 'jetstream_bridge/publisher/publisher'
|
|
6
8
|
require_relative 'jetstream_bridge/publisher/batch_publisher'
|
|
7
9
|
require_relative 'jetstream_bridge/consumer/consumer'
|
|
8
10
|
require_relative 'jetstream_bridge/consumer/middleware'
|
|
9
11
|
require_relative 'jetstream_bridge/models/publish_result'
|
|
10
12
|
require_relative 'jetstream_bridge/models/event'
|
|
13
|
+
require_relative 'jetstream_bridge/facade'
|
|
11
14
|
|
|
12
15
|
# Rails-specific entry point (lifecycle helpers + Railtie)
|
|
13
16
|
require_relative 'jetstream_bridge/rails' if defined?(Rails::Railtie)
|
|
@@ -18,31 +21,19 @@ require_relative 'jetstream_bridge/models/outbox_event'
|
|
|
18
21
|
|
|
19
22
|
# JetStream Bridge - Production-safe realtime data bridge using NATS JetStream.
|
|
20
23
|
#
|
|
21
|
-
# JetStream Bridge provides a reliable, production-ready way to publish and consume
|
|
22
|
-
# events using NATS JetStream with features like:
|
|
23
|
-
#
|
|
24
|
-
# - Transactional Outbox pattern for guaranteed event publishing
|
|
25
|
-
# - Idempotent Inbox pattern for exactly-once message processing
|
|
26
|
-
# - Dead Letter Queue (DLQ) for poison message handling
|
|
27
|
-
# - Automatic stream provisioning and overlap detection
|
|
28
|
-
# - Built-in health checks and monitoring
|
|
29
|
-
# - Middleware support for cross-cutting concerns
|
|
30
|
-
# - Rails integration with generators and migrations
|
|
31
|
-
# - Graceful startup/shutdown lifecycle management
|
|
32
|
-
#
|
|
33
24
|
# @example Quick start
|
|
34
25
|
# # Configure
|
|
35
26
|
# JetstreamBridge.configure do |config|
|
|
36
27
|
# config.nats_urls = "nats://localhost:4222"
|
|
37
|
-
# config.env = "development"
|
|
38
28
|
# config.app_name = "my_app"
|
|
39
29
|
# config.destination_app = "other_app"
|
|
30
|
+
# config.stream_name = "MY_STREAM"
|
|
40
31
|
# config.use_outbox = true
|
|
41
32
|
# config.use_inbox = true
|
|
42
33
|
# end
|
|
43
34
|
#
|
|
44
|
-
# #
|
|
45
|
-
# JetstreamBridge.
|
|
35
|
+
# # Connect (validates config and establishes connection)
|
|
36
|
+
# JetstreamBridge.connect!
|
|
46
37
|
#
|
|
47
38
|
# # Publish events
|
|
48
39
|
# JetstreamBridge.publish(
|
|
@@ -57,364 +48,282 @@ require_relative 'jetstream_bridge/models/outbox_event'
|
|
|
57
48
|
# consumer.run!
|
|
58
49
|
#
|
|
59
50
|
# # Graceful shutdown
|
|
60
|
-
# at_exit { JetstreamBridge.
|
|
61
|
-
#
|
|
62
|
-
# @see Publisher For publishing events
|
|
63
|
-
# @see Consumer For consuming events
|
|
64
|
-
# @see Config For configuration options
|
|
65
|
-
# @see TestHelpers For testing utilities
|
|
51
|
+
# at_exit { JetstreamBridge.disconnect! }
|
|
66
52
|
#
|
|
67
53
|
module JetstreamBridge
|
|
68
54
|
class << self
|
|
69
|
-
|
|
70
|
-
|
|
55
|
+
# Get configuration instance
|
|
56
|
+
#
|
|
57
|
+
# @return [Config] Configuration object
|
|
71
58
|
def config
|
|
72
|
-
|
|
59
|
+
facade.config
|
|
73
60
|
end
|
|
74
61
|
|
|
75
62
|
# Configure JetStream Bridge settings
|
|
76
63
|
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
# automatically on first use (publish/subscribe) or via Rails railtie initialization.
|
|
64
|
+
# @yield [Config] Configuration object for block-based configuration
|
|
65
|
+
# @return [Config] The configured instance
|
|
80
66
|
#
|
|
81
|
-
# @example
|
|
67
|
+
# @example
|
|
82
68
|
# JetstreamBridge.configure do |config|
|
|
83
69
|
# config.nats_urls = "nats://localhost:4222"
|
|
84
70
|
# config.app_name = "my_app"
|
|
85
71
|
# config.destination_app = "worker"
|
|
72
|
+
# config.stream_name = "MY_STREAM"
|
|
86
73
|
# end
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
# @example With hash overrides
|
|
90
|
-
# JetstreamBridge.configure(env: 'production', app_name: 'my_app')
|
|
91
|
-
#
|
|
92
|
-
# @param overrides [Hash] Configuration key-value pairs to set
|
|
93
|
-
# @yield [Config] Configuration object for block-based configuration
|
|
94
|
-
# @return [Config] The configured instance
|
|
95
|
-
def configure(overrides = {}, **extra_overrides)
|
|
96
|
-
# Merge extra keyword arguments into overrides hash
|
|
97
|
-
all_overrides = overrides.nil? ? extra_overrides : overrides.merge(extra_overrides)
|
|
98
|
-
|
|
99
|
-
cfg = config
|
|
100
|
-
all_overrides.each { |k, v| assign_config_option!(cfg, k, v) } unless all_overrides.empty?
|
|
101
|
-
yield(cfg) if block_given?
|
|
102
|
-
|
|
103
|
-
cfg
|
|
74
|
+
def configure(&)
|
|
75
|
+
facade.configure(&)
|
|
104
76
|
end
|
|
105
77
|
|
|
106
|
-
#
|
|
78
|
+
# Connect to NATS and ensure stream topology
|
|
107
79
|
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
80
|
+
# Validates configuration and establishes connection.
|
|
81
|
+
# Idempotent - safe to call multiple times.
|
|
82
|
+
#
|
|
83
|
+
# @return [void]
|
|
84
|
+
# @raise [ConfigurationError] If configuration is invalid
|
|
85
|
+
# @raise [ConnectionError] If unable to connect
|
|
110
86
|
#
|
|
111
87
|
# @example
|
|
112
|
-
# JetstreamBridge.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
# config.destination_app = "worker"
|
|
116
|
-
# end
|
|
117
|
-
# JetstreamBridge.startup! # Explicitly start connection
|
|
118
|
-
#
|
|
119
|
-
# @param preset [Symbol] Preset name (:development, :test, :production, etc.)
|
|
120
|
-
# @yield [Config] Configuration object
|
|
121
|
-
# @return [Config] Configured instance
|
|
122
|
-
def configure_for(preset)
|
|
123
|
-
configure do |cfg|
|
|
124
|
-
cfg.apply_preset(preset)
|
|
125
|
-
yield(cfg) if block_given?
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def reset!
|
|
130
|
-
@config = nil
|
|
131
|
-
@connection_initialized = false
|
|
88
|
+
# JetstreamBridge.connect!
|
|
89
|
+
def connect!
|
|
90
|
+
facade.connect!
|
|
132
91
|
end
|
|
133
92
|
|
|
134
|
-
#
|
|
93
|
+
# Disconnect from NATS
|
|
135
94
|
#
|
|
136
|
-
#
|
|
95
|
+
# Closes the NATS connection and cleans up resources.
|
|
137
96
|
#
|
|
138
97
|
# @return [void]
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
Logging.info('JetStream Bridge started successfully', tag: 'JetstreamBridge')
|
|
98
|
+
#
|
|
99
|
+
# @example
|
|
100
|
+
# at_exit { JetstreamBridge.disconnect! }
|
|
101
|
+
def disconnect!
|
|
102
|
+
facade.disconnect!
|
|
145
103
|
end
|
|
146
104
|
|
|
147
|
-
# Reconnect to NATS
|
|
105
|
+
# Reconnect to NATS (disconnect + connect)
|
|
148
106
|
#
|
|
149
|
-
#
|
|
107
|
+
# Useful for:
|
|
150
108
|
# - Forking web servers (Puma, Unicorn) after worker boot
|
|
151
109
|
# - Recovering from connection issues
|
|
152
|
-
# - Configuration changes that require reconnection
|
|
153
110
|
#
|
|
154
|
-
# @
|
|
111
|
+
# @return [void]
|
|
112
|
+
#
|
|
113
|
+
# @example In Puma configuration
|
|
155
114
|
# on_worker_boot do
|
|
156
115
|
# JetstreamBridge.reconnect! if defined?(JetstreamBridge)
|
|
157
116
|
# end
|
|
158
|
-
#
|
|
159
|
-
# @return [void]
|
|
160
|
-
# @raise [ConnectionError] If unable to reconnect to NATS
|
|
161
117
|
def reconnect!
|
|
162
|
-
|
|
163
|
-
shutdown! if @connection_initialized
|
|
164
|
-
startup!
|
|
118
|
+
facade.reconnect!
|
|
165
119
|
end
|
|
166
120
|
|
|
167
|
-
#
|
|
121
|
+
# Publish an event
|
|
168
122
|
#
|
|
169
|
-
#
|
|
170
|
-
#
|
|
123
|
+
# Simplified API with single pattern:
|
|
124
|
+
# - event_type: required (e.g., "user.created")
|
|
125
|
+
# - payload: required event data
|
|
126
|
+
# - resource_type: optional (inferred from event_type if dotted notation)
|
|
127
|
+
# - All other fields are optional
|
|
171
128
|
#
|
|
172
|
-
# @
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def use_dlq?
|
|
196
|
-
config.use_dlq
|
|
129
|
+
# @param event_type [String] Event type (required)
|
|
130
|
+
# @param payload [Hash] Event payload data (required)
|
|
131
|
+
# @param resource_type [String, nil] Resource type (optional, inferred if nil)
|
|
132
|
+
# @param subject [String, nil] Optional NATS subject override
|
|
133
|
+
# @param options [Hash] Additional options (event_id, occurred_at, trace_id, etc.)
|
|
134
|
+
# @return [Models::PublishResult] Result object with success status and metadata
|
|
135
|
+
#
|
|
136
|
+
# @example Basic publishing
|
|
137
|
+
# result = JetstreamBridge.publish(
|
|
138
|
+
# event_type: "user.created",
|
|
139
|
+
# payload: { id: 1, email: "ada@example.com" }
|
|
140
|
+
# )
|
|
141
|
+
# puts "Success!" if result.success?
|
|
142
|
+
#
|
|
143
|
+
# @example With options
|
|
144
|
+
# result = JetstreamBridge.publish(
|
|
145
|
+
# event_type: "order.updated",
|
|
146
|
+
# payload: { id: 123, status: "shipped" },
|
|
147
|
+
# resource_type: "order",
|
|
148
|
+
# trace_id: request_id
|
|
149
|
+
# )
|
|
150
|
+
def publish(event_type:, payload:, **)
|
|
151
|
+
facade.publish(event_type: event_type, payload: payload, **)
|
|
197
152
|
end
|
|
198
153
|
|
|
199
|
-
#
|
|
200
|
-
#
|
|
201
|
-
# @
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
154
|
+
# Publish variant that raises on error
|
|
155
|
+
#
|
|
156
|
+
# @param (see #publish)
|
|
157
|
+
# @return [Models::PublishResult] Result object
|
|
158
|
+
# @raise [PublishError] If publishing fails
|
|
159
|
+
#
|
|
160
|
+
# @example
|
|
161
|
+
# JetstreamBridge.publish!(event_type: "user.created", payload: { id: 1 })
|
|
162
|
+
def publish!(event_type:, payload:, **)
|
|
163
|
+
result = publish(event_type: event_type, payload: payload, **)
|
|
164
|
+
if result.failure?
|
|
165
|
+
raise PublishError.new(result.error&.message, event_id: result.event_id, subject: result.subject)
|
|
166
|
+
end
|
|
208
167
|
|
|
209
|
-
|
|
210
|
-
def ensure_topology!
|
|
211
|
-
connect_and_ensure_stream!
|
|
168
|
+
result
|
|
212
169
|
end
|
|
213
170
|
|
|
214
|
-
#
|
|
171
|
+
# Publish a complete event envelope (advanced usage)
|
|
215
172
|
#
|
|
216
|
-
#
|
|
217
|
-
#
|
|
218
|
-
# - Verifies stream exists and is accessible (active: queries stream info)
|
|
219
|
-
# - Tests NATS round-trip communication (active: RTT measurement)
|
|
173
|
+
# Provides full control over the envelope structure. Use this when you need to
|
|
174
|
+
# manually construct the envelope or preserve all fields from an external source.
|
|
220
175
|
#
|
|
221
|
-
#
|
|
222
|
-
#
|
|
176
|
+
# @param envelope [Hash] Complete event envelope with all required fields
|
|
177
|
+
# @param subject [String, nil] Optional NATS subject override
|
|
178
|
+
# @return [Models::PublishResult] Result object
|
|
223
179
|
#
|
|
224
|
-
# @
|
|
225
|
-
#
|
|
226
|
-
# @
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
180
|
+
# @raise [ArgumentError] If required envelope fields are missing
|
|
181
|
+
#
|
|
182
|
+
# @example Publishing a complete envelope
|
|
183
|
+
# envelope = {
|
|
184
|
+
# 'event_id' => SecureRandom.uuid,
|
|
185
|
+
# 'schema_version' => 1,
|
|
186
|
+
# 'event_type' => 'user.created',
|
|
187
|
+
# 'producer' => 'custom-producer',
|
|
188
|
+
# 'resource_type' => 'user',
|
|
189
|
+
# 'resource_id' => '123',
|
|
190
|
+
# 'occurred_at' => Time.now.utc.iso8601,
|
|
191
|
+
# 'trace_id' => 'trace-123',
|
|
192
|
+
# 'payload' => { id: 123, name: 'Alice' }
|
|
193
|
+
# }
|
|
194
|
+
#
|
|
195
|
+
# result = JetstreamBridge.publish_envelope(envelope)
|
|
196
|
+
# puts "Published: #{result.event_id}"
|
|
197
|
+
#
|
|
198
|
+
# @example Forwarding events from another system
|
|
199
|
+
# # Receive event from external system
|
|
200
|
+
# external_event = external_api.get_event
|
|
201
|
+
#
|
|
202
|
+
# # Publish as-is, preserving all metadata
|
|
203
|
+
# JetstreamBridge.publish_envelope(external_event)
|
|
204
|
+
def publish_envelope(envelope, subject: nil)
|
|
205
|
+
facade.publish_envelope(envelope, subject: subject)
|
|
206
|
+
end
|
|
249
207
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
healthy: false,
|
|
277
|
-
connection: {
|
|
278
|
-
state: :failed,
|
|
279
|
-
connected: false
|
|
280
|
-
},
|
|
281
|
-
error: "#{e.class}: #{e.message}"
|
|
282
|
-
}
|
|
208
|
+
# Subscribe to events
|
|
209
|
+
#
|
|
210
|
+
# @param handler [Proc, #call, nil] Message handler (optional if block given)
|
|
211
|
+
# @param durable_name [String, nil] Optional durable consumer name override
|
|
212
|
+
# @param batch_size [Integer, nil] Optional batch size override
|
|
213
|
+
# @yield [event] Yields Models::Event object to block
|
|
214
|
+
# @return [Consumer] Consumer instance (call run! to start processing)
|
|
215
|
+
#
|
|
216
|
+
# @example With block
|
|
217
|
+
# consumer = JetstreamBridge.subscribe do |event|
|
|
218
|
+
# puts "Received: #{event.type} - #{event.payload.to_h}"
|
|
219
|
+
# end
|
|
220
|
+
# consumer.run! # Blocking
|
|
221
|
+
#
|
|
222
|
+
# @example With handler
|
|
223
|
+
# handler = ->(event) { EventProcessor.process(event) }
|
|
224
|
+
# consumer = JetstreamBridge.subscribe(handler)
|
|
225
|
+
# consumer.run!
|
|
226
|
+
def subscribe(handler = nil, durable_name: nil, batch_size: nil, &block)
|
|
227
|
+
raise ArgumentError, 'Handler or block required' unless handler || block
|
|
228
|
+
|
|
229
|
+
facade.subscribe(
|
|
230
|
+
handler || block,
|
|
231
|
+
durable_name: durable_name,
|
|
232
|
+
batch_size: batch_size
|
|
233
|
+
)
|
|
283
234
|
end
|
|
284
235
|
|
|
285
236
|
# Check if connected to NATS
|
|
286
237
|
#
|
|
238
|
+
# @param skip_cache [Boolean] Force fresh health check
|
|
287
239
|
# @return [Boolean] true if connected and healthy
|
|
288
|
-
def connected?
|
|
289
|
-
Connection.instance.connected?
|
|
290
|
-
rescue StandardError
|
|
291
|
-
false
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
# Get stream information for the configured stream
|
|
295
240
|
#
|
|
296
|
-
# @
|
|
297
|
-
|
|
298
|
-
|
|
241
|
+
# @example
|
|
242
|
+
# if JetstreamBridge.connected?
|
|
243
|
+
# puts "Ready to publish"
|
|
244
|
+
# end
|
|
245
|
+
def connected?(skip_cache: false)
|
|
246
|
+
facade.connected?(skip_cache: skip_cache)
|
|
299
247
|
end
|
|
300
248
|
|
|
301
|
-
#
|
|
302
|
-
#
|
|
303
|
-
# Automatically establishes connection on first use if not already connected.
|
|
304
|
-
#
|
|
305
|
-
# Supports three usage patterns:
|
|
249
|
+
# Get comprehensive health status (primary method)
|
|
306
250
|
#
|
|
307
|
-
#
|
|
308
|
-
#
|
|
251
|
+
# Provides complete system health including connection state, stream info,
|
|
252
|
+
# performance metrics, and configuration. Use this for monitoring and
|
|
253
|
+
# readiness probes.
|
|
309
254
|
#
|
|
310
|
-
#
|
|
311
|
-
# JetstreamBridge.publish(event_type: 'user.created', payload: { id: 1, name: 'Ada' })
|
|
255
|
+
# Rate limited to once every 5 seconds for uncached checks.
|
|
312
256
|
#
|
|
313
|
-
#
|
|
314
|
-
#
|
|
257
|
+
# @param skip_cache [Boolean] Force fresh health check (rate limited)
|
|
258
|
+
# @return [Hash] Health status including connection, stream, performance, config, and version
|
|
315
259
|
#
|
|
316
|
-
# @
|
|
317
|
-
#
|
|
318
|
-
#
|
|
319
|
-
#
|
|
320
|
-
#
|
|
321
|
-
# @param options [Hash] Additional options (event_id, occurred_at, trace_id)
|
|
322
|
-
# @return [Models::PublishResult] Result object with success status and metadata
|
|
260
|
+
# @example Basic health check
|
|
261
|
+
# health = JetstreamBridge.health
|
|
262
|
+
# puts "Healthy: #{health[:healthy]}"
|
|
263
|
+
# puts "State: #{health[:connection][:state]}"
|
|
264
|
+
# puts "RTT: #{health[:performance][:nats_rtt_ms]}ms"
|
|
323
265
|
#
|
|
324
|
-
# @example
|
|
325
|
-
#
|
|
326
|
-
#
|
|
327
|
-
#
|
|
328
|
-
#
|
|
329
|
-
#
|
|
266
|
+
# @example Kubernetes readiness probe
|
|
267
|
+
# def ready
|
|
268
|
+
# health = JetstreamBridge.health
|
|
269
|
+
# if health[:healthy]
|
|
270
|
+
# render json: health, status: :ok
|
|
271
|
+
# else
|
|
272
|
+
# render json: health, status: :service_unavailable
|
|
273
|
+
# end
|
|
330
274
|
# end
|
|
331
|
-
def
|
|
332
|
-
|
|
333
|
-
publisher = Publisher.new
|
|
334
|
-
publisher.publish(event_or_hash, resource_type: resource_type, event_type: event_type, payload: payload,
|
|
335
|
-
subject: subject, **)
|
|
275
|
+
def health(skip_cache: false)
|
|
276
|
+
facade.health(skip_cache: skip_cache)
|
|
336
277
|
end
|
|
337
278
|
|
|
338
|
-
#
|
|
339
|
-
#
|
|
340
|
-
# @example
|
|
341
|
-
# JetstreamBridge.publish!(event_type: "user.created", payload: { id: 1 })
|
|
342
|
-
# # Raises PublishError if publishing fails
|
|
279
|
+
# Backward compatibility alias for health
|
|
343
280
|
#
|
|
344
|
-
# @
|
|
345
|
-
# @
|
|
346
|
-
# @
|
|
347
|
-
def
|
|
348
|
-
|
|
349
|
-
if result.failure?
|
|
350
|
-
raise PublishError.new(result.error&.message, event_id: result.event_id,
|
|
351
|
-
subject: result.subject)
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
result
|
|
281
|
+
# @deprecated Use {#health} instead
|
|
282
|
+
# @param skip_cache [Boolean] Force fresh health check
|
|
283
|
+
# @return [Hash] Health status
|
|
284
|
+
def health_check(skip_cache: false)
|
|
285
|
+
health(skip_cache: skip_cache)
|
|
355
286
|
end
|
|
356
287
|
|
|
357
|
-
#
|
|
288
|
+
# Check if system is healthy (convenience method)
|
|
289
|
+
#
|
|
290
|
+
# Returns a simple boolean indicating overall health.
|
|
291
|
+
# Equivalent to `health[:healthy]` but more convenient.
|
|
292
|
+
#
|
|
293
|
+
# @return [Boolean] true if connected and stream exists
|
|
358
294
|
#
|
|
359
295
|
# @example
|
|
360
|
-
#
|
|
361
|
-
#
|
|
362
|
-
#
|
|
363
|
-
#
|
|
296
|
+
# if JetstreamBridge.healthy?
|
|
297
|
+
# JetstreamBridge.publish(...)
|
|
298
|
+
# else
|
|
299
|
+
# logger.warn "JetStream not healthy, skipping publish"
|
|
364
300
|
# end
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
# @yield [BatchPublisher] Batch publisher instance
|
|
368
|
-
# @return [BatchPublisher::BatchResult] Result with success/failure counts
|
|
369
|
-
def publish_batch
|
|
370
|
-
batch = BatchPublisher.new
|
|
371
|
-
yield(batch) if block_given?
|
|
372
|
-
batch.publish
|
|
301
|
+
def healthy?
|
|
302
|
+
facade.healthy?
|
|
373
303
|
end
|
|
374
304
|
|
|
375
|
-
#
|
|
376
|
-
#
|
|
377
|
-
# Automatically establishes connection on first use if not already connected.
|
|
378
|
-
#
|
|
379
|
-
# Supports two usage patterns:
|
|
305
|
+
# Get stream information
|
|
380
306
|
#
|
|
381
|
-
#
|
|
382
|
-
# consumer = JetstreamBridge.subscribe do |event|
|
|
383
|
-
# puts "Received: #{event.type} on #{event.subject} (attempt #{event.deliveries})"
|
|
384
|
-
# end
|
|
385
|
-
# consumer.run!
|
|
386
|
-
#
|
|
387
|
-
# 2. With auto-run (returns Thread):
|
|
388
|
-
# thread = JetstreamBridge.subscribe(run: true) do |event|
|
|
389
|
-
# puts "Received: #{event.type}"
|
|
390
|
-
# end
|
|
391
|
-
# thread.join # Wait for consumer to finish
|
|
307
|
+
# @return [Hash] Stream information including subjects and message count
|
|
392
308
|
#
|
|
393
|
-
#
|
|
394
|
-
#
|
|
395
|
-
#
|
|
396
|
-
|
|
309
|
+
# @example
|
|
310
|
+
# info = JetstreamBridge.stream_info
|
|
311
|
+
# puts "Messages: #{info[:messages]}"
|
|
312
|
+
def stream_info
|
|
313
|
+
facade.stream_info
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Reset facade (for testing)
|
|
397
317
|
#
|
|
398
|
-
# @
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
# @yield [event] Yields Models::Event object to block
|
|
403
|
-
# @return [Consumer, Thread] Consumer instance or Thread if run: true
|
|
404
|
-
def subscribe(handler = nil, run: false, durable_name: nil, batch_size: nil, &block)
|
|
405
|
-
connect_if_needed!
|
|
406
|
-
handler ||= block
|
|
407
|
-
raise ArgumentError, 'Handler or block required' unless handler
|
|
318
|
+
# @api private
|
|
319
|
+
def reset!
|
|
320
|
+
@facade = nil
|
|
321
|
+
end
|
|
408
322
|
|
|
409
|
-
|
|
323
|
+
private
|
|
410
324
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
thread.abort_on_exception = true
|
|
414
|
-
thread
|
|
415
|
-
else
|
|
416
|
-
consumer
|
|
417
|
-
end
|
|
325
|
+
def facade
|
|
326
|
+
@facade ||= Facade.new
|
|
418
327
|
end
|
|
419
328
|
end
|
|
420
329
|
end
|