jetstream_bridge 1.7.0 → 1.9.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/.rubocop.yml +0 -4
- data/Gemfile.lock +1 -3
- data/lib/jetstream_bridge/models/inbox_event.rb +49 -38
- data/lib/jetstream_bridge/models/outbox_event.rb +49 -47
- data/lib/jetstream_bridge/railtie.rb +28 -3
- data/lib/jetstream_bridge/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 601869d9b83276fd0e3b6a249dafc1bb7a749ffe6aade556077b60b95eb24124
|
4
|
+
data.tar.gz: b7ea75cdabb21f5f71d5ff447cd4a449c470a56ff6a3723737895c1cfa260bbc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4de9e146864e60fda36d68380e7bf5485d317d599c689d2594e719c1f71f73da3667d43add5e646289a2e5dc8f4dc93495b2f615b99d90c120d4674684b415fe
|
7
|
+
data.tar.gz: 4b4f148a4dea6c38c108f84f07bb179e601faf8aec06f01cbf495a6b645e14dc11ce805d69ab7c91dbd747966f6f44866d19a9b429e347617fa06e208b159c8c
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
jetstream_bridge (1.
|
4
|
+
jetstream_bridge (1.9.0)
|
5
5
|
activerecord (>= 6.0)
|
6
6
|
activesupport (>= 6.0)
|
7
7
|
nats-pure (~> 2.4)
|
@@ -142,8 +142,6 @@ GEM
|
|
142
142
|
racc (~> 1.4)
|
143
143
|
nokogiri (1.18.7-arm64-darwin)
|
144
144
|
racc (~> 1.4)
|
145
|
-
nokogiri (1.18.7-x86_64-linux-gnu)
|
146
|
-
racc (~> 1.4)
|
147
145
|
parallel (1.27.0)
|
148
146
|
parser (3.3.9.0)
|
149
147
|
ast (~> 2.4.1)
|
@@ -3,62 +3,74 @@
|
|
3
3
|
begin
|
4
4
|
require 'active_record'
|
5
5
|
rescue LoadError
|
6
|
-
# No-op; shim below
|
6
|
+
# No-op; shim defined below.
|
7
7
|
end
|
8
8
|
|
9
9
|
module JetstreamBridge
|
10
|
-
# Default Inbox model when `use_inbox` is enabled.
|
11
|
-
# Prefers event_id, but can fall back to (stream, stream_seq).
|
12
10
|
if defined?(ActiveRecord::Base)
|
13
11
|
class InboxEvent < ActiveRecord::Base
|
14
12
|
self.table_name = 'jetstream_inbox_events'
|
15
13
|
|
16
14
|
class << self
|
17
|
-
|
15
|
+
# Safe column presence check that never boots a connection during class load.
|
16
|
+
def has_column?(name)
|
17
|
+
return false unless ar_connected?
|
18
|
+
connection.schema_cache.columns_hash(table_name).key?(name.to_s)
|
19
|
+
rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::NoDatabaseError
|
20
|
+
false
|
21
|
+
end
|
18
22
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
23
|
+
def ar_connected?
|
24
|
+
ActiveRecord::Base.connected? && connection_pool.active_connection?
|
25
|
+
rescue
|
26
|
+
false
|
22
27
|
end
|
23
28
|
end
|
24
29
|
|
25
|
-
|
26
|
-
serialize :payload, coder: JSON unless attribute_json?(:payload)
|
27
|
-
end
|
28
|
-
if column?(:headers)
|
29
|
-
serialize :headers, coder: JSON unless attribute_json?(:headers)
|
30
|
-
end
|
30
|
+
# ---- Validations (NO with_options; guard everything with procs) ----
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
if column?(:stream)
|
38
|
-
validates :stream_seq, uniqueness: { scope: :stream }
|
39
|
-
else
|
40
|
-
validates :stream_seq, uniqueness: true
|
41
|
-
end
|
42
|
-
end
|
32
|
+
# Preferred dedupe key
|
33
|
+
validates :event_id,
|
34
|
+
presence: true,
|
35
|
+
uniqueness: true,
|
36
|
+
if: -> { self.class.has_column?(:event_id) }
|
43
37
|
|
44
|
-
|
38
|
+
# Fallback to (stream, stream_seq) when event_id column not present
|
39
|
+
validates :stream_seq,
|
40
|
+
presence: true,
|
41
|
+
if: -> { !self.class.has_column?(:event_id) && self.class.has_column?(:stream_seq) }
|
45
42
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
43
|
+
validates :stream_seq,
|
44
|
+
uniqueness: { scope: :stream },
|
45
|
+
if: -> {
|
46
|
+
!self.class.has_column?(:event_id) &&
|
47
|
+
self.class.has_column?(:stream_seq) &&
|
48
|
+
self.class.has_column?(:stream)
|
49
|
+
}
|
50
50
|
|
51
|
-
|
51
|
+
validates :stream_seq,
|
52
|
+
uniqueness: true,
|
53
|
+
if: -> {
|
54
|
+
!self.class.has_column?(:event_id) &&
|
55
|
+
self.class.has_column?(:stream_seq) &&
|
56
|
+
!self.class.has_column?(:stream)
|
57
|
+
}
|
52
58
|
|
59
|
+
validates :subject,
|
60
|
+
presence: true,
|
61
|
+
if: -> { self.class.has_column?(:subject) }
|
62
|
+
|
63
|
+
# ---- Defaults that do not require schema at load time ----
|
53
64
|
before_validation do
|
54
|
-
self.status ||= 'received' if self.class.
|
55
|
-
self.received_at ||= Time.now.utc if self.class.
|
65
|
+
self.status ||= 'received' if self.class.has_column?(:status) && status.blank?
|
66
|
+
self.received_at ||= Time.now.utc if self.class.has_column?(:received_at) && received_at.blank?
|
56
67
|
end
|
57
68
|
|
69
|
+
# ---- Helpers ----
|
58
70
|
def processed?
|
59
|
-
if self.class.
|
71
|
+
if self.class.has_column?(:processed_at)
|
60
72
|
processed_at.present?
|
61
|
-
elsif self.class.
|
73
|
+
elsif self.class.has_column?(:status)
|
62
74
|
status == 'processed'
|
63
75
|
else
|
64
76
|
false
|
@@ -75,21 +87,20 @@ module JetstreamBridge
|
|
75
87
|
end
|
76
88
|
end
|
77
89
|
else
|
90
|
+
# Shim: loud failure if AR isn't present but someone calls the model.
|
78
91
|
class InboxEvent
|
79
92
|
class << self
|
80
93
|
def method_missing(method_name, *_args, &_block)
|
81
94
|
raise_missing_ar!('Inbox', method_name)
|
82
95
|
end
|
83
|
-
|
84
|
-
def respond_to_missing?(_name, _priv = false) = false
|
96
|
+
def respond_to_missing?(_m, _p = false) = false
|
85
97
|
|
86
98
|
private
|
87
|
-
|
88
99
|
def raise_missing_ar!(which, method_name)
|
89
100
|
raise(
|
90
101
|
"#{which} requires ActiveRecord (tried to call ##{method_name}). " \
|
91
102
|
'Enable `use_inbox` only in apps with ActiveRecord, or add ' \
|
92
|
-
'`gem "activerecord"` to your Gemfile.'
|
103
|
+
'`gem \"activerecord\"` to your Gemfile.'
|
93
104
|
)
|
94
105
|
end
|
95
106
|
end
|
@@ -3,80 +3,82 @@
|
|
3
3
|
begin
|
4
4
|
require 'active_record'
|
5
5
|
rescue LoadError
|
6
|
-
# No-op;
|
6
|
+
# No-op; shim defined below.
|
7
7
|
end
|
8
8
|
|
9
9
|
module JetstreamBridge
|
10
|
-
# Default Outbox model when `use_outbox` is enabled.
|
11
|
-
# Works with event-centric columns and stays compatible with legacy resource_* fields.
|
12
10
|
if defined?(ActiveRecord::Base)
|
13
11
|
class OutboxEvent < ActiveRecord::Base
|
14
12
|
self.table_name = 'jetstream_outbox_events'
|
15
13
|
|
16
14
|
class << self
|
17
|
-
|
15
|
+
# Safe column presence check that never boots a connection during class load.
|
16
|
+
def has_column?(name)
|
17
|
+
return false unless ar_connected?
|
18
|
+
connection.schema_cache.columns_hash(table_name).key?(name.to_s)
|
19
|
+
rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::NoDatabaseError
|
20
|
+
false
|
21
|
+
end
|
18
22
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
23
|
+
def ar_connected?
|
24
|
+
# Avoid creating a connection; rescue if pool isn't set yet.
|
25
|
+
ActiveRecord::Base.connected? && connection_pool.active_connection?
|
26
|
+
rescue
|
27
|
+
false
|
22
28
|
end
|
23
29
|
end
|
24
30
|
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
if column?(:headers)
|
30
|
-
serialize :headers, coder: JSON unless attribute_json?(:headers)
|
31
|
-
end
|
31
|
+
# ---- Validations guarded by safe schema checks (no with_options) ----
|
32
|
+
validates :payload,
|
33
|
+
presence: true,
|
34
|
+
if: -> { self.class.has_column?(:payload) }
|
32
35
|
|
33
|
-
#
|
34
|
-
validates :
|
36
|
+
# Preferred path when event_id exists
|
37
|
+
validates :event_id,
|
38
|
+
presence: true,
|
39
|
+
uniqueness: true,
|
40
|
+
if: -> { self.class.has_column?(:event_id) }
|
35
41
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
validates :resource_id, presence: true, if: -> { self.class.column?(:resource_id) }
|
41
|
-
validates :event_type, presence: true, if: -> { self.class.column?(:event_type) }
|
42
|
-
end
|
42
|
+
# Fallback legacy fields when event_id is absent
|
43
|
+
validates :resource_type,
|
44
|
+
presence: true,
|
45
|
+
if: -> { !self.class.has_column?(:event_id) && self.class.has_column?(:resource_type) }
|
43
46
|
|
44
|
-
validates :
|
47
|
+
validates :resource_id,
|
48
|
+
presence: true,
|
49
|
+
if: -> { !self.class.has_column?(:event_id) && self.class.has_column?(:resource_id) }
|
45
50
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
51
|
+
validates :event_type,
|
52
|
+
presence: true,
|
53
|
+
if: -> { !self.class.has_column?(:event_id) && self.class.has_column?(:event_type) }
|
50
54
|
|
51
|
-
|
52
|
-
|
53
|
-
|
55
|
+
validates :subject,
|
56
|
+
presence: true,
|
57
|
+
if: -> { self.class.has_column?(:subject) }
|
54
58
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
scope :failed, -> { where(status: 'failed') }, if: -> { column?(:status) }
|
59
|
-
scope :sent, -> { where(status: 'sent') }, if: -> { column?(:status) }
|
60
|
-
scope :ready_to_send, -> { where(status: %w[pending failed]) }, if: -> { column?(:status) }
|
59
|
+
validates :attempts,
|
60
|
+
numericality: { only_integer: true, greater_than_or_equal_to: 0 },
|
61
|
+
if: -> { self.class.has_column?(:attempts) }
|
61
62
|
|
63
|
+
# ---- Defaults that do not require schema at load time ----
|
62
64
|
before_validation do
|
63
65
|
now = Time.now.utc
|
64
|
-
self.status
|
65
|
-
self.enqueued_at ||= now
|
66
|
-
self.attempts
|
66
|
+
self.status ||= 'pending' if self.class.has_column?(:status) && status.blank?
|
67
|
+
self.enqueued_at ||= now if self.class.has_column?(:enqueued_at) && enqueued_at.blank?
|
68
|
+
self.attempts = 0 if self.class.has_column?(:attempts) && attempts.nil?
|
67
69
|
end
|
68
70
|
|
69
|
-
# Helpers
|
71
|
+
# ---- Helpers ----
|
70
72
|
def mark_sent!
|
71
73
|
now = Time.now.utc
|
72
|
-
self.status = 'sent' if self.class.
|
73
|
-
self.sent_at = now if self.class.
|
74
|
+
self.status = 'sent' if self.class.has_column?(:status)
|
75
|
+
self.sent_at = now if self.class.has_column?(:sent_at)
|
74
76
|
save!
|
75
77
|
end
|
76
78
|
|
77
79
|
def mark_failed!(err_msg)
|
78
|
-
self.status = 'failed' if self.class.
|
79
|
-
self.last_error = err_msg if self.class.
|
80
|
+
self.status = 'failed' if self.class.has_column?(:status)
|
81
|
+
self.last_error = err_msg if self.class.has_column?(:last_error)
|
80
82
|
save!
|
81
83
|
end
|
82
84
|
|
@@ -90,14 +92,14 @@ module JetstreamBridge
|
|
90
92
|
end
|
91
93
|
end
|
92
94
|
else
|
93
|
-
# Shim:
|
95
|
+
# Shim: loud failure if AR isn't present but someone calls the model.
|
94
96
|
class OutboxEvent
|
95
97
|
class << self
|
96
98
|
def method_missing(method_name, *_args, &_block)
|
97
99
|
raise_missing_ar!('Outbox', method_name)
|
98
100
|
end
|
99
101
|
|
100
|
-
def respond_to_missing?(
|
102
|
+
def respond_to_missing?(_m, _p = false) = false
|
101
103
|
|
102
104
|
private
|
103
105
|
|
@@ -1,10 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
# lib/jetstream_bridge/railtie.rb
|
5
4
|
module JetstreamBridge
|
6
|
-
# Railtie for JetstreamBridge.
|
7
5
|
class Railtie < ::Rails::Railtie
|
6
|
+
initializer 'jetstream_bridge.defer_model_tweaks' do
|
7
|
+
ActiveSupport.on_load(:active_record) do
|
8
|
+
ActiveSupport::Reloader.to_prepare do
|
9
|
+
# Skip if not connected (e.g., non-DB rake tasks)
|
10
|
+
begin
|
11
|
+
next unless ActiveRecord::Base.connected?
|
12
|
+
rescue StandardError
|
13
|
+
next
|
14
|
+
end
|
15
|
+
|
16
|
+
[JetstreamBridge::OutboxEvent, JetstreamBridge::InboxEvent].each do |klass|
|
17
|
+
next unless klass.table_exists?
|
18
|
+
|
19
|
+
%w[payload headers].each do |attr|
|
20
|
+
next unless klass.columns_hash.key?(attr)
|
21
|
+
|
22
|
+
# Only add serialize if the column is not JSON/JSONB
|
23
|
+
type = klass.type_for_attribute(attr)
|
24
|
+
klass.serialize attr.to_sym, coder: JSON unless type&.json?
|
25
|
+
end
|
26
|
+
rescue ActiveRecord::StatementInvalid, ActiveRecord::ConnectionNotEstablished
|
27
|
+
# Ignore in tasks/environments without DB
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
8
33
|
rake_tasks do
|
9
34
|
load File.expand_path('tasks/install.rake', __dir__)
|
10
35
|
end
|