jetstream_bridge 3.0.2 → 4.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.
@@ -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
- # JetstreamBridge main module.
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
@@ -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: 3.0.2
4
+ version: 4.0.0
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/inbox_event.rb
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/outbox_event.rb
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://github.com/attaradev/jetstream_bridge#readme
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