jetstream_bridge 3.0.2 → 4.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 +54 -1
- data/README.md +1149 -84
- data/lib/jetstream_bridge/consumer/consumer.rb +174 -6
- data/lib/jetstream_bridge/consumer/inbox/inbox_processor.rb +1 -1
- data/lib/jetstream_bridge/consumer/message_processor.rb +41 -7
- data/lib/jetstream_bridge/consumer/middleware.rb +154 -0
- data/lib/jetstream_bridge/core/config.rb +150 -9
- data/lib/jetstream_bridge/core/config_preset.rb +99 -0
- data/lib/jetstream_bridge/core/connection.rb +5 -2
- data/lib/jetstream_bridge/core/connection_factory.rb +1 -1
- data/lib/jetstream_bridge/core/duration.rb +19 -35
- data/lib/jetstream_bridge/errors.rb +60 -8
- data/lib/jetstream_bridge/models/event.rb +202 -0
- data/lib/jetstream_bridge/{inbox_event.rb → models/inbox_event.rb} +61 -3
- data/lib/jetstream_bridge/{outbox_event.rb → models/outbox_event.rb} +64 -15
- data/lib/jetstream_bridge/models/publish_result.rb +64 -0
- data/lib/jetstream_bridge/models/subject.rb +53 -2
- data/lib/jetstream_bridge/publisher/batch_publisher.rb +163 -0
- data/lib/jetstream_bridge/publisher/publisher.rb +238 -19
- data/lib/jetstream_bridge/test_helpers.rb +275 -0
- data/lib/jetstream_bridge/version.rb +1 -1
- data/lib/jetstream_bridge.rb +178 -3
- data/lib/tasks/yard.rake +18 -0
- metadata +11 -4
data/lib/jetstream_bridge.rb
CHANGED
|
@@ -6,16 +6,59 @@ require_relative 'jetstream_bridge/core/duration'
|
|
|
6
6
|
require_relative 'jetstream_bridge/core/logging'
|
|
7
7
|
require_relative 'jetstream_bridge/core/connection'
|
|
8
8
|
require_relative 'jetstream_bridge/publisher/publisher'
|
|
9
|
+
require_relative 'jetstream_bridge/publisher/batch_publisher'
|
|
9
10
|
require_relative 'jetstream_bridge/consumer/consumer'
|
|
11
|
+
require_relative 'jetstream_bridge/consumer/middleware'
|
|
12
|
+
require_relative 'jetstream_bridge/models/publish_result'
|
|
13
|
+
require_relative 'jetstream_bridge/models/event'
|
|
10
14
|
|
|
11
15
|
# If you have a Railtie for tasks/eager-loading
|
|
12
16
|
require_relative 'jetstream_bridge/railtie' if defined?(Rails::Railtie)
|
|
13
17
|
|
|
14
18
|
# Load gem-provided models from lib/
|
|
15
|
-
require_relative 'jetstream_bridge/inbox_event'
|
|
16
|
-
require_relative 'jetstream_bridge/outbox_event'
|
|
19
|
+
require_relative 'jetstream_bridge/models/inbox_event'
|
|
20
|
+
require_relative 'jetstream_bridge/models/outbox_event'
|
|
17
21
|
|
|
18
|
-
#
|
|
22
|
+
# JetStream Bridge - Production-safe realtime data bridge using NATS JetStream.
|
|
23
|
+
#
|
|
24
|
+
# JetStream Bridge provides a reliable, production-ready way to publish and consume
|
|
25
|
+
# events using NATS JetStream with features like:
|
|
26
|
+
#
|
|
27
|
+
# - Transactional Outbox pattern for guaranteed event publishing
|
|
28
|
+
# - Idempotent Inbox pattern for exactly-once message processing
|
|
29
|
+
# - Dead Letter Queue (DLQ) for poison message handling
|
|
30
|
+
# - Automatic stream provisioning and overlap detection
|
|
31
|
+
# - Built-in health checks and monitoring
|
|
32
|
+
# - Middleware support for cross-cutting concerns
|
|
33
|
+
# - Rails integration with generators and migrations
|
|
34
|
+
#
|
|
35
|
+
# @example Quick start
|
|
36
|
+
# # Configure
|
|
37
|
+
# JetstreamBridge.configure do |config|
|
|
38
|
+
# config.nats_urls = "nats://localhost:4222"
|
|
39
|
+
# config.env = "development"
|
|
40
|
+
# config.app_name = "my_app"
|
|
41
|
+
# config.destination_app = "other_app"
|
|
42
|
+
# config.use_outbox = true
|
|
43
|
+
# config.use_inbox = true
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# # Publish events
|
|
47
|
+
# JetstreamBridge.publish(
|
|
48
|
+
# event_type: "user.created",
|
|
49
|
+
# payload: { id: 1, email: "ada@example.com" }
|
|
50
|
+
# )
|
|
51
|
+
#
|
|
52
|
+
# # Consume events
|
|
53
|
+
# JetstreamBridge.subscribe do |event|
|
|
54
|
+
# puts "Received: #{event.type} - #{event.payload.to_h}"
|
|
55
|
+
# end.run!
|
|
56
|
+
#
|
|
57
|
+
# @see Publisher For publishing events
|
|
58
|
+
# @see Consumer For consuming events
|
|
59
|
+
# @see Config For configuration options
|
|
60
|
+
# @see TestHelpers For testing utilities
|
|
61
|
+
#
|
|
19
62
|
module JetstreamBridge
|
|
20
63
|
class << self
|
|
21
64
|
def config
|
|
@@ -29,6 +72,25 @@ module JetstreamBridge
|
|
|
29
72
|
cfg
|
|
30
73
|
end
|
|
31
74
|
|
|
75
|
+
# Configure with a preset
|
|
76
|
+
#
|
|
77
|
+
# @example
|
|
78
|
+
# JetstreamBridge.configure_for(:production) do |config|
|
|
79
|
+
# config.nats_urls = ENV["NATS_URLS"]
|
|
80
|
+
# config.app_name = "my_app"
|
|
81
|
+
# config.destination_app = "worker"
|
|
82
|
+
# end
|
|
83
|
+
#
|
|
84
|
+
# @param preset [Symbol] Preset name (:development, :test, :production, etc.)
|
|
85
|
+
# @yield [Config] Configuration object
|
|
86
|
+
# @return [Config] Configured instance
|
|
87
|
+
def configure_for(preset)
|
|
88
|
+
configure do |cfg|
|
|
89
|
+
cfg.apply_preset(preset)
|
|
90
|
+
yield(cfg) if block_given?
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
32
94
|
def reset!
|
|
33
95
|
@config = nil
|
|
34
96
|
end
|
|
@@ -101,6 +163,119 @@ module JetstreamBridge
|
|
|
101
163
|
fetch_stream_info
|
|
102
164
|
end
|
|
103
165
|
|
|
166
|
+
# Convenience method to publish events
|
|
167
|
+
#
|
|
168
|
+
# Supports three usage patterns:
|
|
169
|
+
#
|
|
170
|
+
# 1. Structured parameters (recommended):
|
|
171
|
+
# JetstreamBridge.publish(resource_type: 'user', event_type: 'created', payload: { id: 1, name: 'Ada' })
|
|
172
|
+
#
|
|
173
|
+
# 2. Simplified hash (infers resource_type from event_type):
|
|
174
|
+
# JetstreamBridge.publish(event_type: 'user.created', payload: { id: 1, name: 'Ada' })
|
|
175
|
+
#
|
|
176
|
+
# 3. Complete envelope (advanced):
|
|
177
|
+
# JetstreamBridge.publish({ event_type: 'created', resource_type: 'user', payload: {...}, event_id: '...' })
|
|
178
|
+
#
|
|
179
|
+
# @param event_or_hash [Hash, nil] Event hash or first positional argument
|
|
180
|
+
# @param resource_type [String, nil] Resource type (e.g., 'user', 'order')
|
|
181
|
+
# @param event_type [String, nil] Event type (e.g., 'created', 'updated', 'user.created')
|
|
182
|
+
# @param payload [Hash, nil] Event payload data
|
|
183
|
+
# @param subject [String, nil] Optional subject override
|
|
184
|
+
# @param options [Hash] Additional options (event_id, occurred_at, trace_id)
|
|
185
|
+
# @return [Models::PublishResult] Result object with success status and metadata
|
|
186
|
+
#
|
|
187
|
+
# @example Check result status
|
|
188
|
+
# result = JetstreamBridge.publish(event_type: "user.created", payload: { id: 1 })
|
|
189
|
+
# if result.success?
|
|
190
|
+
# puts "Published event #{result.event_id}"
|
|
191
|
+
# else
|
|
192
|
+
# logger.error("Publish failed: #{result.error}")
|
|
193
|
+
# end
|
|
194
|
+
def publish(event_or_hash = nil, resource_type: nil, event_type: nil, payload: nil, subject: nil, **)
|
|
195
|
+
publisher = Publisher.new
|
|
196
|
+
publisher.publish(event_or_hash, resource_type: resource_type, event_type: event_type, payload: payload,
|
|
197
|
+
subject: subject, **)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Publish variant that raises on error
|
|
201
|
+
#
|
|
202
|
+
# @example
|
|
203
|
+
# JetstreamBridge.publish!(event_type: "user.created", payload: { id: 1 })
|
|
204
|
+
# # Raises PublishError if publishing fails
|
|
205
|
+
#
|
|
206
|
+
# @param (see #publish)
|
|
207
|
+
# @return [Models::PublishResult] Result object
|
|
208
|
+
# @raise [PublishError] If publishing fails
|
|
209
|
+
def publish!(...)
|
|
210
|
+
result = publish(...)
|
|
211
|
+
if result.failure?
|
|
212
|
+
raise PublishError.new(result.error&.message, event_id: result.event_id,
|
|
213
|
+
subject: result.subject)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
result
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Batch publish multiple events efficiently
|
|
220
|
+
#
|
|
221
|
+
# @example
|
|
222
|
+
# results = JetstreamBridge.publish_batch do |batch|
|
|
223
|
+
# users.each do |user|
|
|
224
|
+
# batch.add(event_type: "user.created", payload: { id: user.id })
|
|
225
|
+
# end
|
|
226
|
+
# end
|
|
227
|
+
# puts "Success: #{results.successful_count}, Failed: #{results.failed_count}"
|
|
228
|
+
#
|
|
229
|
+
# @yield [BatchPublisher] Batch publisher instance
|
|
230
|
+
# @return [BatchPublisher::BatchResult] Result with success/failure counts
|
|
231
|
+
def publish_batch
|
|
232
|
+
batch = BatchPublisher.new
|
|
233
|
+
yield(batch) if block_given?
|
|
234
|
+
batch.publish
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Convenience method to start consuming messages
|
|
238
|
+
#
|
|
239
|
+
# Supports two usage patterns:
|
|
240
|
+
#
|
|
241
|
+
# 1. With a block (recommended):
|
|
242
|
+
# consumer = JetstreamBridge.subscribe do |event|
|
|
243
|
+
# puts "Received: #{event.type} on #{event.subject} (attempt #{event.deliveries})"
|
|
244
|
+
# end
|
|
245
|
+
# consumer.run!
|
|
246
|
+
#
|
|
247
|
+
# 2. With auto-run (returns Thread):
|
|
248
|
+
# thread = JetstreamBridge.subscribe(run: true) do |event|
|
|
249
|
+
# puts "Received: #{event.type}"
|
|
250
|
+
# end
|
|
251
|
+
# thread.join # Wait for consumer to finish
|
|
252
|
+
#
|
|
253
|
+
# 3. With a handler object:
|
|
254
|
+
# handler = ->(event) { puts event.type }
|
|
255
|
+
# consumer = JetstreamBridge.subscribe(handler)
|
|
256
|
+
# consumer.run!
|
|
257
|
+
#
|
|
258
|
+
# @param handler [Proc, #call, nil] Message handler (optional if block given)
|
|
259
|
+
# @param run [Boolean] If true, automatically runs consumer in a background thread
|
|
260
|
+
# @param durable_name [String, nil] Optional durable consumer name override
|
|
261
|
+
# @param batch_size [Integer, nil] Optional batch size override
|
|
262
|
+
# @yield [event] Yields Models::Event object to block
|
|
263
|
+
# @return [Consumer, Thread] Consumer instance or Thread if run: true
|
|
264
|
+
def subscribe(handler = nil, run: false, durable_name: nil, batch_size: nil, &block)
|
|
265
|
+
handler ||= block
|
|
266
|
+
raise ArgumentError, 'Handler or block required' unless handler
|
|
267
|
+
|
|
268
|
+
consumer = Consumer.new(handler, durable_name: durable_name, batch_size: batch_size)
|
|
269
|
+
|
|
270
|
+
if run
|
|
271
|
+
thread = Thread.new { consumer.run! }
|
|
272
|
+
thread.abort_on_exception = true
|
|
273
|
+
thread
|
|
274
|
+
else
|
|
275
|
+
consumer
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
104
279
|
private
|
|
105
280
|
|
|
106
281
|
def fetch_stream_info
|
data/lib/tasks/yard.rake
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'yard'
|
|
5
|
+
|
|
6
|
+
YARD::Rake::YardocTask.new(:yard) do |t|
|
|
7
|
+
t.files = ['lib/**/*.rb']
|
|
8
|
+
t.options = ['--markup', 'markdown', '--readme', 'README.md']
|
|
9
|
+
t.stats_options = ['--list-undoc']
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
desc 'Generate YARD documentation and display stats'
|
|
13
|
+
task 'yard:stats' do
|
|
14
|
+
sh 'bundle exec yard stats --list-undoc'
|
|
15
|
+
end
|
|
16
|
+
rescue LoadError
|
|
17
|
+
# YARD not available
|
|
18
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jetstream_bridge
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 4.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mike Attara
|
|
@@ -127,8 +127,10 @@ files:
|
|
|
127
127
|
- lib/jetstream_bridge/consumer/inbox/inbox_processor.rb
|
|
128
128
|
- lib/jetstream_bridge/consumer/inbox/inbox_repository.rb
|
|
129
129
|
- lib/jetstream_bridge/consumer/message_processor.rb
|
|
130
|
+
- lib/jetstream_bridge/consumer/middleware.rb
|
|
130
131
|
- lib/jetstream_bridge/consumer/subscription_manager.rb
|
|
131
132
|
- lib/jetstream_bridge/core/config.rb
|
|
133
|
+
- lib/jetstream_bridge/core/config_preset.rb
|
|
132
134
|
- lib/jetstream_bridge/core/connection.rb
|
|
133
135
|
- lib/jetstream_bridge/core/connection_factory.rb
|
|
134
136
|
- lib/jetstream_bridge/core/debug_helper.rb
|
|
@@ -138,19 +140,24 @@ files:
|
|
|
138
140
|
- lib/jetstream_bridge/core/model_utils.rb
|
|
139
141
|
- lib/jetstream_bridge/core/retry_strategy.rb
|
|
140
142
|
- lib/jetstream_bridge/errors.rb
|
|
141
|
-
- lib/jetstream_bridge/
|
|
143
|
+
- lib/jetstream_bridge/models/event.rb
|
|
142
144
|
- lib/jetstream_bridge/models/event_envelope.rb
|
|
145
|
+
- lib/jetstream_bridge/models/inbox_event.rb
|
|
146
|
+
- lib/jetstream_bridge/models/outbox_event.rb
|
|
147
|
+
- lib/jetstream_bridge/models/publish_result.rb
|
|
143
148
|
- lib/jetstream_bridge/models/subject.rb
|
|
144
|
-
- lib/jetstream_bridge/
|
|
149
|
+
- lib/jetstream_bridge/publisher/batch_publisher.rb
|
|
145
150
|
- lib/jetstream_bridge/publisher/outbox_repository.rb
|
|
146
151
|
- lib/jetstream_bridge/publisher/publisher.rb
|
|
147
152
|
- lib/jetstream_bridge/railtie.rb
|
|
148
153
|
- lib/jetstream_bridge/tasks/install.rake
|
|
154
|
+
- lib/jetstream_bridge/test_helpers.rb
|
|
149
155
|
- lib/jetstream_bridge/topology/overlap_guard.rb
|
|
150
156
|
- lib/jetstream_bridge/topology/stream.rb
|
|
151
157
|
- lib/jetstream_bridge/topology/subject_matcher.rb
|
|
152
158
|
- lib/jetstream_bridge/topology/topology.rb
|
|
153
159
|
- lib/jetstream_bridge/version.rb
|
|
160
|
+
- lib/tasks/yard.rake
|
|
154
161
|
homepage: https://github.com/attaradev/jetstream_bridge
|
|
155
162
|
licenses:
|
|
156
163
|
- MIT
|
|
@@ -158,7 +165,7 @@ metadata:
|
|
|
158
165
|
homepage_uri: https://github.com/attaradev/jetstream_bridge
|
|
159
166
|
source_code_uri: https://github.com/attaradev/jetstream_bridge
|
|
160
167
|
changelog_uri: https://github.com/attaradev/jetstream_bridge/blob/main/CHANGELOG.md
|
|
161
|
-
documentation_uri: https://
|
|
168
|
+
documentation_uri: https://rubydoc.info/gems/jetstream_bridge
|
|
162
169
|
bug_tracker_uri: https://github.com/attaradev/jetstream_bridge/issues
|
|
163
170
|
rubygems_mfa_required: 'true'
|
|
164
171
|
allowed_push_host: https://rubygems.org
|