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
data/lib/tina4/cache.rb
CHANGED
|
@@ -1,154 +1,154 @@
|
|
|
1
|
-
module Tina4
|
|
2
|
-
# In-memory TTL cache with tag-based invalidation.
|
|
3
|
-
#
|
|
4
|
-
# Matches the Python / PHP / Node.js QueryCache API for cross-framework
|
|
5
|
-
# parity. Thread-safe via an internal Mutex.
|
|
6
|
-
#
|
|
7
|
-
# Usage:
|
|
8
|
-
# cache = Tina4::QueryCache.new(default_ttl: 60, max_size: 1000)
|
|
9
|
-
# cache.set("key", "value", ttl: 30, tags: ["users"])
|
|
10
|
-
# cache.get("key") # => "value"
|
|
11
|
-
# cache.clear_tag("users")
|
|
12
|
-
#
|
|
13
|
-
class QueryCache
|
|
14
|
-
CacheEntry = Struct.new(:value, :expires_at, :tags)
|
|
15
|
-
|
|
16
|
-
# @param default_ttl [Integer] default TTL in seconds (default: 300)
|
|
17
|
-
# @param max_size [Integer] maximum number of cache entries (default: 1000)
|
|
18
|
-
def initialize(default_ttl: 300, max_size: 1000)
|
|
19
|
-
@default_ttl = default_ttl
|
|
20
|
-
@max_size = max_size
|
|
21
|
-
@store = {}
|
|
22
|
-
@mutex = Mutex.new
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Store a value with optional TTL and tags.
|
|
26
|
-
#
|
|
27
|
-
# @param key [String]
|
|
28
|
-
# @param value [Object]
|
|
29
|
-
# @param ttl [Integer, nil] TTL in seconds (nil uses default)
|
|
30
|
-
# @param tags [Array<String>] optional tags for grouped invalidation
|
|
31
|
-
def set(key, value, ttl: nil, tags: [])
|
|
32
|
-
ttl ||= @default_ttl
|
|
33
|
-
expires_at = Time.now.to_f + ttl
|
|
34
|
-
|
|
35
|
-
@mutex.synchronize do
|
|
36
|
-
# Evict oldest if at capacity
|
|
37
|
-
if @store.size >= @max_size && !@store.key?(key)
|
|
38
|
-
oldest_key = @store.keys.first
|
|
39
|
-
@store.delete(oldest_key)
|
|
40
|
-
end
|
|
41
|
-
@store[key] = CacheEntry.new(value, expires_at, tags)
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# Retrieve a cached value. Returns nil if expired or missing.
|
|
46
|
-
#
|
|
47
|
-
# @param key [String]
|
|
48
|
-
# @param default [Object] value to return if key is missing
|
|
49
|
-
# @return [Object, nil]
|
|
50
|
-
def get(key, default = nil)
|
|
51
|
-
@mutex.synchronize do
|
|
52
|
-
entry = @store[key]
|
|
53
|
-
return default unless entry
|
|
54
|
-
|
|
55
|
-
if Time.now.to_f > entry.expires_at
|
|
56
|
-
@store.delete(key)
|
|
57
|
-
return default
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
entry.value
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Check if a key exists and is not expired.
|
|
65
|
-
#
|
|
66
|
-
# @param key [String]
|
|
67
|
-
# @return [Boolean]
|
|
68
|
-
def has?(key)
|
|
69
|
-
@mutex.synchronize do
|
|
70
|
-
entry = @store[key]
|
|
71
|
-
return false unless entry
|
|
72
|
-
|
|
73
|
-
if Time.now.to_f > entry.expires_at
|
|
74
|
-
@store.delete(key)
|
|
75
|
-
return false
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
true
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Delete a key from the cache.
|
|
83
|
-
#
|
|
84
|
-
# @param key [String]
|
|
85
|
-
# @return [Boolean] true if the key was present
|
|
86
|
-
def delete(key)
|
|
87
|
-
@mutex.synchronize do
|
|
88
|
-
!@store.delete(key).nil?
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# Clear all entries from the cache.
|
|
93
|
-
def clear
|
|
94
|
-
@mutex.synchronize { @store.clear }
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# Clear all entries with a given tag.
|
|
98
|
-
#
|
|
99
|
-
# @param tag [String]
|
|
100
|
-
# @return [Integer] number of entries removed
|
|
101
|
-
def clear_tag(tag)
|
|
102
|
-
@mutex.synchronize do
|
|
103
|
-
keys_to_remove = @store.select { |_k, v| v.tags.include?(tag) }.keys
|
|
104
|
-
keys_to_remove.each { |k| @store.delete(k) }
|
|
105
|
-
keys_to_remove.size
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Remove all expired entries.
|
|
110
|
-
#
|
|
111
|
-
# @return [Integer] number of entries removed
|
|
112
|
-
def sweep
|
|
113
|
-
@mutex.synchronize do
|
|
114
|
-
now = Time.now.to_f
|
|
115
|
-
keys_to_remove = @store.select { |_k, v| now > v.expires_at }.keys
|
|
116
|
-
keys_to_remove.each { |k| @store.delete(k) }
|
|
117
|
-
keys_to_remove.size
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# Fetch from cache, or compute and store.
|
|
122
|
-
#
|
|
123
|
-
# @param key [String]
|
|
124
|
-
# @param ttl [Integer] TTL in seconds
|
|
125
|
-
# @param block [Proc] factory to compute the value if not cached
|
|
126
|
-
# @return [Object]
|
|
127
|
-
def remember(key, ttl, &block)
|
|
128
|
-
cached = get(key)
|
|
129
|
-
return cached unless cached.nil?
|
|
130
|
-
|
|
131
|
-
value = block.call
|
|
132
|
-
set(key, value, ttl: ttl)
|
|
133
|
-
value
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Current number of entries in the cache.
|
|
137
|
-
#
|
|
138
|
-
# @return [Integer]
|
|
139
|
-
def size
|
|
140
|
-
@mutex.synchronize { @store.size }
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
# Generate a stable cache key from a SQL query and params.
|
|
144
|
-
# Mirrors SQLTranslator.query_key for direct use on QueryCache.
|
|
145
|
-
#
|
|
146
|
-
# @param sql [String]
|
|
147
|
-
# @param params [Array, nil]
|
|
148
|
-
# @return [String]
|
|
149
|
-
def self.query_key(sql, params = nil)
|
|
150
|
-
raw = params ? "#{sql}|#{params.inspect}" : sql
|
|
151
|
-
"query:#{Digest::SHA256.hexdigest(raw)}"
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
end
|
|
1
|
+
module Tina4
|
|
2
|
+
# In-memory TTL cache with tag-based invalidation.
|
|
3
|
+
#
|
|
4
|
+
# Matches the Python / PHP / Node.js QueryCache API for cross-framework
|
|
5
|
+
# parity. Thread-safe via an internal Mutex.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# cache = Tina4::QueryCache.new(default_ttl: 60, max_size: 1000)
|
|
9
|
+
# cache.set("key", "value", ttl: 30, tags: ["users"])
|
|
10
|
+
# cache.get("key") # => "value"
|
|
11
|
+
# cache.clear_tag("users")
|
|
12
|
+
#
|
|
13
|
+
class QueryCache
|
|
14
|
+
CacheEntry = Struct.new(:value, :expires_at, :tags)
|
|
15
|
+
|
|
16
|
+
# @param default_ttl [Integer] default TTL in seconds (default: 300)
|
|
17
|
+
# @param max_size [Integer] maximum number of cache entries (default: 1000)
|
|
18
|
+
def initialize(default_ttl: 300, max_size: 1000)
|
|
19
|
+
@default_ttl = default_ttl
|
|
20
|
+
@max_size = max_size
|
|
21
|
+
@store = {}
|
|
22
|
+
@mutex = Mutex.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Store a value with optional TTL and tags.
|
|
26
|
+
#
|
|
27
|
+
# @param key [String]
|
|
28
|
+
# @param value [Object]
|
|
29
|
+
# @param ttl [Integer, nil] TTL in seconds (nil uses default)
|
|
30
|
+
# @param tags [Array<String>] optional tags for grouped invalidation
|
|
31
|
+
def set(key, value, ttl: nil, tags: [])
|
|
32
|
+
ttl ||= @default_ttl
|
|
33
|
+
expires_at = Time.now.to_f + ttl
|
|
34
|
+
|
|
35
|
+
@mutex.synchronize do
|
|
36
|
+
# Evict oldest if at capacity
|
|
37
|
+
if @store.size >= @max_size && !@store.key?(key)
|
|
38
|
+
oldest_key = @store.keys.first
|
|
39
|
+
@store.delete(oldest_key)
|
|
40
|
+
end
|
|
41
|
+
@store[key] = CacheEntry.new(value, expires_at, tags)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Retrieve a cached value. Returns nil if expired or missing.
|
|
46
|
+
#
|
|
47
|
+
# @param key [String]
|
|
48
|
+
# @param default [Object] value to return if key is missing
|
|
49
|
+
# @return [Object, nil]
|
|
50
|
+
def get(key, default = nil)
|
|
51
|
+
@mutex.synchronize do
|
|
52
|
+
entry = @store[key]
|
|
53
|
+
return default unless entry
|
|
54
|
+
|
|
55
|
+
if Time.now.to_f > entry.expires_at
|
|
56
|
+
@store.delete(key)
|
|
57
|
+
return default
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
entry.value
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Check if a key exists and is not expired.
|
|
65
|
+
#
|
|
66
|
+
# @param key [String]
|
|
67
|
+
# @return [Boolean]
|
|
68
|
+
def has?(key)
|
|
69
|
+
@mutex.synchronize do
|
|
70
|
+
entry = @store[key]
|
|
71
|
+
return false unless entry
|
|
72
|
+
|
|
73
|
+
if Time.now.to_f > entry.expires_at
|
|
74
|
+
@store.delete(key)
|
|
75
|
+
return false
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
true
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Delete a key from the cache.
|
|
83
|
+
#
|
|
84
|
+
# @param key [String]
|
|
85
|
+
# @return [Boolean] true if the key was present
|
|
86
|
+
def delete(key)
|
|
87
|
+
@mutex.synchronize do
|
|
88
|
+
!@store.delete(key).nil?
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Clear all entries from the cache.
|
|
93
|
+
def clear
|
|
94
|
+
@mutex.synchronize { @store.clear }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Clear all entries with a given tag.
|
|
98
|
+
#
|
|
99
|
+
# @param tag [String]
|
|
100
|
+
# @return [Integer] number of entries removed
|
|
101
|
+
def clear_tag(tag)
|
|
102
|
+
@mutex.synchronize do
|
|
103
|
+
keys_to_remove = @store.select { |_k, v| v.tags.include?(tag) }.keys
|
|
104
|
+
keys_to_remove.each { |k| @store.delete(k) }
|
|
105
|
+
keys_to_remove.size
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Remove all expired entries.
|
|
110
|
+
#
|
|
111
|
+
# @return [Integer] number of entries removed
|
|
112
|
+
def sweep
|
|
113
|
+
@mutex.synchronize do
|
|
114
|
+
now = Time.now.to_f
|
|
115
|
+
keys_to_remove = @store.select { |_k, v| now > v.expires_at }.keys
|
|
116
|
+
keys_to_remove.each { |k| @store.delete(k) }
|
|
117
|
+
keys_to_remove.size
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Fetch from cache, or compute and store.
|
|
122
|
+
#
|
|
123
|
+
# @param key [String]
|
|
124
|
+
# @param ttl [Integer] TTL in seconds
|
|
125
|
+
# @param block [Proc] factory to compute the value if not cached
|
|
126
|
+
# @return [Object]
|
|
127
|
+
def remember(key, ttl, &block)
|
|
128
|
+
cached = get(key)
|
|
129
|
+
return cached unless cached.nil?
|
|
130
|
+
|
|
131
|
+
value = block.call
|
|
132
|
+
set(key, value, ttl: ttl)
|
|
133
|
+
value
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Current number of entries in the cache.
|
|
137
|
+
#
|
|
138
|
+
# @return [Integer]
|
|
139
|
+
def size
|
|
140
|
+
@mutex.synchronize { @store.size }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Generate a stable cache key from a SQL query and params.
|
|
144
|
+
# Mirrors SQLTranslator.query_key for direct use on QueryCache.
|
|
145
|
+
#
|
|
146
|
+
# @param sql [String]
|
|
147
|
+
# @param params [Array, nil]
|
|
148
|
+
# @return [String]
|
|
149
|
+
def self.query_key(sql, params = nil)
|
|
150
|
+
raw = params ? "#{sql}|#{params.inspect}" : sql
|
|
151
|
+
"query:#{Digest::SHA256.hexdigest(raw)}"
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|