tina4ruby 3.13.37 → 3.13.39

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.
data/lib/tina4.rb CHANGED
@@ -1,13 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # ── Fast JSON: use oj if available, fall back to stdlib json ──────────
4
- begin
5
- require "oj"
6
- Oj.default_options = { mode: :compat, symbol_keys: false }
7
- rescue LoadError
8
- # oj not installed — stdlib json is fine
9
- end
10
-
11
3
  # ── Core (always loaded) ──────────────────────────────────────────────
12
4
  require_relative "tina4/version"
13
5
  require_relative "tina4/constants"
@@ -108,6 +100,9 @@ module Tina4
108
100
  autoload :WebSocket, File.expand_path("tina4/websocket", __dir__)
109
101
  autoload :WebSocketConnection, File.expand_path("tina4/websocket", __dir__)
110
102
  autoload :DevReload, File.expand_path("tina4/websocket", __dir__)
103
+ autoload :WebSocketBackplane, File.expand_path("tina4/websocket_backplane", __dir__)
104
+ autoload :RedisBackplane, File.expand_path("tina4/websocket_backplane", __dir__)
105
+ autoload :NATSBackplane, File.expand_path("tina4/websocket_backplane", __dir__)
111
106
  autoload :Testing, File.expand_path("tina4/testing", __dir__)
112
107
  autoload :ScssCompiler, File.expand_path("tina4/scss_compiler", __dir__)
113
108
  autoload :FakeData, File.expand_path("tina4/seeder", __dir__)
@@ -188,13 +183,20 @@ module Tina4
188
183
  # Print banner
189
184
  print_banner
190
185
 
191
- # Load environment
186
+ # Load environment. Precedence: real-env > .env.local > .env
187
+ # (.env.local loads first, both first-wins, so a real env var always wins).
192
188
  Tina4::Env.load_env(root_dir)
193
189
 
194
190
  # Setup debug logging
195
191
  Tina4::Log.configure(root_dir)
196
192
  Tina4::Log.info("Tina4 Ruby v#{VERSION} initializing...")
197
193
 
194
+ # Fail-safe dev secret: in dev (and NOT CI/prod) mint a per-machine
195
+ # random TINA4_SECRET into gitignored .env.local if it is blank; in
196
+ # CI/prod with a blank secret, emit the actionable warning. Runs once at
197
+ # boot after env load, before any auth use. Never crashes boot.
198
+ Tina4::Auth.ensure_dev_secret(root_dir)
199
+
198
200
  # Setup auth keys
199
201
  Tina4::Auth.setup(root_dir)
200
202
 
@@ -210,6 +212,10 @@ module Tina4
210
212
  # Auto-discover routes
211
213
  auto_discover(root_dir)
212
214
 
215
+ # Apply pending DB migrations on startup (non-breaking — see method doc).
216
+ # Runs AFTER route discovery / DB bind, BEFORE serving.
217
+ auto_migrate_on_startup!(root_dir)
218
+
213
219
  Tina4::Log.info("Tina4 initialized successfully")
214
220
  end
215
221
 
@@ -381,9 +387,17 @@ module Tina4
381
387
  Tina4::Router.group(prefix, auth_handler: auth, &block)
382
388
  end
383
389
 
384
- # WebSocket route registration
385
- def websocket(path, &block)
386
- Tina4::Router.websocket(path, &block)
390
+ # WebSocket route registration. PUBLIC by default (mirrors GET). Pass
391
+ # secure: true OR chain .secure on the returned route to require a valid JWT
392
+ # on the upgrade.
393
+ def websocket(path, secure: false, &block)
394
+ Tina4::Router.websocket(path, secure: secure, &block)
395
+ end
396
+
397
+ # Register a SECURED WebSocket route — declarative sibling of
398
+ # Tina4.websocket(...).secure, mirroring secure_get/secure_post.
399
+ def secure_websocket(path, &block)
400
+ Tina4::Router.secure_websocket(path, &block)
387
401
  end
388
402
 
389
403
  # Middleware hooks
@@ -438,8 +452,73 @@ module Tina4
438
452
  Tina4::Container.get(name)
439
453
  end
440
454
 
