tina4ruby 3.11.11 → 3.11.13

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27b0a9fcfbd4fb2873e490aac1cc5743784214da09d8f7be199adcb7aefbd159
4
- data.tar.gz: af42dff46d929b795e2139a439d1a23c2e0822fed7616e7abe03222673b02539
3
+ metadata.gz: ca811aa2cb83ea336ce8be177f6f3688d014d6a7cb2ad4b90304a752a163d6aa
4
+ data.tar.gz: 51208212f5cf5b7c49fde4f77d80b042a99d65ec615f9e422228299dd04993c9
5
5
  SHA512:
6
- metadata.gz: 316581b5698cabf6953d7af3e7aaec76535d22a25b9fb5332729553f5f0aa1e9edcb3078fe01435818340ea4464bf74b52a8e96b8f40ba79fc528c54e6298aa3
7
- data.tar.gz: 80a0bfab88b1ffeb4d93576e47bf6a093ae7f7c3b2642eb897e479d5e06e2b78adf55067c330f3024b750dd961a829933621ce853f692a21fe949622e548b8c1
6
+ metadata.gz: 8baf46841c98a50c37b767ce9f963404c04250802e58b33c97d4927847fd7895098e243affbde71caae19d872fd6238b388c2d08affc5d157f9b9a51aae73e46
7
+ data.tar.gz: bcc3047216b640f05573f7f450f6f591b8e3467979f12c503f80d2a652cdbb17cc513ed373b7e881b6c1305502b420910941bba6aefba401d827e45b21d918ec
data/README.md CHANGED
@@ -4,11 +4,11 @@
4
4
  <h1 align="center">Tina4 Ruby</h1>
5
5
  <h3 align="center">TINA4 — The Intelligent Native Application 4ramework</h3>
6
6
  <p align="center"><em>Simple. Fast. Human. &nbsp;|&nbsp; Built for AI. Built for you.</em></p>
7
- <p align="center">54 built-in features. Zero runtime dependencies. One require, everything works.</p>
7
+ <p align="center">55 built-in features. Zero runtime dependencies. One require, everything works.</p>
8
8
  <p align="center">
9
9
  <a href="https://rubygems.org/gems/tina4ruby"><img src="https://img.shields.io/gem/v/tina4ruby?color=7b1fa2&label=RubyGems" alt="RubyGems"></a>
10
- <img src="https://img.shields.io/badge/tests-1%2C578%20passing-brightgreen" alt="Tests">
11
- <img src="https://img.shields.io/badge/features-54-blue" alt="Features">
10
+ <img src="https://img.shields.io/badge/tests-2%2C508%20passing-brightgreen" alt="Tests">
11
+ <img src="https://img.shields.io/badge/features-55-blue" alt="Features">
12
12
  <img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero Deps">
13
13
  <a href="https://tina4.com"><img src="https://img.shields.io/badge/docs-tina4.com-7b1fa2" alt="Docs"></a>
14
14
  </p>
@@ -18,6 +18,12 @@
18
18
  ## Quick Start
19
19
 
