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.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +80 -80
  3. data/LICENSE.txt +21 -21
  4. data/README.md +137 -137
  5. data/exe/tina4ruby +5 -5
  6. data/lib/tina4/ai.rb +696 -696
  7. data/lib/tina4/api.rb +189 -189
  8. data/lib/tina4/auth.rb +305 -305
  9. data/lib/tina4/auto_crud.rb +244 -244
  10. data/lib/tina4/cache.rb +154 -154
  11. data/lib/tina4/cli.rb +1449 -1449
  12. data/lib/tina4/constants.rb +46 -46
  13. data/lib/tina4/container.rb +74 -74
  14. data/lib/tina4/cors.rb +74 -74
  15. data/lib/tina4/crud.rb +692 -692
  16. data/lib/tina4/database/sqlite3_adapter.rb +165 -165
  17. data/lib/tina4/database.rb +625 -625
  18. data/lib/tina4/database_result.rb +208 -208
  19. data/lib/tina4/debug.rb +8 -8
  20. data/lib/tina4/dev.rb +14 -14
  21. data/lib/tina4/dev_admin.rb +935 -935
  22. data/lib/tina4/dev_mailbox.rb +191 -191
  23. data/lib/tina4/drivers/firebird_driver.rb +124 -110
  24. data/lib/tina4/drivers/mongodb_driver.rb +561 -561
  25. data/lib/tina4/drivers/mssql_driver.rb +112 -112
  26. data/lib/tina4/drivers/mysql_driver.rb +90 -90
  27. data/lib/tina4/drivers/odbc_driver.rb +191 -191
  28. data/lib/tina4/drivers/postgres_driver.rb +116 -106
  29. data/lib/tina4/drivers/sqlite_driver.rb +122 -122
  30. data/lib/tina4/env.rb +95 -95
  31. data/lib/tina4/error_overlay.rb +252 -252
  32. data/lib/tina4/events.rb +109 -109
  33. data/lib/tina4/field_types.rb +154 -154
  34. data/lib/tina4/frond.rb +2025 -2025
  35. data/lib/tina4/gallery/auth/meta.json +1 -1
  36. data/lib/tina4/gallery/auth/src/routes/api/gallery_auth.rb +114 -114
  37. data/lib/tina4/gallery/database/meta.json +1 -1
  38. data/lib/tina4/gallery/database/src/routes/api/gallery_db.rb +43 -43
  39. data/lib/tina4/gallery/error-overlay/meta.json +1 -1
  40. data/lib/tina4/gallery/error-overlay/src/routes/api/gallery_crash.rb +17 -17
  41. data/lib/tina4/gallery/orm/meta.json +1 -1
  42. data/lib/tina4/gallery/orm/src/routes/api/gallery_products.rb +16 -16
  43. data/lib/tina4/gallery/queue/meta.json +1 -1
  44. data/lib/tina4/gallery/queue/src/routes/api/gallery_queue.rb +325 -325
  45. data/lib/tina4/gallery/rest-api/meta.json +1 -1
  46. data/lib/tina4/gallery/rest-api/src/routes/api/gallery_hello.rb +14 -14
  47. data/lib/tina4/gallery/templates/meta.json +1 -1
  48. data/lib/tina4/gallery/templates/src/routes/gallery_page.rb +12 -12
  49. data/lib/tina4/gallery/templates/src/templates/gallery_page.twig +257 -257
  50. data/lib/tina4/graphql.rb +966 -966
  51. data/lib/tina4/health.rb +39 -39
  52. data/lib/tina4/html_element.rb +170 -170
  53. data/lib/tina4/job.rb +80 -80
  54. data/lib/tina4/localization.rb +168 -168
  55. data/lib/tina4/log.rb +203 -203
  56. data/lib/tina4/mcp.rb +696 -696
  57. data/lib/tina4/messenger.rb +587 -587
  58. data/lib/tina4/metrics.rb +793 -793
  59. data/lib/tina4/middleware.rb +445 -445
  60. data/lib/tina4/migration.rb +451 -451
  61. data/lib/tina4/orm.rb +790 -790
  62. data/lib/tina4/public/css/tina4.css +2463 -2463
  63. data/lib/tina4/public/css/tina4.min.css +1 -1
  64. data/lib/tina4/public/images/logo.svg +5 -5
  65. data/lib/tina4/public/js/frond.min.js +2 -2
  66. data/lib/tina4/public/js/tina4-dev-admin.js +565 -565
  67. data/lib/tina4/public/js/tina4-dev-admin.min.js +480 -480
  68. data/lib/tina4/public/js/tina4.min.js +92 -92
  69. data/lib/tina4/public/js/tina4js.min.js +48 -48
  70. data/lib/tina4/public/swagger/index.html +90 -90
  71. data/lib/tina4/public/swagger/oauth2-redirect.html +63 -63
  72. data/lib/tina4/query_builder.rb +380 -380
  73. data/lib/tina4/queue.rb +366 -366
  74. data/lib/tina4/queue_backends/kafka_backend.rb +80 -80
  75. data/lib/tina4/queue_backends/lite_backend.rb +298 -298
  76. data/lib/tina4/queue_backends/mongo_backend.rb +126 -126
  77. data/lib/tina4/queue_backends/rabbitmq_backend.rb +73 -73
  78. data/lib/tina4/rack_app.rb +817 -817
  79. data/lib/tina4/rate_limiter.rb +130 -130
  80. data/lib/tina4/request.rb +268 -255
  81. data/lib/tina4/response.rb +346 -346
  82. data/lib/tina4/response_cache.rb +551 -551
  83. data/lib/tina4/router.rb +406 -406
  84. data/lib/tina4/scss/tina4css/_alerts.scss +34 -34
  85. data/lib/tina4/scss/tina4css/_badges.scss +22 -22
  86. data/lib/tina4/scss/tina4css/_buttons.scss +69 -69
  87. data/lib/tina4/scss/tina4css/_cards.scss +49 -49
  88. data/lib/tina4/scss/tina4css/_forms.scss +156 -156
  89. data/lib/tina4/scss/tina4css/_grid.scss +81 -81
  90. data/lib/tina4/scss/tina4css/_modals.scss +84 -84
  91. data/lib/tina4/scss/tina4css/_nav.scss +149 -149
  92. data/lib/tina4/scss/tina4css/_reset.scss +94 -94
  93. data/lib/tina4/scss/tina4css/_tables.scss +54 -54
  94. data/lib/tina4/scss/tina4css/_typography.scss +55 -55
  95. data/lib/tina4/scss/tina4css/_utilities.scss +197 -197
  96. data/lib/tina4/scss/tina4css/_variables.scss +117 -117
  97. data/lib/tina4/scss/tina4css/base.scss +1 -1
  98. data/lib/tina4/scss/tina4css/colors.scss +48 -48
  99. data/lib/tina4/scss/tina4css/tina4.scss +17 -17
  100. data/lib/tina4/scss_compiler.rb +178 -178
  101. data/lib/tina4/seeder.rb +567 -567
  102. data/lib/tina4/service_runner.rb +303 -303
  103. data/lib/tina4/session.rb +297 -297
  104. data/lib/tina4/session_handlers/database_handler.rb +72 -72
  105. data/lib/tina4/session_handlers/file_handler.rb +67 -67
  106. data/lib/tina4/session_handlers/mongo_handler.rb +49 -49
  107. data/lib/tina4/session_handlers/redis_handler.rb +43 -43
  108. data/lib/tina4/session_handlers/valkey_handler.rb +43 -43
  109. data/lib/tina4/shutdown.rb +84 -84
  110. data/lib/tina4/sql_translation.rb +158 -158
  111. data/lib/tina4/swagger.rb +124 -124
  112. data/lib/tina4/template.rb +894 -894
  113. data/lib/tina4/templates/base.twig +26 -26
  114. data/lib/tina4/templates/errors/302.twig +14 -14
  115. data/lib/tina4/templates/errors/401.twig +9 -9
  116. data/lib/tina4/templates/errors/403.twig +29 -29
  117. data/lib/tina4/templates/errors/404.twig +29 -29
  118. data/lib/tina4/templates/errors/500.twig +38 -38
  119. data/lib/tina4/templates/errors/502.twig +9 -9
  120. data/lib/tina4/templates/errors/503.twig +12 -12
  121. data/lib/tina4/templates/errors/base.twig +37 -37
  122. data/lib/tina4/test_client.rb +159 -159
  123. data/lib/tina4/testing.rb +340 -340
  124. data/lib/tina4/validator.rb +174 -174
  125. data/lib/tina4/version.rb +1 -1
  126. data/lib/tina4/webserver.rb +312 -312
  127. data/lib/tina4/websocket.rb +343 -343
  128. data/lib/tina4/websocket_backplane.rb +190 -190
  129. data/lib/tina4/wsdl.rb +564 -564
  130. data/lib/tina4.rb +458 -458
  131. data/lib/tina4ruby.rb +4 -4
  132. 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