tina4ruby 3.11.13 → 3.11.15
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 +80 -80
- data/LICENSE.txt +21 -21
- data/README.md +137 -137
- data/exe/tina4ruby +5 -5
- data/lib/tina4/ai.rb +696 -696
- data/lib/tina4/api.rb +189 -189
- data/lib/tina4/auth.rb +305 -305
- data/lib/tina4/auto_crud.rb +244 -244
- data/lib/tina4/cache.rb +154 -154
- data/lib/tina4/cli.rb +1449 -1449
- data/lib/tina4/constants.rb +46 -46
- data/lib/tina4/container.rb +74 -74
- data/lib/tina4/cors.rb +74 -74
- data/lib/tina4/crud.rb +692 -692
- data/lib/tina4/database/sqlite3_adapter.rb +165 -165
- data/lib/tina4/database.rb +625 -625
- data/lib/tina4/database_result.rb +208 -208
- data/lib/tina4/debug.rb +8 -8
- data/lib/tina4/dev.rb +14 -14
- data/lib/tina4/dev_admin.rb +935 -935
- data/lib/tina4/dev_mailbox.rb +191 -191
- data/lib/tina4/drivers/firebird_driver.rb +124 -110
- data/lib/tina4/drivers/mongodb_driver.rb +561 -561
- data/lib/tina4/drivers/mssql_driver.rb +112 -112
- data/lib/tina4/drivers/mysql_driver.rb +90 -90
- data/lib/tina4/drivers/odbc_driver.rb +191 -191
- data/lib/tina4/drivers/postgres_driver.rb +116 -106
- data/lib/tina4/drivers/sqlite_driver.rb +122 -122
- data/lib/tina4/env.rb +95 -95
- data/lib/tina4/error_overlay.rb +252 -252
- data/lib/tina4/events.rb +109 -109
- data/lib/tina4/field_types.rb +154 -154
- data/lib/tina4/frond.rb +2025 -2025
- data/lib/tina4/gallery/auth/meta.json +1 -1
- data/lib/tina4/gallery/auth/src/routes/api/gallery_auth.rb +114 -114
- data/lib/tina4/gallery/database/meta.json +1 -1
- data/lib/tina4/gallery/database/src/routes/api/gallery_db.rb +43 -43
- data/lib/tina4/gallery/error-overlay/meta.json +1 -1
- data/lib/tina4/gallery/error-overlay/src/routes/api/gallery_crash.rb +17 -17
- data/lib/tina4/gallery/orm/meta.json +1 -1
- data/lib/tina4/gallery/orm/src/routes/api/gallery_products.rb +16 -16
- data/lib/tina4/gallery/queue/meta.json +1 -1
- data/lib/tina4/gallery/queue/src/routes/api/gallery_queue.rb +325 -325
- data/lib/tina4/gallery/rest-api/meta.json +1 -1
- data/lib/tina4/gallery/rest-api/src/routes/api/gallery_hello.rb +14 -14
- data/lib/tina4/gallery/templates/meta.json +1 -1
- data/lib/tina4/gallery/templates/src/routes/gallery_page.rb +12 -12
- data/lib/tina4/gallery/templates/src/templates/gallery_page.twig +257 -257
- data/lib/tina4/graphql.rb +966 -966
- data/lib/tina4/health.rb +39 -39
- data/lib/tina4/html_element.rb +170 -170
- data/lib/tina4/job.rb +80 -80
- data/lib/tina4/localization.rb +168 -168
- data/lib/tina4/log.rb +203 -203
- data/lib/tina4/mcp.rb +696 -696
- data/lib/tina4/messenger.rb +587 -587
- data/lib/tina4/metrics.rb +793 -793
- data/lib/tina4/middleware.rb +445 -445
- data/lib/tina4/migration.rb +451 -451
- data/lib/tina4/orm.rb +790 -790
- data/lib/tina4/public/css/tina4.css +2463 -2463
- data/lib/tina4/public/css/tina4.min.css +1 -1
- data/lib/tina4/public/images/logo.svg +5 -5
- data/lib/tina4/public/js/frond.min.js +2 -2
- data/lib/tina4/public/js/tina4-dev-admin.js +565 -565
- data/lib/tina4/public/js/tina4-dev-admin.min.js +480 -480
- data/lib/tina4/public/js/tina4.min.js +92 -92
- data/lib/tina4/public/js/tina4js.min.js +48 -48
- data/lib/tina4/public/swagger/index.html +90 -90
- data/lib/tina4/public/swagger/oauth2-redirect.html +63 -63
- data/lib/tina4/query_builder.rb +380 -380
- data/lib/tina4/queue.rb +366 -366
- data/lib/tina4/queue_backends/kafka_backend.rb +80 -80
- data/lib/tina4/queue_backends/lite_backend.rb +298 -298
- data/lib/tina4/queue_backends/mongo_backend.rb +126 -126
- data/lib/tina4/queue_backends/rabbitmq_backend.rb +73 -73
- data/lib/tina4/rack_app.rb +817 -817
- data/lib/tina4/rate_limiter.rb +130 -130
- data/lib/tina4/request.rb +268 -255
- data/lib/tina4/response.rb +346 -346
- data/lib/tina4/response_cache.rb +551 -551
- data/lib/tina4/router.rb +406 -406
- data/lib/tina4/scss/tina4css/_alerts.scss +34 -34
- data/lib/tina4/scss/tina4css/_badges.scss +22 -22
- data/lib/tina4/scss/tina4css/_buttons.scss +69 -69
- data/lib/tina4/scss/tina4css/_cards.scss +49 -49
- data/lib/tina4/scss/tina4css/_forms.scss +156 -156
- data/lib/tina4/scss/tina4css/_grid.scss +81 -81
- data/lib/tina4/scss/tina4css/_modals.scss +84 -84
- data/lib/tina4/scss/tina4css/_nav.scss +149 -149
- data/lib/tina4/scss/tina4css/_reset.scss +94 -94
- data/lib/tina4/scss/tina4css/_tables.scss +54 -54
- data/lib/tina4/scss/tina4css/_typography.scss +55 -55
- data/lib/tina4/scss/tina4css/_utilities.scss +197 -197
- data/lib/tina4/scss/tina4css/_variables.scss +117 -117
- data/lib/tina4/scss/tina4css/base.scss +1 -1
- data/lib/tina4/scss/tina4css/colors.scss +48 -48
- data/lib/tina4/scss/tina4css/tina4.scss +17 -17
- data/lib/tina4/scss_compiler.rb +178 -178
- data/lib/tina4/seeder.rb +567 -567
- data/lib/tina4/service_runner.rb +303 -303
- data/lib/tina4/session.rb +297 -297
- data/lib/tina4/session_handlers/database_handler.rb +72 -72
- data/lib/tina4/session_handlers/file_handler.rb +67 -67
- data/lib/tina4/session_handlers/mongo_handler.rb +49 -49
- data/lib/tina4/session_handlers/redis_handler.rb +43 -43
- data/lib/tina4/session_handlers/valkey_handler.rb +43 -43
- data/lib/tina4/shutdown.rb +84 -84
- data/lib/tina4/sql_translation.rb +158 -158
- data/lib/tina4/swagger.rb +124 -124
- data/lib/tina4/template.rb +894 -894
- data/lib/tina4/templates/base.twig +26 -26
- data/lib/tina4/templates/errors/302.twig +14 -14
- data/lib/tina4/templates/errors/401.twig +9 -9
- data/lib/tina4/templates/errors/403.twig +29 -29
- data/lib/tina4/templates/errors/404.twig +29 -29
- data/lib/tina4/templates/errors/500.twig +38 -38
- data/lib/tina4/templates/errors/502.twig +9 -9
- data/lib/tina4/templates/errors/503.twig +12 -12
- data/lib/tina4/templates/errors/base.twig +37 -37
- data/lib/tina4/test_client.rb +159 -159
- data/lib/tina4/testing.rb +340 -340
- data/lib/tina4/validator.rb +174 -174
- data/lib/tina4/version.rb +1 -1
- data/lib/tina4/webserver.rb +312 -312
- data/lib/tina4/websocket.rb +343 -343
- data/lib/tina4/websocket_backplane.rb +190 -190
- data/lib/tina4/wsdl.rb +564 -564
- data/lib/tina4.rb +458 -458
- data/lib/tina4ruby.rb +4 -4
- metadata +3 -3
|
@@ -1,190 +1,190 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# WebSocket Backplane Abstraction for Tina4 Ruby.
|
|
4
|
-
#
|
|
5
|
-
# Enables broadcasting WebSocket messages across multiple server instances
|
|
6
|
-
# using a shared pub/sub channel (e.g. Redis). Without a backplane configured,
|
|
7
|
-
# broadcast() only reaches connections on the local process.
|
|
8
|
-
#
|
|
9
|
-
# Configuration via environment variables:
|
|
10
|
-
# TINA4_WS_BACKPLANE — Backend type: "redis", "nats", or "" (default: none)
|
|
11
|
-
# TINA4_WS_BACKPLANE_URL — Connection string (default: redis://localhost:6379)
|
|
12
|
-
#
|
|
13
|
-
# Usage:
|
|
14
|
-
# backplane = Tina4::WebSocketBackplane.create_backplane
|
|
15
|
-
# if backplane
|
|
16
|
-
# backplane.subscribe("chat") { |msg| relay_to_local(msg) }
|
|
17
|
-
# backplane.publish("chat", '{"user":"A","text":"hello"}')
|
|
18
|
-
# end
|
|
19
|
-
|
|
20
|
-
module Tina4
|
|
21
|
-
# Base backplane interface for scaling WebSocket broadcast across instances.
|
|
22
|
-
#
|
|
23
|
-
# Subclasses implement publish/subscribe over a shared message bus so that
|
|
24
|
-
# every server instance receives every broadcast, not just the originator.
|
|
25
|
-
class WebSocketBackplane
|
|
26
|
-
# Publish a message to all instances listening on +channel+.
|
|
27
|
-
def publish(channel, message)
|
|
28
|
-
raise NotImplementedError, "#{self.class}#publish not implemented"
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Subscribe to +channel+. The block is called with each incoming message.
|
|
32
|
-
# Runs in a background thread.
|
|
33
|
-
def subscribe(channel, &block)
|
|
34
|
-
raise NotImplementedError, "#{self.class}#subscribe not implemented"
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Stop listening on +channel+.
|
|
38
|
-
def unsubscribe(channel)
|
|
39
|
-
raise NotImplementedError, "#{self.class}#unsubscribe not implemented"
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Tear down connections and background threads.
|
|
43
|
-
def close
|
|
44
|
-
raise NotImplementedError, "#{self.class}#close not implemented"
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Factory that reads TINA4_WS_BACKPLANE and returns the appropriate
|
|
48
|
-
# backplane instance, or +nil+ if no backplane is configured.
|
|
49
|
-
#
|
|
50
|
-
# This keeps backplane usage entirely optional — callers simply check
|
|
51
|
-
# +if backplane+ before publishing.
|
|
52
|
-
def self.create_backplane(url: nil)
|
|
53
|
-
backend = ENV.fetch("TINA4_WS_BACKPLANE", "").strip.downcase
|
|
54
|
-
|
|
55
|
-
case backend
|
|
56
|
-
when "redis"
|
|
57
|
-
RedisBackplane.new(url: url)
|
|
58
|
-
when "nats"
|
|
59
|
-
NATSBackplane.new(url: url)
|
|
60
|
-
when ""
|
|
61
|
-
nil
|
|
62
|
-
else
|
|
63
|
-
raise ArgumentError, "Unknown TINA4_WS_BACKPLANE value: '#{backend}'"
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Redis pub/sub backplane.
|
|
69
|
-
#
|
|
70
|
-
# Requires the +redis+ gem (+gem install redis+). The require is deferred
|
|
71
|
-
# so the rest of Tina4 works fine without it installed — an error is raised
|
|
72
|
-
# only when this class is actually instantiated.
|
|
73
|
-
class RedisBackplane < WebSocketBackplane
|
|
74
|
-
def initialize(url: nil)
|
|
75
|
-
begin
|
|
76
|
-
require "redis"
|
|
77
|
-
rescue LoadError
|
|
78
|
-
raise LoadError,
|
|
79
|
-
"The 'redis' gem is required for RedisBackplane. " \
|
|
80
|
-
"Install it with: gem install redis"
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
@url = url || ENV.fetch("TINA4_WS_BACKPLANE_URL", "redis://localhost:6379")
|
|
84
|
-
@redis = Redis.new(url: @url)
|
|
85
|
-
@subscriber = Redis.new(url: @url)
|
|
86
|
-
@threads = {}
|
|
87
|
-
@running = true
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def publish(channel, message)
|
|
91
|
-
@redis.publish(channel, message)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def subscribe(channel, &block)
|
|
95
|
-
@threads[channel] = Thread.new do
|
|
96
|
-
@subscriber.subscribe(channel) do |on|
|
|
97
|
-
on.message do |_chan, msg|
|
|
98
|
-
block.call(msg) if @running
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def unsubscribe(channel)
|
|
105
|
-
@subscriber.unsubscribe(channel)
|
|
106
|
-
thread = @threads.delete(channel)
|
|
107
|
-
thread&.join(1)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def close
|
|
111
|
-
@running = false
|
|
112
|
-
@threads.each_value { |t| t.kill }
|
|
113
|
-
@threads.clear
|
|
114
|
-
@subscriber.close
|
|
115
|
-
@redis.close
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# NATS pub/sub backplane.
|
|
120
|
-
#
|
|
121
|
-
# Requires the +nats-pure+ gem (+gem install nats-pure+). The require is
|
|
122
|
-
# deferred so the rest of Tina4 works fine without it installed — an error
|
|
123
|
-
# is raised only when this class is actually instantiated.
|
|
124
|
-
#
|
|
125
|
-
# NATS is async-native, so we run a background thread with an event
|
|
126
|
-
# machine for the subscription listener.
|
|
127
|
-
class NATSBackplane < WebSocketBackplane
|
|
128
|
-
def initialize(url: nil)
|
|
129
|
-
begin
|
|
130
|
-
require "nats/client"
|
|
131
|
-
rescue LoadError
|
|
132
|
-
raise LoadError,
|
|
133
|
-
"The 'nats-pure' gem is required for NATSBackplane. " \
|
|
134
|
-
"Install it with: gem install nats-pure"
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
@url = url || ENV.fetch("TINA4_WS_BACKPLANE_URL", "nats://localhost:4222")
|
|
138
|
-
@subs = {}
|
|
139
|
-
@threads = {}
|
|
140
|
-
@running = true
|
|
141
|
-
@mutex = Mutex.new
|
|
142
|
-
|
|
143
|
-
# Connect to NATS in a background thread with its own event loop
|
|
144
|
-
@nats = NATS::IO::Client.new
|
|
145
|
-
@nats.connect(@url)
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def publish(channel, message)
|
|
149
|
-
@nats.publish(channel, message)
|
|
150
|
-
@nats.flush
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def subscribe(channel, &block)
|
|
154
|
-
@mutex.synchronize do
|
|
155
|
-
sid = @nats.subscribe(channel) do |msg|
|
|
156
|
-
block.call(msg.data) if @running
|
|
157
|
-
end
|
|
158
|
-
@subs[channel] = sid
|
|
159
|
-
|
|
160
|
-
# Run NATS event processing in a background thread
|
|
161
|
-
@threads[channel] ||= Thread.new do
|
|
162
|
-
loop do
|
|
163
|
-
break unless @running
|
|
164
|
-
sleep 0.01
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def unsubscribe(channel)
|
|
171
|
-
@mutex.synchronize do
|
|
172
|
-
sid = @subs.delete(channel)
|
|
173
|
-
@nats.unsubscribe(sid) if sid
|
|
174
|
-
thread = @threads.delete(channel)
|
|
175
|
-
thread&.kill
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
def close
|
|
180
|
-
@running = false
|
|
181
|
-
@mutex.synchronize do
|
|
182
|
-
@subs.each_value { |sid| @nats.unsubscribe(sid) rescue nil }
|
|
183
|
-
@subs.clear
|
|
184
|
-
@threads.each_value { |t| t.kill }
|
|
185
|
-
@threads.clear
|
|
186
|
-
end
|
|
187
|
-
@nats.close
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# WebSocket Backplane Abstraction for Tina4 Ruby.
|
|
4
|
+
#
|
|
5
|
+
# Enables broadcasting WebSocket messages across multiple server instances
|
|
6
|
+
# using a shared pub/sub channel (e.g. Redis). Without a backplane configured,
|
|
7
|
+
# broadcast() only reaches connections on the local process.
|
|
8
|
+
#
|
|
9
|
+
# Configuration via environment variables:
|
|
10
|
+
# TINA4_WS_BACKPLANE — Backend type: "redis", "nats", or "" (default: none)
|
|
11
|
+
# TINA4_WS_BACKPLANE_URL — Connection string (default: redis://localhost:6379)
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# backplane = Tina4::WebSocketBackplane.create_backplane
|
|
15
|
+
# if backplane
|
|
16
|
+
# backplane.subscribe("chat") { |msg| relay_to_local(msg) }
|
|
17
|
+
# backplane.publish("chat", '{"user":"A","text":"hello"}')
|
|
18
|
+
# end
|
|
19
|
+
|
|
20
|
+
module Tina4
|
|
21
|
+
# Base backplane interface for scaling WebSocket broadcast across instances.
|
|
22
|
+
#
|
|
23
|
+
# Subclasses implement publish/subscribe over a shared message bus so that
|
|
24
|
+
# every server instance receives every broadcast, not just the originator.
|
|
25
|
+
class WebSocketBackplane
|
|
26
|
+
# Publish a message to all instances listening on +channel+.
|
|
27
|
+
def publish(channel, message)
|
|
28
|
+
raise NotImplementedError, "#{self.class}#publish not implemented"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Subscribe to +channel+. The block is called with each incoming message.
|
|
32
|
+
# Runs in a background thread.
|
|
33
|
+
def subscribe(channel, &block)
|
|
34
|
+
raise NotImplementedError, "#{self.class}#subscribe not implemented"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Stop listening on +channel+.
|
|
38
|
+
def unsubscribe(channel)
|
|
39
|
+
raise NotImplementedError, "#{self.class}#unsubscribe not implemented"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Tear down connections and background threads.
|
|
43
|
+
def close
|
|
44
|
+
raise NotImplementedError, "#{self.class}#close not implemented"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Factory that reads TINA4_WS_BACKPLANE and returns the appropriate
|
|
48
|
+
# backplane instance, or +nil+ if no backplane is configured.
|
|
49
|
+
#
|
|
50
|
+
# This keeps backplane usage entirely optional — callers simply check
|
|
51
|
+
# +if backplane+ before publishing.
|
|
52
|
+
def self.create_backplane(url: nil)
|
|
53
|
+
backend = ENV.fetch("TINA4_WS_BACKPLANE", "").strip.downcase
|
|
54
|
+
|
|
55
|
+
case backend
|
|
56
|
+
when "redis"
|
|
57
|
+
RedisBackplane.new(url: url)
|
|
58
|
+
when "nats"
|
|
59
|
+
NATSBackplane.new(url: url)
|
|
60
|
+
when ""
|
|
61
|
+
nil
|
|
62
|
+
else
|
|
63
|
+
raise ArgumentError, "Unknown TINA4_WS_BACKPLANE value: '#{backend}'"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Redis pub/sub backplane.
|
|
69
|
+
#
|
|
70
|
+
# Requires the +redis+ gem (+gem install redis+). The require is deferred
|
|
71
|
+
# so the rest of Tina4 works fine without it installed — an error is raised
|
|
72
|
+
# only when this class is actually instantiated.
|
|
73
|
+
class RedisBackplane < WebSocketBackplane
|
|
74
|
+
def initialize(url: nil)
|
|
75
|
+
begin
|
|
76
|
+
require "redis"
|
|
77
|
+
rescue LoadError
|
|
78
|
+
raise LoadError,
|
|
79
|
+
"The 'redis' gem is required for RedisBackplane. " \
|
|
80
|
+
"Install it with: gem install redis"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
@url = url || ENV.fetch("TINA4_WS_BACKPLANE_URL", "redis://localhost:6379")
|
|
84
|
+
@redis = Redis.new(url: @url)
|
|
85
|
+
@subscriber = Redis.new(url: @url)
|
|
86
|
+
@threads = {}
|
|
87
|
+
@running = true
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def publish(channel, message)
|
|
91
|
+
@redis.publish(channel, message)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def subscribe(channel, &block)
|
|
95
|
+
@threads[channel] = Thread.new do
|
|
96
|
+
@subscriber.subscribe(channel) do |on|
|
|
97
|
+
on.message do |_chan, msg|
|
|
98
|
+
block.call(msg) if @running
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def unsubscribe(channel)
|
|
105
|
+
@subscriber.unsubscribe(channel)
|
|
106
|
+
thread = @threads.delete(channel)
|
|
107
|
+
thread&.join(1)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def close
|
|
111
|
+
@running = false
|
|
112
|
+
@threads.each_value { |t| t.kill }
|
|
113
|
+
@threads.clear
|
|
114
|
+
@subscriber.close
|
|
115
|
+
@redis.close
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# NATS pub/sub backplane.
|
|
120
|
+
#
|
|
121
|
+
# Requires the +nats-pure+ gem (+gem install nats-pure+). The require is
|
|
122
|
+
# deferred so the rest of Tina4 works fine without it installed — an error
|
|
123
|
+
# is raised only when this class is actually instantiated.
|
|
124
|
+
#
|
|
125
|
+
# NATS is async-native, so we run a background thread with an event
|
|
126
|
+
# machine for the subscription listener.
|
|
127
|
+
class NATSBackplane < WebSocketBackplane
|
|
128
|
+
def initialize(url: nil)
|
|
129
|
+
begin
|
|
130
|
+
require "nats/client"
|
|
131
|
+
rescue LoadError
|
|
132
|
+
raise LoadError,
|
|
133
|
+
"The 'nats-pure' gem is required for NATSBackplane. " \
|
|
134
|
+
"Install it with: gem install nats-pure"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
@url = url || ENV.fetch("TINA4_WS_BACKPLANE_URL", "nats://localhost:4222")
|
|
138
|
+
@subs = {}
|
|
139
|
+
@threads = {}
|
|
140
|
+
@running = true
|
|
141
|
+
@mutex = Mutex.new
|
|
142
|
+
|
|
143
|
+
# Connect to NATS in a background thread with its own event loop
|
|
144
|
+
@nats = NATS::IO::Client.new
|
|
145
|
+
@nats.connect(@url)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def publish(channel, message)
|
|
149
|
+
@nats.publish(channel, message)
|
|
150
|
+
@nats.flush
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def subscribe(channel, &block)
|
|
154
|
+
@mutex.synchronize do
|
|
155
|
+
sid = @nats.subscribe(channel) do |msg|
|
|
156
|
+
block.call(msg.data) if @running
|
|
157
|
+
end
|
|
158
|
+
@subs[channel] = sid
|
|
159
|
+
|
|
160
|
+
# Run NATS event processing in a background thread
|
|
161
|
+
@threads[channel] ||= Thread.new do
|
|
162
|
+
loop do
|
|
163
|
+
break unless @running
|
|
164
|
+
sleep 0.01
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def unsubscribe(channel)
|
|
171
|
+
@mutex.synchronize do
|
|
172
|
+
sid = @subs.delete(channel)
|
|
173
|
+
@nats.unsubscribe(sid) if sid
|
|
174
|
+
thread = @threads.delete(channel)
|
|
175
|
+
thread&.kill
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def close
|
|
180
|
+
@running = false
|
|
181
|
+
@mutex.synchronize do
|
|
182
|
+
@subs.each_value { |sid| @nats.unsubscribe(sid) rescue nil }
|
|
183
|
+
@subs.clear
|
|
184
|
+
@threads.each_value { |t| t.kill }
|
|
185
|
+
@threads.clear
|
|
186
|
+
end
|
|
187
|
+
@nats.close
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|