20
20
  ```bash
21
+ # With the Tina4 CLI (recommended — enables SCSS + live reload)
22
+ cargo install tina4 # or grab a binary from https://github.com/tina4stack/tina4/releases
23
+ tina4 init ruby ./my-app
24
+ cd my-app && tina4 serve
25
+
26
+ # Without the Tina4 CLI
21
27
  gem install tina4ruby
22
28
  tina4ruby init my-app
23
29
  cd my-app && tina4ruby serve
@@ -60,7 +66,7 @@ db = Tina4::Database.new("sqlite://app.db")
60
66
  | **Developer Tools** (7) | Dev dashboard (11 tabs), dev toolbar, error overlay (Catppuccin Mocha), dev mailbox, hot reload + CSS hot-reload, code metrics (complexity, coupling, maintainability), AI context installer (7 tools) |
61
67
  | **Utilities** (7) | DI container (transient + singleton), HtmlElement builder, inline testing (`@tests` decorator), i18n (6 languages), Swagger/OpenAPI auto-generation, CLI scaffolding (`generate model/route/migration/middleware`), structured logging |
62
68
 
63
- **1,793 tests. Zero runtime dependencies. Full parity across Python, PHP, Ruby, and Node.js.**
69
+ **2,508 tests. Zero runtime dependencies. Full parity across Python, PHP, Ruby, and Node.js.**
64
70
 
65
71
  ---
66
72
 
@@ -82,10 +88,10 @@ Benchmarked with `wrk` — 5,000 requests, 50 concurrent, median of 3 runs:
82
88
 
83
89
  | Framework | JSON req/s | Deps | Features |
84
90
  |-----------|-----------|------|----------|
85
- | **Tina4 Ruby** | **10,243** | 0 | 54 |
91
+ | **Tina4 Ruby** | **10,243** | 0 | 55 |
86
92
  | Sinatra | 9,548 | 5+ | ~4 |
87
93
 
88
- Tina4 Ruby outperforms Sinatra while delivering **54 features vs ~4** — with zero runtime dependencies.
94
+ Tina4 Ruby outperforms Sinatra while delivering **55 features vs ~4** — with zero runtime dependencies.
89
95
 
90
96
  **Across all 4 Tina4 implementations:**
91
97
 
@@ -93,21 +99,21 @@ Tina4 Ruby outperforms Sinatra while delivering **54 features vs ~4** — with z
93
99
  |---|--------|-----|------|---------|
94
100
  | **JSON req/s** | 6,508 | 29,293 | 10,243 | 84,771 |
95
101
  | **Dependencies** | 0 | 0 | 0 | 0 |
96
- | **Features** | 54 | 54 | 54 | 54 |
102
+ | **Features** | 55 | 55 | 55 | 55 |
97
103
 
98
104
  ---
99
105
 
100
106
  ## Cross-Framework Parity
101
107
 
102
- Tina4 ships identical features across four languages — same architecture, same conventions, same 54 features:
108
+ Tina4 ships identical features across four languages — same architecture, same conventions, same 55 features:
103
109
 
104
110
  | | Python | PHP | Ruby | Node.js |
105
111
  |---|--------|-----|------|---------|
106
- | **Package** | `tina4-python` | `tina4stack/tina4php` | `tina4ruby` | `tina4-nodejs` |
107
- | **Tests** | 2,066 | 1,427 | 1,793 | 1,950 |
108
- | **Default port** | 7145 | 7146 | 7147 | 7148 |
112
+ | **Package** | `tina4-python` | `tina4stack/tina4php` | `tina4ruby` | `@tina4/core` |
113
+ | **Tests (v3.11.12)** | 2,281 | 2,073 | 2,508 | 2,897 |
114
+ | **Default port** | 7146 | 7145 | 7147 | 7148 |
109
115
 
110
- **7,236 tests** across all 4 frameworks. See [tina4.com](https://tina4.com).
116
+ **~9,700 tests** across all 4 frameworks. See [tina4.com](https://tina4.com).
111
117
 
112
118
  ---
113
119
 
@@ -14,7 +14,7 @@ module Tina4
14
14
 
15
15
  def connect(connection_string)
16
16
  require "sqlite3"
17
- @db_path = connection_string.to_s.sub(/^sqlite3?:\/\//, "").sub(/^sqlite3?:/, "")
17
+ @db_path = self.class.resolve_path(connection_string)
18
18
  @connection = SQLite3::Database.new(@db_path)
19
19
  @connection.results_as_hash = true
20
20
  @connection.execute("PRAGMA journal_mode=WAL")
@@ -22,6 +22,32 @@ module Tina4
22
22
  self
23
23
  end
24
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
+
25
51
  def close
26
52
  @connection.close if @connection
27
53
  @connection = nil
@@ -7,10 +7,7 @@ module Tina4
7
7
 
8
8
  def connect(connection_string, username: nil, password: nil)
9
9
  require "sqlite3"
10
- db_path = connection_string.sub(/^sqlite:\/\//, "").sub(/^sqlite:/, "")
11
- # Windows: sqlite:///C:/Users/app.db → /C:/Users/app.db after stripping scheme.
12
- # The leading / before the drive letter must be removed.
13
- db_path = db_path[1..] if db_path.match?(/^\/[A-Za-z]:/)
10
+ db_path = self.class.resolve_path(connection_string)
14
11
 
15
12
  @connection = SQLite3::Database.new(db_path)
16
13
  @connection.results_as_hash = true
@@ -18,6 +15,42 @@ module Tina4
18
15
  @connection.execute("PRAGMA foreign_keys=ON")
19
16
  end
20
17
 
18
+ # Resolve a SQLite URL / path against the project root (cwd).
19
+ #
20
+ # Convention (matches tina4-python, tina4-php, tina4-nodejs):
21
+ # sqlite::memory: → :memory:
22
+ # sqlite:///:memory: → :memory:
23
+ # sqlite:///app.db → {cwd}/app.db (relative)
24
+ # sqlite:///data/app.db → {cwd}/data/app.db (relative; auto-mkdir under cwd)
25
+ # sqlite:////var/data/app.db → /var/data/app.db (absolute; no auto-mkdir)
26
+ # sqlite:///C:/Users/app.db → C:/Users/app.db (Windows absolute)
27
+ #
28
+ # Never mkdir outside cwd — that was the root cause of the
29
+ # "Read-only file system: '/data'" crash on macOS.
30
+ def self.resolve_path(connection_string)
31
+ return ":memory:" if connection_string == "sqlite::memory:" || connection_string == "sqlite:///:memory:"
32
+
33
+ # Strip the scheme + up to three slashes, preserving a potential fourth
34
+ # slash (absolute) or drive letter.
35
+ raw = connection_string.sub(/^sqlite:\/\/\//, "").sub(/^sqlite:\/\//, "").sub(/^sqlite:/, "")
36
+ return ":memory:" if raw == ":memory:"
37
+
38
+ is_windows_abs = raw.match?(/^[A-Za-z]:[\/\\]/)
39
+ is_unix_abs = raw.start_with?("/")
40
+
41
+ if is_windows_abs || is_unix_abs
42
+ # Absolute — trust the user; don't auto-mkdir outside cwd.
43
+ raw
44
+ else
45
+ # Relative — resolve under cwd; auto-mkdir parent dir.
46
+ resolved = File.join(Dir.pwd, raw)
47
+ parent = File.dirname(resolved)
48
+ require "fileutils"
49
+ FileUtils.mkdir_p(parent) unless File.directory?(parent)
50
+ resolved
51
+ end
52
+ end
53
+
21
54
  def close
22
55
  @connection&.close
23
56
  end
@@ -560,13 +560,13 @@ module Tina4
560
560
  var maxAttempts = 5;
561
561
  function pollRoute() {
562
562
  fetch(tryUrl, {method: 'HEAD'}).then(function() {
563
- window.location.href = tryUrl;
563
+ window.open(tryUrl, '_blank');
564
564
  }).catch(function() {
565
565
  attempts++;
566
566
  if (attempts < maxAttempts) {
567
567
  setTimeout(pollRoute, 500);
568
568
  } else {
569
- window.location.href = tryUrl;
569
+ window.open(tryUrl, '_blank');
570
570
  }
571
571
  });
572
572
  }
data/lib/tina4/router.rb CHANGED
@@ -98,6 +98,27 @@ module Tina4
98
98
  p
99
99
  end
100
100
 
101
+ # Supported typed-parameter constraints. Mirrored verbatim in
102
+ # tina4-python / tina4-php / tina4-nodejs for cross-framework parity.
103
+ #
104
+ # Any type name not in this table raises ``ArgumentError`` at route
105
+ # registration — we never silently fall through to the default matcher,
106
+ # because a typo like ``{id:inetger}`` would otherwise match anything
107
+ # and create a security footgun (see tina4-book#125).
108
+ PARAM_TYPE_PATTERNS = {
109
+ "string" => "[^/]+", # default, any non-slash segment
110
+ "int" => '\d+',
111
+ "integer" => '\d+',
112
+ "float" => '[\d.]+',
113
+ "number" => '[\d.]+',
114
+ "alpha" => "[A-Za-z]+", # letters only
115
+ "alnum" => "[A-Za-z0-9]+", # letters + digits
116
+ "slug" => "[a-z0-9-]+", # URL slug
117
+ "uuid" => "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}",
118
+ "path" => ".+", # greedy
119
+ ".*" => ".+",
120
+ }.freeze
121
+
101
122
  def compile_pattern(path)
102
123
  return Regexp.new("\\A/\\z") if path == "/"
103
124
 
@@ -113,23 +134,19 @@ module Tina4
113
134
  # to match Python/PHP/Node parity (docs say `request.params["*"]`).
114
135
  @param_names << { name: :"*", type: "path" }
115
136
  '(.+)'
116
- elsif part =~ /\A\{(\w+)(?::(\w+))?\}\z/
137
+ elsif part =~ /\A\{(\w+)(?::([\w.*]+))?\}\z/
117
138
  # Tina4/Python-style brace params: {id} or {id:int}
118
139
  # This is the ONLY supported param syntax, matching Python exactly.
119
140
  # Do NOT add :id (colon) style params.
120
141
  name = Regexp.last_match(1)
121
142
  type = Regexp.last_match(2) || "string"
122
- @param_names << { name: name.to_sym, type: type }
123
- case type
124
- when "int", "integer"
125
- '(\d+)'
126
- when "float", "number"
127
- '([\d.]+)'
128
- when "path"
129
- '(.+)'
130
- else
131
- '([^/]+)'
143
+ unless PARAM_TYPE_PATTERNS.key?(type)
144
+ valid = PARAM_TYPE_PATTERNS.keys.reject { |k| k == ".*" }.sort.join(", ")
145
+ raise ArgumentError,
146
+ "Unknown param type '#{type}' in route '#{path}'. Valid types: #{valid}."
132
147
  end
148
+ @param_names << { name: name.to_sym, type: type }
149
+ "(#{PARAM_TYPE_PATTERNS[type]})"
133
150
  else
134
151
  Regexp.escape(part)
135
152
  end
data/lib/tina4/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tina4
4
- VERSION = "3.11.11"
4
+ VERSION = "3.11.13"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tina4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.11.11
4
+ version: 3.11.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tina4 Team
@@ -179,33 +179,33 @@ dependencies:
179
179
  - !ruby/object:Gem::Version
180
180
  version: '1.8'
181
181
  - !ruby/object:Gem::Dependency
182
- name: listen
182
+ name: sqlite3
183
183
  requirement: !ruby/object:Gem::Requirement
184
184
  requirements:
185
185
  - - "~>"
186
186
  - !ruby/object:Gem::Version
187
- version: '3.8'
188
- type: :development
187
+ version: '2.0'
188
+ type: :runtime
189
189
  prerelease: false
190
190
  version_requirements: !ruby/object:Gem::Requirement
191
191
  requirements:
192
192
  - - "~>"
193
193
  - !ruby/object:Gem::Version
194
- version: '3.8'
194
+ version: '2.0'
195
195
  - !ruby/object:Gem::Dependency
196
- name: sqlite3
196
+ name: listen
197
197
  requirement: !ruby/object:Gem::Requirement
198
198
  requirements:
199
199
  - - "~>"
200
200
  - !ruby/object:Gem::Version
201
- version: '2.0'
201
+ version: '3.8'
202
202
  type: :development
203
203
  prerelease: false
204
204
  version_requirements: !ruby/object:Gem::Requirement
205
205
  requirements:
206
206
  - - "~>"
207
207
  - !ruby/object:Gem::Version
208
- version: '2.0'
208
+ version: '3.8'
209
209
  - !ruby/object:Gem::Dependency
210
210
  name: pg
211
211
  requirement: !ruby/object:Gem::Requirement