455
+ # Apply pending DB migrations on startup — NON-BREAKING. Public so it can be
456
+ # called explicitly (and unit-tested) as `Tina4.auto_migrate_on_startup!`.
457
+ #
458
+ # When a migrations/ folder exists (with at least one .sql file) and
459
+ # TINA4_AUTO_MIGRATE is not disabled, pending migrations are applied during
460
+ # boot so the schema is current with no manual `tina4ruby migrate` step. A
461
+ # failure here is logged LOUD and the service STILL starts — a bad migration
462
+ # must never take the backend down. (The explicit `tina4ruby migrate` CLI
463
+ # stays fail-fast so CI still gets a non-zero exit.)
464
+ #
465
+ # Disable with TINA4_AUTO_MIGRATE=false (also 0/no/off) — e.g. multi-instance
466
+ # production that migrates as a separate deploy step (concurrent first-apply
467
+ # can race).
468
+ def auto_migrate_on_startup!(root_dir = Dir.pwd)
469
+ # Gate 1: a migrations folder with at least one .sql file must exist.
470
+ migrations_dir = resolve_startup_migrations_dir(root_dir)
471
+ return unless migrations_dir
472
+
473
+ # Gate 2: TINA4_AUTO_MIGRATE not disabled (default "true"; false/0/no/off off).
474
+ unless Tina4::Env.is_truthy(ENV.fetch("TINA4_AUTO_MIGRATE", "true"))
475
+ Tina4::Log.debug("TINA4_AUTO_MIGRATE is off — skipping startup migrations")
476
+ return
477
+ end
478
+
479
+ # Gate 3: a database must be resolvable.
480
+ db = Tina4.database
481
+ unless db
482
+ Tina4::Log.debug("Startup migrations skipped (no database configured)")
483
+ return
484
+ end
485
+
486
+ begin
487
+ migration = Tina4::Migration.new(db, migrations_dir: migrations_dir)
488
+ results = migration.run
489
+ applied = Array(results).count { |r| r[:status] == "success" }
490
+ Tina4::Log.info("Applied #{applied} pending migration(s) on startup") if applied.positive?
491
+ # A migration that records as "failed" surfaces in `run`'s results but
492
+ # does NOT raise from the runner; treat a recorded failure as loud-log too.
493
+ if Array(results).any? { |r| r[:status] == "failed" }
494
+ Tina4::Log.error(
495
+ "Startup auto-migration failed — the service is starting anyway. " \
496
+ "Run `tina4ruby migrate` to retry."
497
+ )
498
+ end
499
+ rescue => e
500
+ # NON-BREAKING: never re-raise out of the startup hook.
501
+ Tina4::Log.error(
502
+ "Startup auto-migration failed: #{e.message} — the service is starting " \
503
+ "anyway. Run `tina4ruby migrate` to retry."
504
+ )
505
+ end
506
+ end
507
+
441
508
  private
442
509
 
510
+ # Resolve the migrations directory for startup auto-migration, returning it
511
+ # only when it exists AND contains at least one .sql file. Prefers
512
+ # src/migrations, falls back to migrations/ (mirrors Migration#resolve_migrations_dir).
513
+ def resolve_startup_migrations_dir(root_dir)
514
+ %w[src/migrations migrations].each do |rel|
515
+ dir = File.join(root_dir, rel)
516
+ next unless Dir.exist?(dir)
517
+ return dir unless Dir.glob(File.join(dir, "*.sql")).empty?
518
+ end
519
+ nil
520
+ end
521
+
443
522
  # Resolve auth option for route registration
444
523
  # :default => use bearer auth (default for POST/PUT/PATCH/DELETE)
445
524
  # false => no auth (public route)
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.13.37
4
+ version: 3.13.39
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-06-17 00:00:00.000000000 Z
11
+ date: 2026-06-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -52,20 +52,6 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '6.0'
55
- - !ruby/object:Gem::Dependency
56
- name: dotenv
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '3.0'
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '3.0'
69
55
  - !ruby/object:Gem::Dependency
70
56
  name: jwt
71
57
  requirement: !ruby/object:Gem::Requirement
@@ -80,20 +66,6 @@ dependencies:
80
66
  - - "~>"
81
67
  - !ruby/object:Gem::Version
82
68
  version: '2.7'
83
- - !ruby/object:Gem::Dependency
84
- name: bcrypt
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '3.1'
90
- type: :runtime
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '3.1'
97
69
  - !ruby/object:Gem::Dependency
98
70
  name: net-smtp
99
71
  requirement: !ruby/object:Gem::Requirement
@@ -136,20 +108,6 @@ dependencies:
136
108
  - - "~>"
137
109
  - !ruby/object:Gem::Version
138
110
  version: '2.7'
139
- - !ruby/object:Gem::Dependency
140
- name: oj
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - "~>"
144
- - !ruby/object:Gem::Version
145
- version: '3.16'
146
- type: :runtime
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - "~>"
151
- - !ruby/object:Gem::Version
152
- version: '3.16'
153
111
  - !ruby/object:Gem::Dependency
154
112
  name: rexml
155
113
  requirement: !ruby/object:Gem::Requirement
@@ -277,8 +235,9 @@ dependencies:
277
235
  - !ruby/object:Gem::Version
278
236
  version: '1.50'
279
237
  description: 'TINA4: The Intelligent Native Application 4ramework for Ruby — Simple.
280
- Fast. Human. Built for AI. Built for you. 54 built-in features, zero dependencies.
281
- Full parity with Python, PHP, and Node.js.'
238
+ Fast. Human. Built for AI. Built for you. 54 built-in features, minimal dependencies
239
+ (Rack-based; the runtime gems are production-deployment essentials, not framework
240
+ bloat). Full parity with Python, PHP, and Node.js.'
282
241
  email:
283
242
  - info@tina4.com
284
243
  executables:
@@ -460,5 +419,5 @@ requirements: []
460
419
  rubygems_version: 3.4.19
461
420
  signing_key:
462
421
  specification_version: 4
463
- summary: Tina4 for Ruby — 54 built-in features, zero dependencies
422
+ summary: Tina4 for Ruby — 54 built-in features, minimal dependencies
464
423
  test_files: []