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,165 +1,165 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Tina4
|
|
4
|
-
module Adapters
|
|
5
|
-
class Sqlite3Adapter
|
|
6
|
-
attr_reader :connection, :db_path
|
|
7
|
-
|
|
8
|
-
def initialize(connection_string = nil)
|
|
9
|
-
@connection = nil
|
|
10
|
-
@db_path = nil
|
|
11
|
-
@in_transaction = false
|
|
12
|
-
connect(connection_string) if connection_string
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def connect(connection_string)
|
|
16
|
-
require "sqlite3"
|
|
17
|
-
@db_path = self.class.resolve_path(connection_string)
|
|
18
|
-
@connection = SQLite3::Database.new(@db_path)
|
|
19
|
-
@connection.results_as_hash = true
|
|
20
|
-
@connection.execute("PRAGMA journal_mode=WAL")
|
|
21
|
-
@connection.execute("PRAGMA foreign_keys=ON")
|
|
22
|
-
self
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Resolve a SQLite URL / path against the project root (cwd).
|
|
26
|
-
# Convention matches Tina4::Drivers::SqliteDriver.resolve_path —
|
|
27
|
-
# three slashes = relative, four = absolute, drive letter = Windows abs.
|
|
28
|
-
def self.resolve_path(connection_string)
|
|
29
|
-
return ":memory:" if connection_string == "sqlite::memory:" || connection_string == "sqlite:///:memory:"
|
|
30
|
-
|
|
31
|
-
raw = connection_string.to_s
|
|
32
|
-
.sub(/^sqlite3?:\/\/\//, "")
|
|
33
|
-
.sub(/^sqlite3?:\/\//, "")
|
|
34
|
-
.sub(/^sqlite3?:/, "")
|
|
35
|
-
return ":memory:" if raw == ":memory:"
|
|
36
|
-
|
|
37
|
-
is_windows_abs = raw.match?(/^[A-Za-z]:[\/\\]/)
|
|
38
|
-
is_unix_abs = raw.start_with?("/")
|
|
39
|
-
|
|
40
|
-
if is_windows_abs || is_unix_abs
|
|
41
|
-
raw
|
|
42
|
-
else
|
|
43
|
-
resolved = File.join(Dir.pwd, raw)
|
|
44
|
-
parent = File.dirname(resolved)
|
|
45
|
-
require "fileutils"
|
|
46
|
-
FileUtils.mkdir_p(parent) unless File.directory?(parent)
|
|
47
|
-
resolved
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def close
|
|
52
|
-
@connection.close if @connection
|
|
53
|
-
@connection = nil
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def connected?
|
|
57
|
-
!@connection.nil? && !@connection.closed?
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Execute a query and return rows as array of symbol-keyed hashes
|
|
61
|
-
def query(sql, params = [])
|
|
62
|
-
results = @connection.execute(sql, params)
|
|
63
|
-
results.map { |row| symbolize_keys(row) }
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Paginated fetch
|
|
67
|
-
def fetch(sql, limit = 100, offset = nil)
|
|
68
|
-
effective_sql = sql
|
|
69
|
-
if limit
|
|
70
|
-
effective_sql = "#{sql} LIMIT #{limit}"
|
|
71
|
-
effective_sql += " OFFSET #{offset}" if offset && offset > 0
|
|
72
|
-
end
|
|
73
|
-
query(effective_sql)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# Execute DDL or DML without returning rows
|
|
77
|
-
def exec(sql, params = [])
|
|
78
|
-
@connection.execute(sql, params)
|
|
79
|
-
{ affected_rows: @connection.changes }
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Check if a table exists
|
|
83
|
-
def table_exists?(table)
|
|
84
|
-
rows = query(
|
|
85
|
-
"SELECT name FROM sqlite_master WHERE type='table' AND name = ?",
|
|
86
|
-
[table.to_s]
|
|
87
|
-
)
|
|
88
|
-
!rows.empty?
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Get column metadata for a table
|
|
92
|
-
def columns(table)
|
|
93
|
-
query("PRAGMA table_info(#{table})").map do |r|
|
|
94
|
-
{
|
|
95
|
-
name: r[:name],
|
|
96
|
-
type: r[:type],
|
|
97
|
-
nullable: r[:notnull] == 0,
|
|
98
|
-
default: r[:dflt_value],
|
|
99
|
-
primary_key: r[:pk] == 1
|
|
100
|
-
}
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
# List all user tables
|
|
105
|
-
def tables
|
|
106
|
-
rows = query("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
|
|
107
|
-
rows.map { |r| r[:name] }
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Get last inserted row id
|
|
111
|
-
def last_insert_id
|
|
112
|
-
@connection.last_insert_row_id
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Transaction support
|
|
116
|
-
def begin_transaction
|
|
117
|
-
return if @in_transaction
|
|
118
|
-
@connection.execute("BEGIN TRANSACTION")
|
|
119
|
-
@in_transaction = true
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def commit
|
|
123
|
-
return unless @in_transaction
|
|
124
|
-
@connection.execute("COMMIT")
|
|
125
|
-
@in_transaction = false
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def rollback
|
|
129
|
-
return unless @in_transaction
|
|
130
|
-
@connection.execute("ROLLBACK")
|
|
131
|
-
@in_transaction = false
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def transaction
|
|
135
|
-
begin_transaction
|
|
136
|
-
yield self
|
|
137
|
-
commit
|
|
138
|
-
rescue => e
|
|
139
|
-
rollback
|
|
140
|
-
raise e
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
# Convenience: placeholder for parameterized queries
|
|
144
|
-
def placeholder
|
|
145
|
-
"?"
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def placeholders(count)
|
|
149
|
-
(["?"] * count).join(", ")
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def apply_limit(sql, limit, offset = 0)
|
|
153
|
-
"#{sql} LIMIT #{limit} OFFSET #{offset}"
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
private
|
|
157
|
-
|
|
158
|
-
def symbolize_keys(hash)
|
|
159
|
-
hash.each_with_object({}) do |(k, v), h|
|
|
160
|
-
h[k.to_s.to_sym] = v if k.is_a?(String) || k.is_a?(Symbol)
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tina4
|
|
4
|
+
module Adapters
|
|
5
|
+
class Sqlite3Adapter
|
|
6
|
+
attr_reader :connection, :db_path
|
|
7
|
+
|
|
8
|
+
def initialize(connection_string = nil)
|
|
9
|
+
@connection = nil
|
|
10
|
+
@db_path = nil
|
|
11
|
+
@in_transaction = false
|
|
12
|
+
connect(connection_string) if connection_string
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def connect(connection_string)
|
|
16
|
+
require "sqlite3"
|
|
17
|
+
@db_path = self.class.resolve_path(connection_string)
|
|
18
|
+
@connection = SQLite3::Database.new(@db_path)
|
|
19
|
+
@connection.results_as_hash = true
|
|
20
|
+
@connection.execute("PRAGMA journal_mode=WAL")
|
|
21
|
+
@connection.execute("PRAGMA foreign_keys=ON")
|
|
22
|
+
self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Resolve a SQLite URL / path against the project root (cwd).
|
|
26
|
+
# Convention matches Tina4::Drivers::SqliteDriver.resolve_path —
|
|
27
|
+
# three slashes = relative, four = absolute, drive letter = Windows abs.
|
|
28
|
+
def self.resolve_path(connection_string)
|
|
29
|
+
return ":memory:" if connection_string == "sqlite::memory:" || connection_string == "sqlite:///:memory:"
|
|
30
|
+
|
|
31
|
+
raw = connection_string.to_s
|
|
32
|
+
.sub(/^sqlite3?:\/\/\//, "")
|
|
33
|
+
.sub(/^sqlite3?:\/\//, "")
|
|
34
|
+
.sub(/^sqlite3?:/, "")
|
|
35
|
+
return ":memory:" if raw == ":memory:"
|
|
36
|
+
|
|
37
|
+
is_windows_abs = raw.match?(/^[A-Za-z]:[\/\\]/)
|
|
38
|
+
is_unix_abs = raw.start_with?("/")
|
|
39
|
+
|
|
40
|
+
if is_windows_abs || is_unix_abs
|
|
41
|
+
raw
|
|
42
|
+
else
|
|
43
|
+
resolved = File.join(Dir.pwd, raw)
|
|
44
|
+
parent = File.dirname(resolved)
|
|
45
|
+
require "fileutils"
|
|
46
|
+
FileUtils.mkdir_p(parent) unless File.directory?(parent)
|
|
47
|
+
resolved
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def close
|
|
52
|
+
@connection.close if @connection
|
|
53
|
+
@connection = nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def connected?
|
|
57
|
+
!@connection.nil? && !@connection.closed?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Execute a query and return rows as array of symbol-keyed hashes
|
|
61
|
+
def query(sql, params = [])
|
|
62
|
+
results = @connection.execute(sql, params)
|
|
63
|
+
results.map { |row| symbolize_keys(row) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Paginated fetch
|
|
67
|
+
def fetch(sql, limit = 100, offset = nil)
|
|
68
|
+
effective_sql = sql
|
|
69
|
+
if limit
|
|
70
|
+
effective_sql = "#{sql} LIMIT #{limit}"
|
|
71
|
+
effective_sql += " OFFSET #{offset}" if offset && offset > 0
|
|
72
|
+
end
|
|
73
|
+
query(effective_sql)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Execute DDL or DML without returning rows
|
|
77
|
+
def exec(sql, params = [])
|
|
78
|
+
@connection.execute(sql, params)
|
|
79
|
+
{ affected_rows: @connection.changes }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Check if a table exists
|
|
83
|
+
def table_exists?(table)
|
|
84
|
+
rows = query(
|
|
85
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name = ?",
|
|
86
|
+
[table.to_s]
|
|
87
|
+
)
|
|
88
|
+
!rows.empty?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Get column metadata for a table
|
|
92
|
+
def columns(table)
|
|
93
|
+
query("PRAGMA table_info(#{table})").map do |r|
|
|
94
|
+
{
|
|
95
|
+
name: r[:name],
|
|
96
|
+
type: r[:type],
|
|
97
|
+
nullable: r[:notnull] == 0,
|
|
98
|
+
default: r[:dflt_value],
|
|
99
|
+
primary_key: r[:pk] == 1
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# List all user tables
|
|
105
|
+
def tables
|
|
106
|
+
rows = query("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
|
|
107
|
+
rows.map { |r| r[:name] }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Get last inserted row id
|
|
111
|
+
def last_insert_id
|
|
112
|
+
@connection.last_insert_row_id
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Transaction support
|
|
116
|
+
def begin_transaction
|
|
117
|
+
return if @in_transaction
|
|
118
|
+
@connection.execute("BEGIN TRANSACTION")
|
|
119
|
+
@in_transaction = true
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def commit
|
|
123
|
+
return unless @in_transaction
|
|
124
|
+
@connection.execute("COMMIT")
|
|
125
|
+
@in_transaction = false
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def rollback
|
|
129
|
+
return unless @in_transaction
|
|
130
|
+
@connection.execute("ROLLBACK")
|
|
131
|
+
@in_transaction = false
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def transaction
|
|
135
|
+
begin_transaction
|
|
136
|
+
yield self
|
|
137
|
+
commit
|
|
138
|
+
rescue => e
|
|
139
|
+
rollback
|
|
140
|
+
raise e
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Convenience: placeholder for parameterized queries
|
|
144
|
+
def placeholder
|
|
145
|
+
"?"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def placeholders(count)
|
|
149
|
+
(["?"] * count).join(", ")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def apply_limit(sql, limit, offset = 0)
|
|
153
|
+
"#{sql} LIMIT #{limit} OFFSET #{offset}"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
|
|
158
|
+
def symbolize_keys(hash)
|
|
159
|
+
hash.each_with_object({}) do |(k, v), h|
|
|
160
|
+
h[k.to_s.to_sym] = v if k.is_a?(String) || k.is_a?(Symbol)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|