tina4ruby 3.11.15 → 3.11.17

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 (134) 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 +1291 -935
  22. data/lib/tina4/dev_mailbox.rb +191 -191
  23. data/lib/tina4/drivers/firebird_driver.rb +124 -124
  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 -116
  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 +2087 -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 +871 -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/plan.rb +471 -0
  63. data/lib/tina4/project_index.rb +366 -0
  64. data/lib/tina4/public/css/tina4.css +2463 -2463
  65. data/lib/tina4/public/css/tina4.min.css +1 -1
  66. data/lib/tina4/public/images/logo.svg +5 -5
  67. data/lib/tina4/public/js/frond.min.js +2 -2
  68. data/lib/tina4/public/js/tina4-dev-admin.js +1264 -565
  69. data/lib/tina4/public/js/tina4-dev-admin.min.js +1264 -480
  70. data/lib/tina4/public/js/tina4.min.js +92 -92
  71. data/lib/tina4/public/js/tina4js.min.js +48 -48
  72. data/lib/tina4/public/swagger/index.html +90 -90
  73. data/lib/tina4/public/swagger/oauth2-redirect.html +63 -63
  74. data/lib/tina4/query_builder.rb +380 -380
  75. data/lib/tina4/queue.rb +366 -366
  76. data/lib/tina4/queue_backends/kafka_backend.rb +80 -80
  77. data/lib/tina4/queue_backends/lite_backend.rb +298 -298
  78. data/lib/tina4/queue_backends/mongo_backend.rb +126 -126
  79. data/lib/tina4/queue_backends/rabbitmq_backend.rb +73 -73
  80. data/lib/tina4/rack_app.rb +817 -817
  81. data/lib/tina4/rate_limiter.rb +130 -130
  82. data/lib/tina4/request.rb +268 -268
  83. data/lib/tina4/response.rb +346 -346
  84. data/lib/tina4/response_cache.rb +551 -551
  85. data/lib/tina4/router.rb +406 -406
  86. data/lib/tina4/scss/tina4css/_alerts.scss +34 -34
  87. data/lib/tina4/scss/tina4css/_badges.scss +22 -22
  88. data/lib/tina4/scss/tina4css/_buttons.scss +69 -69
  89. data/lib/tina4/scss/tina4css/_cards.scss +49 -49
  90. data/lib/tina4/scss/tina4css/_forms.scss +156 -156
  91. data/lib/tina4/scss/tina4css/_grid.scss +81 -81
  92. data/lib/tina4/scss/tina4css/_modals.scss +84 -84
  93. data/lib/tina4/scss/tina4css/_nav.scss +149 -149
  94. data/lib/tina4/scss/tina4css/_reset.scss +94 -94
  95. data/lib/tina4/scss/tina4css/_tables.scss +54 -54
  96. data/lib/tina4/scss/tina4css/_typography.scss +55 -55
  97. data/lib/tina4/scss/tina4css/_utilities.scss +197 -197
  98. data/lib/tina4/scss/tina4css/_variables.scss +117 -117
  99. data/lib/tina4/scss/tina4css/base.scss +1 -1
  100. data/lib/tina4/scss/tina4css/colors.scss +48 -48
  101. data/lib/tina4/scss/tina4css/tina4.scss +17 -17
  102. data/lib/tina4/scss_compiler.rb +178 -178
  103. data/lib/tina4/seeder.rb +567 -567
  104. data/lib/tina4/service_runner.rb +303 -303
  105. data/lib/tina4/session.rb +297 -297
  106. data/lib/tina4/session_handlers/database_handler.rb +72 -72
  107. data/lib/tina4/session_handlers/file_handler.rb +67 -67
  108. data/lib/tina4/session_handlers/mongo_handler.rb +49 -49
  109. data/lib/tina4/session_handlers/redis_handler.rb +43 -43
  110. data/lib/tina4/session_handlers/valkey_handler.rb +43 -43
  111. data/lib/tina4/shutdown.rb +84 -84
  112. data/lib/tina4/sql_translation.rb +158 -158
  113. data/lib/tina4/swagger.rb +124 -124
  114. data/lib/tina4/template.rb +894 -894
  115. data/lib/tina4/templates/base.twig +26 -26
  116. data/lib/tina4/templates/errors/302.twig +14 -14
  117. data/lib/tina4/templates/errors/401.twig +9 -9
  118. data/lib/tina4/templates/errors/403.twig +29 -29
  119. data/lib/tina4/templates/errors/404.twig +29 -29
  120. data/lib/tina4/templates/errors/500.twig +38 -38
  121. data/lib/tina4/templates/errors/502.twig +9 -9
  122. data/lib/tina4/templates/errors/503.twig +12 -12
  123. data/lib/tina4/templates/errors/base.twig +37 -37
  124. data/lib/tina4/test_client.rb +159 -159
  125. data/lib/tina4/testing.rb +340 -340
  126. data/lib/tina4/validator.rb +174 -174
  127. data/lib/tina4/version.rb +1 -1
  128. data/lib/tina4/webserver.rb +312 -312
  129. data/lib/tina4/websocket.rb +343 -343
  130. data/lib/tina4/websocket_backplane.rb +190 -190
  131. data/lib/tina4/wsdl.rb +564 -564
  132. data/lib/tina4.rb +460 -458
  133. data/lib/tina4ruby.rb +4 -4
  134. metadata +5 -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