tina4ruby 3.11.12 → 3.11.14

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: 1ffc1ea931cf8203378e774e1efb4b3dbec399dd4309757200f8cd7967732b8b
4
- data.tar.gz: 5141bdc93061f5eafa53e40098732d5342f645a69cd6978f3380bd7e2c3f1606
3
+ metadata.gz: 0306b1bed1468e9d48729f576f4494f01f0ba42bd0868f137c56a533d4d3a987
4
+ data.tar.gz: 500e0e589914771bdc96b5b901dc0b9948ecf901df072b0ed845fd31efe7ff34
5
5
  SHA512:
6
- metadata.gz: 10b0c0e20c2389c088b494a6db800b3ea95aae3317d826491ebc4f610d6dd11e6ce13d3cc79eba23e4bde0adba2ff9fe76a4d9bbe91b10bdb47ead3ac333933a
7
- data.tar.gz: a748a5ce83aa37858b99a7a1ed204385ce09f2c43886fe6742dffe2332fb5ffba56849f0f3c81e046b31d6bed17f7d1a4d5f1ba7c61365398c0863dce0c6cf7f
6
+ metadata.gz: 225d391063a44527dc0df900ee7cce8fa20151be48a6a786fd41e2f6130253cff8899e1939a1a067e2d3f6879463f8b5cd7c888a85af29baaac85c8608a1607e
7
+ data.tar.gz: 0ece15acd6ebdeef69e90f67e10b250a3c1e05dee513ea3fad2f5794df93abc10141471362e59040bcff03f3aeeb2986dc4e7e6e1e1670a7721e26af82e0d902
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
 
@@ -34,11 +34,12 @@ module Tina4
34
34
  end
35
35
 
36
36
  def execute_query(sql, params = [])
37
- if params.empty?
38
- @connection.query(:hash, sql)
39
- else
40
- @connection.query(:hash, sql, *params)
41
- end.map { |row| stringify_keys(row) }
37
+ rows = if params.empty?
38
+ @connection.query(:hash, sql)
39
+ else
40
+ @connection.query(:hash, sql, *params)
41
+ end
42
+ rows.map { |row| decode_blobs(stringify_keys(row)) }
42
43
  end
43
44
 
44
45
  def execute(sql, params = [])
@@ -105,6 +106,19 @@ module Tina4
105
106
  def stringify_keys(hash)
106
107
  hash.each_with_object({}) { |(k, v), h| h[k.to_s] = v }
107
108
  end
109
+
110
+ # Ensure Firebird BLOB columns are proper byte strings.
111
+ # The Fb gem may return BLOBs as resource handles or IO objects —
112
+ # read them into strings if needed.
113
+ def decode_blobs(row)
114
+ row.each do |key, value|
115
+ if value.respond_to?(:read)
116
+ row[key] = value.read
117
+ value.close if value.respond_to?(:close)
118
+ end
119
+ end
120
+ row
121
+ end
108
122
  end
109
123
  end
110
124
  end
@@ -28,7 +28,7 @@ module Tina4
28
28
  else
29
29
  @connection.exec_params(converted_sql, params)
30
30
  end
31
- result.map { |row| symbolize_keys(row) }
31
+ result.map { |row| decode_blobs(symbolize_keys(row)) }
32
32
  end
33
33
 
34
34
  def execute(sql, params = [])
@@ -101,6 +101,16 @@ module Tina4
101
101
  def symbolize_keys(hash)
102
102
  hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v }
103
103
  end
104
+
105
+ # Ensure binary (bytea) columns are proper byte strings.
106
+ # PostgreSQL's pg gem returns bytea as ASCII-8BIT encoded strings —
107
+ # they're already raw bytes, just tag them so Ruby treats them right.
108
+ def decode_blobs(row)
109
+ # No conversion needed — pg gem returns bytea as ASCII-8BIT strings
110
+ # which are raw bytes. Users can .force_encoding("UTF-8") for text
111
+ # BLOBs or use the bytes directly for binary data.
112
+ row
113
+ end
104
114
  end
105
115
  end
106
116
  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.12"
4
+ VERSION = "3.11.14"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tina4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.11.12
4
+ version: 3.11.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tina4 Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-16 00:00:00.000000000 Z
11
+ date: 2026-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -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