Chapter 35: Release Notes
v3.12.4 (2026-05-06)
Documentation-truth release. The audit-truth.py CI gate (introduced post-3.12.3) flagged 39 env vars referenced in docs that no framework actually read. This release closes that gap: 25 of them now exist in code, the other 14 are deleted from docs (11 hallucinations + 6 clustering vars deferred to tina4#2). Both audit gates (CLI drift + env-var drift) are now strict in CI.
25 new env vars across all 4 frameworks
Server: TINA4_HOST, TINA4_SUPPRESS, TINA4_ENV_FILE. Health: TINA4_HEALTH_PATH (default /__health, with /health kept as a legacy alias), TINA4_TRAILING_SLASH_REDIRECT. Sessions: TINA4_SESSION_HTTPONLY, TINA4_SESSION_NAME, TINA4_SESSION_SECURE. Templates: TINA4_TEMPLATE_CACHE_TTL (0 = permanent). GraphQL: TINA4_GRAPHQL_AUTO_SCHEMA, TINA4_GRAPHQL_ENDPOINT. Mail: TINA4_MAIL_IMAP_ENCRYPTION (tls/starttls/none). MCP: TINA4_MCP, TINA4_MCP_PORT. Swagger: TINA4_SWAGGER_ENABLED, TINA4_SWAGGER_CONTACT_EMAIL, TINA4_SWAGGER_LICENSE. Database: TINA4_DB_POOL (env override on the existing Database(url, pool=N) constructor argument).
Logging — env-driven file output + rotation
Six new vars give you full control over logging without touching code:
| Var | Default | What it does |
|---|---|---|
TINA4_LOG_FILE | (empty — stdout only) | Path to a log file. Empty leaves you on stdout. |
TINA4_LOG_DIR | logs | Directory for log files (joined with _LOG_FILE if relative). |
TINA4_LOG_FORMAT | text | text or json. JSON mode emits one structured record per line. |
TINA4_LOG_OUTPUT | stdout | stdout, file, or both. Strict — stdout means stdout only. |
TINA4_LOG_CRITICAL | false | Enables a Log.critical() level above error. Off = no-op. |
TINA4_LOG_ROTATE_SIZE | 10485760 (10 MB) | Rotate when the file exceeds this many bytes. 0 disables rotation. |
TINA4_LOG_ROTATE_KEEP | 5 | Number of rotated files to retain (app.log.1 … app.log.N). Older ones are deleted. |
Implementation uses each language's stdlib — Python's logging.handlers.RotatingFileHandler, Ruby's Logger.new(path, shift_age, shift_size), and a roll-your-own atomic-rename pattern in PHP and Node. Zero new dependencies in any framework.
Documentation-truth CI gate now strict on both axes
The audit-truth.py script now blocks merges to main of tina4-documentation whenever a doc references a tina4 <command> or TINA4_* env var that doesn't exist in source. Previously CLI drift was strict; env drift was warn-only. Today both are strict.
Tests added
- Python: +53 tests in
tests/test_env_vars.py(2395 → 2448) - PHP: +59 tests in
tests/EnvVarTest.php(2172 → 2231) - Ruby: +51 examples in
spec/env_vars_spec.rb(2696 → 2747) - Node: +59 tests in
test/envVars.test.ts(3204 → 3263)
Cross-framework total: 10,689 tests passing, +222 from 3.12.3.
Upgrade path
Drop in. No breaking changes — every new env var is opt-in with a sensible default. If you were setting any of the 17 deleted vars in your .env, the boot guard will warn (then ignore) — clean them out at your leisure.
v3.12.3 (2026-05-05)
Cross-framework parity sweep. Two minor breaking changes in the Ruby and PHP public API that bring all four frameworks onto the same shape.
Breaking changes (Ruby + PHP only)
Ruby Container — predicate now uses ? suffix.
# before (3.12.2 and earlier)
Tina4::Container.has(:mailer) # outdated
# after (3.12.3)
Tina4::Container.has?(:mailer) # idiomatic Ruby predicateThis brings Ruby in line with Python (has()), PHP (has()), and Node (has()) while still respecting Ruby's ?-suffix idiom for predicates returning bool. The pre-existing resolve → get rename happened earlier; only the predicate was lagging.
ResponseCache public surface — middleware-only across all four frameworks.
The cache has always been middleware. Two of the four frameworks (PHP, Ruby) historically exposed lookup/store as public methods, which let users couple to internals. The public API is now consistent across all four: use the middleware on a route, and read stats with module-level helpers.
# Ruby — module-level helpers (parity with Python)
Tina4.cache_stats # → { hits:, misses:, size:, backend:, keys: }
Tina4.clear_cache # flush all entries
# PHP — static methods on the class
\Tina4\Middleware\ResponseCache::cacheStats();
\Tina4\Middleware\ResponseCache::clearCache();Internal methods that used to be public (get, lookup, store, cache_response) are now private. Tests that needed them retain access via _internal* test seams marked @internal.
Doc parity — CLAUDE.md and book chapter 33
- CLAUDE.md: every framework's "Key Method Stubs" section now covers the same surface area Python documents — Queue, QueryBuilder, Frond, Api, Background Tasks, ResponseCache, etc. PHP added 4 sections; Ruby added 5; Node added 13.
- Book chapter 33: env var tables are now grounded in source. Each framework's chapter 33 lists every
TINA4_*var its source actually reads. Found and fixed several gaps — Ruby was missingTINA4_CACHE_*,TINA4_QUEUE_*,TINA4_KAFKA_*,TINA4_RABBITMQ_*,TINA4_MONGO_*,TINA4_WS_BACKPLANE, and the entireTINA4_SESSION_VALKEY_*block.
Other fixes
- Ruby
lib/tina4/ai.rb— subprocess output is now force-encoded to UTF-8 beforeString#strip, fixingEncoding::CompatibilityErrorthat crashed 4 ai specs on systems with non-ASCII pip output. - Node
test/serverParity.test.ts— setsTINA4_OVERRIDE_CLIENT=truesostart()actually runs, plus emits theN passed, M failedsummary line the runner expects. The test was effectively a no-op before; now it's recorded properly.
Genuine gaps surfaced by the parity audit (follow-up, not blocking 3.12.3)
The chapter 33 audit flagged env vars Python documents that no other framework actually reads — Ruby/PHP/Node lack TINA4_OPEN_BROWSER, TINA4_DEV_POLL_INTERVAL, TINA4_PUBLIC_DIR, TINA4_TOKEN_EXPIRES_IN alias, plus a few framework-specific gaps (Ruby has no Mongo session backend; Node TINA4_CSRF defaults to false vs Python's true). Tracked for a future patch.
Upgrade path
| Symptom | Fix |
|---|---|
Ruby: NoMethodError: undefined method 'has' for Tina4::Container | Replace has(:key) with has?(:key) |
PHP: BadMethodCallException calling $cache->lookup(...) | Use the middleware: [ResponseCache::class, 'beforeCache'] / [..., 'afterCache']. Or call _internalLookup if you really need direct access (test code only — @internal). |
Ruby: NoMethodError: undefined method 'get' for ResponseCache instance | Use Tina4.cache_stats / Tina4.clear_cache for stats. Lookup goes through the middleware. |
No .env changes from 3.12.2.
v3.12.2 (2026-05-05)
Quality-of-life patch. Two related portability fixes — no breaking changes from 3.12.1.
Firebird URL auto-detect
Firebird is the awkward one in the stack. Every other engine has a server-side database name (postgres://host:port/dbname), but Firebird wants either an absolute file path on the server, a Windows drive-letter path, or an alias. The classic URI form needs a double slash to keep the leading / of an absolute path through the URL parser — unintuitive to anyone used to the way postgres / mysql / mssql encode the database name.
The framework now accepts five equivalent forms and normalises all of them transparently:
| URL path you write | Resolved Firebird identifier |
|---|---|
//abs/path/db.fdb (classic double-slash) | /abs/path/db.fdb |
/abs/path/db.fdb (single-slash, intuitive) | /abs/path/db.fdb |
/C:/Data/db.fdb (Windows drive letter) | C:/Data/db.fdb |
/C%3A/Data/db.fdb (URL-encoded colon) | C:/Data/db.fdb |
/employee (Firebird alias) | employee |
For ops setups that keep server URL and DB location in separate config layers — or for Windows backslash paths that fight URL encoding — set TINA4_DATABASE_FIREBIRD_PATH. The env override wins over whatever path is in the URL.
TINA4_DATABASE_FIREBIRD_PATH=C:\firebird\data\app.fdb
TINA4_DATABASE_URL=firebird://SYSDBA:masterkey@localhost:3050/ignoredShipped to all 4 frameworks. 11 regression tests per framework (8 unit + 3 live).
Bug fix specific to PHP — mysqli localhost+port quirk
PHP's mysqli has a long-standing quirk where host == "localhost" triggers a Unix socket lookup and IGNORES the port argument entirely. Connecting to mysql://...:53306 against a Docker container fails with "No such file or directory" — mysqli is hunting for /tmp/mysql.sock instead of opening a TCP connection. MySQLAdapter::rewriteHostForTcp() now rewrites localhost to 127.0.0.1 when a non-zero port is specified, forcing the TCP code path. Bare mysql:///db (no port) is preserved so existing socket-based setups keep working.
Other fixes
- chore(python):
pyproject.tomlhad drifted to3.10.41while__init__.pyread3.12.1. Synced both to 3.12.2 souv buildand runtime introspection now agree. - chore(claude.md, all 4): stale framework version banners in
CLAUDE.mdheaders updated.
No .env changes from 3.12.1, no migration needed. Existing 3.12.1 installs upgrade by changing one version number.
v3.12.1 (2026-05-04)
CI-only patch — no framework code changes from 3.12.0.
- fix(ci, all 4): every
publish.ymlworkflow now declarespermissions: contents: writeon the publish job. Without this,softprops/action-gh-release403'd against the defaultGITHUB_TOKENon repos whose default Workflow permissions setting was read-only (Ruby and Node hit this every release; PHP and Python worked by luck of repo settings). The explicit declaration makes the workflow self-sufficient. - chore(ci): bumped
softprops/action-gh-releasefrom@v1(unmaintained) to@v2.
No .env changes, no API changes, no migration needed. Existing 3.12.0 installs can upgrade without touching anything else.
The version-bump itself is the test: a successful 3.12.1 release proves the workflow fix works on Ruby and Node where 3.12.0 needed manual gh release create.
v3.12.0 (2026-05-04)
⚠️ Breaking change — read before upgrading. Every framework env var now uses the
TINA4_prefix. Existing.envfiles set withDATABASE_URL,SECRET,SMTP_HOST,HOST_NAME, etc. will cause the framework to refuse to boot. Runtina4 env --migrateto rewrite, or follow the rename table below.
Why this release
Tina4's env vars had grown inconsistent. Some had the TINA4_ prefix (TINA4_DEBUG, TINA4_LOCALE, TINA4_CACHE_BACKEND), others didn't (DATABASE_URL, SECRET, SMTP_HOST). Newcomers had to guess which convention applied to which feature. Existing tools and PaaS dashboards collided with un-prefixed names like SECRET and API_KEY that other libraries also read. Documentation drifted — 91 env-var names appeared in the docs that didn't exist in any framework, and 22 framework-specific env vars in the code didn't match the names users were told to set.
This release closes all three gaps with a single hard rename. No deprecation period, no fallback chain. The framework refuses to boot if it detects a legacy name in the environment, prints a list of every var to rename, and tells you which command to run.
What changed
- 22 env vars renamed to
TINA4_*form. See the migration table below. tina4 env --migrateCLI added to all four frameworks. Reads your.env, rewrites it in place, leaves a.env.bakbackup, prints a diff. Idempotent.- Boot-time guard scans
os.environ(or the language equivalent) for the 22 legacy names. If any are present, prints the rename map and exits with code 2. Bypass withTINA4_ALLOW_LEGACY_ENV=truefor migration scripts that need both names set during transition. - All 4 framework books rewritten. Chapter 33 (Environment Variables) is now a clean canonical list — every var prefixed, descriptions current, legacy names removed.
- Doc-vs-code drift closed. Of the 91 stale env vars previously documented, 61 were renames (corrected), 32 were never implemented (removed). The
audit-links.pyCI gate stays at 0 broken links / 0 broken anchors. - Frond bundle rebuilt at v2.1.3 —
frond.min.jsfooter now shows the version explicitly so users can verify what they have.
Bug fixes shipped alongside the rename
- #38 PostgreSQL UUID-PK transaction abort — the post-INSERT
lastval()probe is now wrapped in a SAVEPOINT, so UUID-PK INSERTs no longer poison the outer transaction withInFailedSqlTransaction. Live regression test against PostgreSQL 16. (Affects all 4 frameworks where the PG adapter does this probe.) - #39 Landing page + template auto-routing —
- Auto-routing now scans
src/templates/pages/only. Partials, layouts, base.twig, errors/, components/, and_*files never auto-serve from a URL. TINA4_TEMPLATE_ROUTING=offkills the feature entirely.src/public/index.htmlauto-serves at/(and/foo/servessrc/public/foo/index.html) — SPA hosting Just Works.- The framework landing page only renders when
TINA4_DEBUG=true. Production never shows it; framework version, dev-admin link, and gallery don't leak to real users. - The malformed
HTTP/1.1 404 OKstatus line is fixed — every status code now uses its canonical RFC 7231/9110 reason phrase.
- Auto-routing now scans
- #37 frond.form.submit redirect handling — verified shipped at v2.1.x;
xhr.responseURLchange triggerswindow.locationnavigation correctly. - #36 Session file handler — re-verified safeguards (lazy save, WebSocket skip, probabilistic GC, new-and-empty skip) all still in place.
Migration — every renamed var
| Legacy name | New name |
|---|---|
DATABASE_URL | TINA4_DATABASE_URL |
DATABASE_USERNAME | TINA4_DATABASE_USERNAME |
DATABASE_PASSWORD | TINA4_DATABASE_PASSWORD |
DB_URL | TINA4_DATABASE_URL (alias dropped) |
SECRET | TINA4_SECRET |
API_KEY | TINA4_API_KEY |
JWT_ALGORITHM | TINA4_JWT_ALGORITHM |
SMTP_HOST | TINA4_MAIL_HOST |
SMTP_PORT | TINA4_MAIL_PORT |
SMTP_USERNAME | TINA4_MAIL_USERNAME |
SMTP_PASSWORD | TINA4_MAIL_PASSWORD |
SMTP_FROM | TINA4_MAIL_FROM |
SMTP_FROM_NAME | TINA4_MAIL_FROM_NAME |
IMAP_HOST | TINA4_MAIL_IMAP_HOST |
IMAP_PORT | TINA4_MAIL_IMAP_PORT |
IMAP_USER | TINA4_MAIL_IMAP_USERNAME |
IMAP_PASS | TINA4_MAIL_IMAP_PASSWORD |
HOST_NAME | TINA4_HOST_NAME |
SWAGGER_TITLE | TINA4_SWAGGER_TITLE |
SWAGGER_DESCRIPTION | TINA4_SWAGGER_DESCRIPTION |
SWAGGER_VERSION | TINA4_SWAGGER_VERSION |
ORM_PLURAL_TABLE_NAMES | TINA4_ORM_PLURAL_TABLE_NAMES |
Names that stay un-prefixed (not framework config)
PORT, HOST, NODE_ENV, RACK_ENV, RUBY_ENV, ENVIRONMENT — these are runtime / PaaS conventions, not framework config. Heroku, Railway, Vercel, and friends set them; we keep reading them.
How to upgrade
- Backup your
.env:cp .env .env.bak.pre-v3.12 - Run the migration:
tina4 env --migrate— rewrites your.envin place. - Update PaaS dashboards: Heroku, Railway, Vercel, Render, Fly.io etc — rename the same vars in your provider's env-var UI.
- Restart your app. The boot guard verifies nothing legacy remains.
If your app uses SECRET, DATABASE_URL, or any other listed name in places besides .env (e.g. your CI pipeline's env: blocks), update those too — the boot guard checks os.environ, not just .env.
Parity
All 4 frameworks aligned at 3.12.0:
- tina4-python 3.11.32 → 3.12.0
- tina4-php 3.11.32 → 3.12.0
- tina4-ruby 3.11.32 → 3.12.0
- tina4-nodejs 3.11.32 → 3.12.0
Coordinated release across PyPI, Packagist, RubyGems, npm.
v3.11.32 (2026-04-25)
Critical fix — pool + transactions are now actually atomic. Plus a coordinated parity release that aligns all four frameworks at the same version after months of drift.
Before this release, creating a Database with pool > 0 silently broke transactions. The pool's round-robin checkout rotated to a different adapter on every call — so start_transaction() pinned its flag on adapter A, the executes autocommitted on adapters B and C, and the final commit() / rollback() landed on adapter D, which had nothing to commit. Result: rollback() was a no-op, writes leaked through, and no error or log surfaced the problem.
The fix pins one adapter to the calling context for the lifetime of a transaction. Each language uses its own primitive:
- Python —
threading.local()on theDatabaseinstance - Ruby —
Thread.current[:tina4_pinned_adapter_<obj_id>] - Node.js —
AsyncLocalStoragefromnode:async_hooks(async-safe across overlapping awaits) - PHP — per-instance property (PHP-FPM is one process per request; threading.local is unnecessary)
While pinned, every database call routes to the same adapter. commit() and rollback() release the pin so subsequent calls round-robin again.
- fix (database / all 4): adapter pinning across transaction scope in
Database._get_adapter()(and language equivalents). Every backend is affected — SQLite, PostgreSQL, MySQL, MSSQL, Firebird. Firebird exposed it loudest because of its honest "commit-empty-txn is a real no-op" semantics; the others mostly hid the bug behind eager autocommits but still lost rollback atomicity. - tests (all 4): new regression suite — three INSERTs followed by
rollback()underpool=4now leaves zero rows (was leaking three). Three INSERTs followed bycommit()persists exactly three. Pin-release after commit/rollback verified.pool=0regression test added so single-connection mode stays unaffected. - parity / version alignment: all 4 frameworks bumped to 3.11.32 — closes the cross-framework version drift that had built up (PHP at 3.11.31, Python at 3.11.24, Ruby and Node at 3.11.19). A single coordinated release across all four registries: PyPI, Packagist, RubyGems, npm.
No migration needed. Code using pool=0 (the default for every adapter except where explicitly raised) is unaffected. Code using pool>0 will now actually honour transactions instead of silently dropping them.
If you've been seeing intermittent "writes vanished" or "rollback didn't help" reports on a pooled Database, this release is the cause and the cure.
v3.11.13 (2026-04-16)
Issue-driven release. Everything reported in the open tina4-book issues either was fixed in this version or is already fixed in 3.11.12; this release consolidates the remaining bits and corrects documentation drift.
- feat (router / all 4): Explicit typed-parameter system shared across Python, PHP, Ruby, Node. Adds
alpha,alnum,slug,uuid, and explicitstringtypes in addition to the existingint/integer,float/number,path/.*. Unknown type names now throw at registration —{name:str},{id:inetger}, etc. raise with a clear message listing the valid types instead of silently falling through to the default matcher. Fixes tina4-book#125. +45 new tests across the four suites. - fix (gallery / python+php+ruby): Gallery Try-It / View buttons now open the deployed example in a new tab (
window.open(url, '_blank')) instead of navigating away from the gallery home. Fixes tina4-book#115. - fix (ruby gemspec):
sqlite3promoted fromadd_development_dependencytoadd_dependency. Matches the "zero-config SQLite on first run" promise. Fixes tina4-book#100. - docs (tina4-book): PHP Chapter 2 updated — correct port (7145),
->noAuth()on write-method examples, and an explicit callout explaining the secure-by-default policy for POST/PUT/PATCH/DELETE. Addresses tina4-book#87, #94, #123. - docs (tina4-book): Python
@templatedecorator ordering corrected (must sit BELOW the route decorator) in book chapters 04 and 10; Pythonrequest->queryvsrequest->paramsdistinction in PHP chapter 1. - tests (python): Session-handler tests updated to reflect the real default TTL of 3600s (were stale at 1800s).
- verified already fixed in earlier 3.11.x releases — closed comments posted on all of these:
- #79 dotted numeric index (
{{ items.0.name }}) - #80
truncatefilter - #82
{{ parent() }}/{{ super() }}across all 4 frameworks - #83 Ruby dashboard — WEBrick is runtime dep
- #89
load_dotenvrename,DatabaseResultmethods, SQLite WAL locking - #91 Ruby
request.paramssymbol + string keys viaIndifferentHash - #93 Ruby
/docs/*and bare/*wildcard routes - #97 Frond ternary operator
- parity: All 4 frameworks bumped to 3.11.13.
v3.11.12 (2026-04-16)
Breaking: sqlite:///X URLs are now relative to the project root (cwd), matching the documented convention. For absolute paths use four slashes (sqlite:////abs/path.db) or a Windows drive letter (sqlite:///C:/Users/app.db).
Before this release, TINA4_DATABASE_URL=sqlite:///data/app.db was interpreted differently by every framework. Python/Node/Ruby tried to open /data/app.db (absolute) which crashed on macOS with OSError: [Errno 30] Read-only file system: '/data'. PHP did the same under the hood. All four frameworks now agree: three slashes = relative, four slashes = absolute.
- fix (all 4):
sqlite:///Xresolves under cwd; parent directory auto-created only when inside cwd. Absolute paths are trusted and never mkdir'd at root. - fix (python):
_ensure_foldersno longer creates a bogussrc/migrations/directory. The migration runner always looks atmigrations/at the project root — there is only one correct location. - parity (php, ruby, node): Same
sqlite:///Xparsing as Python. Dedicatedresolve_path/resolveSqlitePathhelpers in each framework so adapters consistently handle:memory:,./forms, Windows drive letters. - tests: 9 new Python tests in
TestSQLiteConnectionPath+TestProjectFolders. 4 new PHP tests inDatabaseUrlTestcovering relative/absolute/Windows/bruce-regression. 6 new Ruby specs indatabase_drivers_spec.rb :: SqliteDriver.resolve_path. Node URL tests expanded indatabase.test.tswith the full relative/absolute/Windows/:memory: matrix. - parity: All 4 frameworks bumped to 3.11.12.
Migration note: If your .env has TINA4_DATABASE_URL=sqlite:///data/app.db, it will now create ./data/app.db in the project root (which is what most users actually want). If you genuinely want an absolute path, change to sqlite:////data/app.db (four slashes).
v3.11.11 (2026-04-16)
- fix (python ORM):
Field.validateno longer re-coerces values that are already the correct type. Previously, any PostgreSQL/MSSQL read of a row containing aDateTimeFieldcrashed becausedatetime(datetime_instance)raisesTypeError. The fix accepts native driver types (datetime,bytes,int,bool,float,str) without re-wrapping, and parses ISO-8601 strings intodatetimefor SQLite. Seetina4-python/plan/orm-field-validate-native-types.md. - fix (python ORM):
BooleanFieldvsIntegerFieldordering handled explicitly.BooleanField(1)still coerces toTrue,IntegerField(True)still coerces to1; no regression for either direction (bool is a subclass of int in Python). - tests (python): 10 new
TestFieldsNativeTypescases covering datetime/int/bool/float/bytes/string/ForeignKey round-trips. - tests (parity): Regression-guard "datetime round-trip on read path" tests added to PHP (
ORMV3Test), Ruby (orm_spec) and Node.js (orm.test.ts) so an equivalent bug can't creep in there later. - parity: All 4 frameworks bumped to 3.11.11.
v3.11.10 (2026-04-15)
- fix (php): Hot-reload loop — DevAdmin's polling fallback used
mt=0as the baseline, so the first poll after every page load triggeredlocation.reload(), which resetmt=0again. Loop now initialises the baseline on the first poll. - fix (php): Reload sentinel removed — PHP was the only framework recursively walking
src/and touchingsrc/.reload_sentinelon every reload POST. The sentinel lived inside the Rust CLI's watched tree and fed back into the watcher, triggering a second loop. Replaced with the same in-memory counter used by Python/Ruby/Node. - fix (php): Polling no longer starts more than once when the WebSocket reconnect retry budget is exhausted (added a
pollStartedguard). - feat (parity):
GET /__dev/api/queue/topicsandGET /__dev/api/queue/dead-lettersadded to PHP, Ruby and Node (previously only in Python). PHP queue endpoints now read from the realTina4\Queuebackend instead of returning stubs. - feat (devadmin): Refreshed
tina4-dev-admin.jsbundle (87.8 KB) across all 4 frameworks — adds the topic selector dropdown, inline payload expand/copy, and corrected version display. - tests: 4-way parity tests for hot-reload: mtime starts at 0, POST /__dev/api/reload bumps the counter, no sentinel file is written to disk, mtime is monotonic across successive reloads. Mirrored in
tina4-php/tests/DevAdminTest.php,tina4-python/tests/test_dev_admin.py,tina4-ruby/spec/dev_admin_spec.rb,tina4-nodejs/test/devAdmin.test.ts. - parity: All 4 frameworks bumped to 3.11.10.
v3.11.9 (2026-04-15)
Catch-up release covering v3.11.0 → v3.11.9 across all 4 frameworks.
- feat (websocket): Full WebSocket parity across Python/PHP/Node/Ruby —
get_client_rooms()/getClientRooms(),route()usable as decorator or direct handler registration, matching room/broadcast semantics, plus new parity tests on all 4. - feat (graphql): Input validation and field-level
@authdirectives with context threading. - feat (graphql): Auto-discovery of schemas; removed legacy DevAdmin HTML/JS in favour of the new UI.
- feat (devadmin — Python): Queue tab with topic selector, dead-letter listing and replay endpoints, inline payload expand/copy, version display.
- feat (cli): Rust CLI now owns file watching — frameworks receive
POST /__dev/api/reloadand internal watchers are disabled when launched by the Rust CLI (--managed). - fix (cli):
parseFlags/parse_flags/parseCliArgsno longer swallowhost:portor positional args after boolean flags. - fix (scss): SCSS recompilation loop fixed; output path corrected to
src/public/css/to match CLI and static serving. - fix (frond — Python): Numeric dotted index for lists (
items.0.name) now resolves correctly. - fix (router — Ruby): Bare
/*wildcard capture exposed under"*"key for parity. - fix (orm — PHP): Three data-sync bugs fixed:
load()double-fill,getPrimaryKeyValue,save()ID sync. - fix (graphql):
from_orm/fromOrmlist resolver usedselect(skip=)instead ofall(offset=). - fix (metrics): Windows backslash paths normalised to forward slashes.
- fix (app — PHP): No longer crashes on notices/deprecations in loaded files;
run()now prints the banner when starting the server directly. - chore: Example demo store ships with the repo; Windows-friendly setup;
.env.exampleand setup scripts added. - parity: All 4 frameworks bumped to 3.11.9. PHP aligned to the 3.x tag scheme on
v3.
v3.10.99 (2026-04-12)
- breaking:
autoMapnow defaults totrue— ORM models automatically map between camelCase properties and snake_case DB columns. Setstatic autoMap = false;on your model to restore the old behaviour. - feat:
toDict(include, case)parameter — pass'snake'as second arg to get snake_case keys matching DB columns, or'camel'(default) for camelCase.
- feat: Frond
replacefilter now accepts object args —{{ v|replace({"T": " ", "-": "/"}) }}for multiple substitutions in one call. - tests: 13 new parity tests covering
toDict(case),autoMapdefault,replacefilter (object + positional), andServiceRunnerregistration. 268 tests passing. - parity: All features shipped identically across Python, PHP, Ruby, Node.js.
v3.10.97 (2026-04-11)
- fix: frond.form.submit redirect handling — XHR follows 3xx redirects transparently; fixed by detecting
xhr.responseURLmismatch and navigating instead. - dep: Updated frond.min.js to v2.1.2.
- parity: All 4 frameworks bumped to 3.10.97.
v3.10.93 (2026-04-11)
- fix: Frond bracket depth tracking in
findOutsideQuotes()andsplitOutsideQuotes()— expressions likearr[i % 2]no longer treated as top-level arithmetic. - fix: Frond subscript expression evaluation — bracket content uses
evalExpr()instead of direct context lookup, enablingarr[loop.index0 % 2]. - fix: Frond slice with variable bounds —
items[start:end]evaluates bounds throughevalExpr(). - docs: Developer skills updated — Metrics Dashboard guidance, Frond Template Parity rules,
@noauthsecurity warnings. - parity: All Frond fixes applied identically across Python, PHP, Ruby, Node.js. 2,831 tests passing (268 Frond).
v3.10.92 (2026-04-10)
- feat: Add
DevAdminmethods —capture()(5-param),clearAll(),health(),unresolvedCount(),reset(),register(). - feat: Add
Server.start()andServer.stop()for cross-framework parity. - feat: Add
DatabaseResult.size()method. - feat: Add
DevReload.start()andDevReload.stop(). - feat: Add
ScssCompiler.compileScss()method. - fix:
autoCrud.ts— fix spread syntax on non-iterable, add id in POST response, correct response format to{data, meta}, change validation status from 400 to 422. - parity: 44/44 cross-framework features green. 2,752 tests passing.
v3.10.91 (2026-04-10)
- feat: Add parity methods —
GraphQLType.parse(),CorsMiddleware.isPreflight(),RateLimiterMiddleware.check(). - breaking: Rename
from()→fromTable(), removetemplate()alias — align with Python canonical names.
v3.10.90 (2026-04-09)
- docs: Chapter 4 (Templates) — new "Dumping Values for Debugging" section covering both
{{ x|dump }}and{{ dump(x) }}forms, the v3.10.88inspectValue()inspector (circular refs, BigInt, Map/Set, Error, Date, class instances), and theTINA4_DEBUG=trueproduction gate. Filter table entry updated to reference the new section. - docs:
plan/parity/parity-template.mdupdated with a cross-framework dump helper comparison table and marks dump parity as confirmed across all 4 frameworks at v3.10.89. - chore: Version sync release — brings all 4 frameworks to the same patch version (3.10.90) so downstream users can upgrade PHP/Python/Ruby/Node.js in lockstep without hunting version mismatches.
v3.10.89 (2026-04-09)
- feat:
{{ dump(value) }}global function form added to Frond alongside the existing{{ value|dump }}filter. Both call a singlerenderDump()helper (which delegates to the v3.10.88inspectValue()inspector) and produce identical output. - security: Dump is now gated on
TINA4_DEBUG=true. In production (env var unset orfalse) both the filter and function silently return an emptySafeString. This prevents accidental leaks of internal state, object shapes, and sensitive values into rendered HTML when a developer leaves a{{ dump(x) }}call in a template. - test: 4 new tests in
frond.test.tscoveringdump()/|dumpparity, debug-mode circular ref handling, production silencing for both forms.
v3.10.88 (2026-04-09)
- fix:
{{ value|dump }}filter now handles complex objects safely. The previous implementation usedJSON.stringifywhich crashed on circular references and BigInt, silently dropped functions/Symbols/undefined, and serialisedMap/Set/Error/class instances as empty{}. Replaced with aninspectValue()inspector that matches PHP'svar_dump, Python'srepr, and Ruby'sinspect:- Circular references:
[Circular] - BigInt:
123n - Date:
Date(2026-04-09T13:00:00.000Z) - Map / Set:
Map(2) { "a" => 1, "b" => 2 }/Set(3) { 1, 2, 3 } - Error:
Error("boom") - Class instances:
User { name: "Alice", age: 30 }(class name preserved) - Functions:
[Function: name] - Depth-capped at 8 levels to prevent runaway graphs
- Circular references:
- test: 11 new edge-case assertions in
frond.test.ts(frond.test now 254 passing).
v3.10.87 (2026-04-09)
- fix: Dev toolbar no longer vanishes after a hot-reload. The CLI watcher used to call
server.router.clear()on every file change — including template/CSS/JS asset edits — which left a brief window of 404 responses that bypass the dev toolbar injection. The watcher now reports whether a.ts/.tsx/.js/.jsxsource file changed; router re-discovery only runs on code changes, and asset edits pass through without touching the router. Matches the PHP v3.10.87 fix.
v3.10.86 (2026-04-09)
- feat:
foreignKeyfield type onBaseModelauto-wires both sides of a foreign key relationship. Declaringuser_id: { type: "foreignKey", references: "User" }injects abelongsToentry on the declaring model and ahasManyentry on the referenced model via a module-level FK registry. New static methods_processForeignKeys()and_applyFkRegistry()are called lazily before relationship resolution. OptionalrelatedNameoverrides the has-many key. - feat: Cross-framework parity — same FK auto-wiring semantics now available in Python (
ForeignKeyField), PHP ($foreignKeys), and Ruby (foreign_key_field) - docs: Chapter 6 (ORM) updated with a new "foreignKey Field Type — Auto-Wired Relationships" section
v3.10.85 (2026-04-09)
- Version bump for parity with Python and PHP releases
v3.10.84 (2026-04-09)
- fix: Router/middleware was setting
request.user/request.auth/ auth payload totrue(boolean) instead of the actual JWT payload aftervalidToken()was changed to return bool — any code readingrequest.user.subetc. would have failed silently or crashed - fix: CSRF middleware was not correctly rejecting invalid tokens (null check on bool result always passed)
- add: Headless routing auth payload integration tests to prevent regression
v3.10.83 (2026-04-08)
- feat: WebSocket rooms —
joinRoom,leaveRoom,broadcastToRoom,getRoomConnections,roomCount,getClientRooms - feat: Queue signature parity — instance-scoped
push/pop/retry, no topic params on public methods - feat: Auth alias cleanup — removed
createToken/validateToken, canonicalgetToken/validToken
v3.10.70 (2026-04-06)
- New: SSE (Server-Sent Events) support via
response.stream()— pass a generator, framework handles chunked transfer encoding, keep-alive, andtext/event-streamcontent type - New: Chapter 24 added to documentation: Server-Sent Events
- Feature count: 45 (was 44)
- Full parity across Python, PHP, Ruby, Node.js
Tina4 Node.js follows semantic versioning. The major version (3) marks the initial Node.js launch — Tina4 Node.js is new in the v3 line, alongside Tina4 Ruby. The minor version tracks feature additions. The patch version tracks fixes, template engine corrections, and cross-framework parity updates.
This chapter covers every release from v3.0.0 through v3.10.x. Each section groups releases by minor version, lists features added, bugs fixed, and breaking changes with migration code where relevant.
v3.10.68 (2026-04-03) — Full Parity Release
- 100% API parity across Python, PHP, Ruby, Node.js — 30+ issues fixed
- ORM: save() returns self/false, arrays not tuples, toDict/toAssoc, scope registers method, where()/all() on Node, count() on PHP
- Auth: expires_in minutes, PBKDF2 260k, env TINA4_SECRET fallback, API key fallback
- Session: dual-mode flash(), get_flash, cookieHeader, getSessionId
- Database: execute() bool/DatabaseResult, get_last_id/get_error, getColumns, cacheStats
- Request/Response: files dict, query, cookies, contentType, xml(), callable
- Queue: consume() poll_interval
- WebSocket: event naming, connection properties
- GraphQL: schema_sdl() + introspect() on all 4
- Events: emitAsync() on all 4
- i18n: zero-dep YAML support
v3.10.67 (2026-04-03)
- load() returns boolean —
model.load(sql, params)calls selectOne internally, populates the instance, returnstrue/false. UsefindById()for PK lookups - api.upload() added to tina4-js — sends FormData with Bearer token auth for multipart file uploads
- ORM CLAUDE.md rewrite — all method stubs now match actual API signatures
- File upload docs —
req.filesformat documented in CLAUDE.md
v3.10.66 (2026-04-03)
- Metrics file detail fix — clicking bubbles in framework scanning mode now resolves paths correctly via scan root tracking
v3.10.65 (2026-04-03)
- Metrics 3-stage test detection — filename, path, and content matching
- Metrics framework mode — scans framework source with correct relative paths
- tina4 console — interactive REPL with framework loaded
- tina4 env — interactive environment configuration
- Brand — "TINA4 — The Intelligent Native Application 4ramework"
- Quick references — 36 sections, DotEnv API documented
- 37 chapters — 7 new (Events, Localization, Logging, API Client, WSDL/SOAP, DI Container, Service Runner)
- MongoDB + ODBC adapters across all 4 frameworks
- Pagination standardized — limit/offset primary, merged dual-key response
- Port kill-and-take-over on startup
v3.10.60 (2026-04-03)
- tina4 console — interactive Node REPL with framework loaded (db, Router, Database, Log)
- tina4 env — interactive environment configuration
- Brand update — "TINA4 — The Intelligent Native Application 4ramework"
- Dynamic version — reads from package.json at runtime
- Port kill-and-take-over — default port always reclaimed
- findAvailablePort — checks 0.0.0.0 not 127.0.0.1
- MongoDB adapter (mongodb npm), ODBC adapter (odbc npm)
- Pagination standardized — limit/offset primary, merged dual-key response
- Metrics dependency lines — basename fix for correct rendering
- autoMap uppercase — snakeToCamel lowercases first
v3.10.57 (2026-04-02)
- MongoDB adapter —
initDatabase({ url: "mongodb://host:port/db" }), requiresnpm install mongodb - ODBC adapter —
initDatabase({ url: "odbc:///DSN=MyDSN" }), requiresnpm install odbc - Pagination standardized — limit/offset primary, merged dual-key toPaginate() response
- Test port at +1000 — user testing port (e.g. 8148) stable, no hot-reload
- Dynamic version — read from package.json, no hardcoded constant
- Metrics dependency lines — fixed basename parsing
- autoMap uppercase columns — snakeToCamel lowercases first
- ORM TINA4_DATABASE_URL discovery — auto-connect from env for SQLite
- 108 features at 100% parity, 2,646 tests
v3.10.54 (2026-04-02)
- Auto AI dev port — second HTTP server on port+1 with no-reload when TINA4_DEBUG=true
- TINA4_NO_RELOAD env var + --no-reload CLI flag
- SQLite transaction safety — commit/rollback/startTransaction guarded
- autoMap uppercase columns — snakeToCamel lowercases first
- ORM TINA4_DATABASE_URL discovery — auto-connect from env for SQLite
- QueryBuilder docs — added to ORM chapter
v3.10.48 — April 2, 2026
Bug Fixes
Cluster mode requires TINA4_PRODUCTION=true — Worker forking no longer auto-triggers when debug is off. Set TINA4_PRODUCTION=true env var or use tina4 serve --production to enable cluster mode.
v3.10.46 — April 1, 2026
Test Coverage
CSRF middleware expanded to 32 tests matching Python reference. Node.js now at 2,546 tests with full parity across all 49 core areas.
v3.10.45 — April 1, 2026
Notes
Version bump for parity with PHP CLI serve fix. No Node.js-specific changes.
v3.10.44 — April 1, 2026
New Features
Database tab redesign — Split-screen layout with tables navigation on the left and query editor + results on the right. Click-to-select table highlighting.
Copy CSV / Copy JSON — Copy query results to clipboard in CSV or JSON format.
Paste data — Modal for pasting JSON arrays or CSV/tab-separated data. Auto-generates INSERT statements targeting the selected table, or prompts for a new table name with CREATE TABLE generation. SQL input passes through unchanged.
Multi-statement execution — Query runner handles batched SQL statements in a transaction.
Database badge on load — Table count shows immediately without clicking the Database tab.
Star wiggle animation — Empty star (☆) on the landing page with delayed wiggle animation at random intervals.
Bug Fixes
Default port — Node.js default port set to 7148 (PHP=7145, Python=7146, Ruby=7147, Node=7148).
SQLite LIMIT fix — Prevents double-LIMIT errors in the database browser.
browseTable quote escaping — Fixed table name click handlers.
Server handler dispatch regex — Fixed a regex that required whitespace after async in handler functions. Transpiled auto-CRUD handlers producing async(req,res)=> were called with zero arguments, causing crashes.
Cluster mode in tests — Server-based tests now set TINA4_DEBUG=true to prevent cluster mode forking, which was causing ECONNREFUSED errors.
Test Coverage
Massive test expansion — 718 new tests added across Auth (+52), ORM (+30), FakeData (+48), Cache (+23), DevMailbox (+32), Static (+21), Queue (+20), Frond (+57), CLI scaffolding (55), Metrics (69), plus v3.10.44 feature tests and server test fixes. 2,530 tests passing, 0 failures.
v3.10.40 — April 1, 2026
Bug Fixes
Dev overlay version check — Fixed misleading "You are up to date" message when running a version ahead of what's published on npm. The overlay now shows a purple "ahead of npm" message. Also added a breaking changes warning (red banner with changelog link) when a major or minor version update is available.
v3.10.39 — April 1, 2026
New Features
Database.getColumns(tableName) — Returns [{name, type, nullable, default, primaryKey}] for each column. Uses PRAGMA table_info for SQLite and information_schema.columns for PostgreSQL/MySQL/MSSQL.
Database.executeMany(sql, paramSets) — Execute a SQL statement with multiple parameter arrays in a single transaction for atomicity and performance.
BaseModel.create<T>(data) — Static factory method: instantiates, saves, and returns the new record.
BaseModel.find() and BaseModel.load() — aliases for findById() (parity with Python, PHP, Ruby).
seed CLI command — tina4nodejs seed scans src/seeds/*.ts and executes them via tsx.
Router.allRoutes() — alias for getRoutes().
v3.10.38 — April 1, 2026
Code Metrics & Bubble Chart
The dev dashboard (/__dev) now includes a Code Metrics tab with a PHPMetrics-style bubble chart visualization. Files appear as animated bubbles sized by LOC and colored by maintainability index. Click any bubble to drill down into per-function cyclomatic complexity.
The metrics engine uses regex-based TypeScript/JavaScript analysis for zero-dependency static analysis covering cyclomatic complexity, Halstead volume, maintainability index, coupling, and violation detection. File analysis is sorted worst-first. Results are cached for 60 seconds.
AI Context Installer
npx tina4nodejs ai now presents a simple numbered menu instead of auto-detection. Select tools by number, comma-separated or all. Already-installed tools show green. Generated context includes the full skills table.
Dashboard Improvements
Full-width layout, sticky header/tabs, full-screen overlay. Fixed /__dev/ trailing slash returning 404.
Cleanup
Removed demo/ directory. Removed old plan/ spec documents, replaced with PARITY.md and TESTS.md. Central parity matrix added to tina4-book.
v3.10.x — Previous Releases (March 28–31, 2026)
The v3.10 line focused on ORM refinements, Frond template engine fixes, and cross-framework parity. Thirty-two patch releases landed in four days.
Features
autoMap for ORM field mapping (v3.10.1)
The ORM gained automatic translation between JavaScript camelCase and database snake_case. Set autoMap = true on a model and the framework handles the rest.
import { BaseModel } from "tina4-nodejs";
class User extends BaseModel {
static tableName = "users";
static autoMap = true;
static fields = {
id: { type: "integer" as const, primaryKey: true },
firstName: { type: "string" as const }, // maps to first_name
lastName: { type: "string" as const }, // maps to last_name
createdAt: { type: "datetime" as const }, // maps to created_at
};
}Explicit fieldMapping entries take precedence over auto-generated ones. The two utilities snakeToCamel() and camelToSnake() are exported for direct use.
WSDL lifecycle hooks and dotted function names (v3.10.6)
WSDL services gained beforeCall and afterCall hooks. The Frond template engine learned to resolve dotted function names like {{ utils.format(value) }}.
ORM auto-commit on write operations (v3.10.13)
The ORM now commits after every save() and delete() call. Before this change, writes on SQLite would silently succeed in memory but never persist to disk unless you called commit() yourself.
get_next_id() for ID pre-generation (v3.10.14)
Models gained a getNextId() method. It queries the database engine for the next auto-increment value before the insert happens. Useful when you need the ID for a related record before you save the parent.
const nextId = await User.getNextId();
// Use nextId in a related record before saving the UserTemplate filters: to_json, tojson, js_escape (v3.10.16)
Three new Frond filters for passing data from templates to JavaScript:
<script>
const config = {{ settings|to_json }};
const message = "{{ userInput|js_escape }}";
</script>formTokenValue() in Frond templates (v3.10.23)
Templates gained a formTokenValue() function that generates a unique CSRF token per form. Each token carries a nonce in the JWT payload, so two forms on the same page get distinct tokens (v3.10.22).
<form method="POST" action="/submit">
<input type="hidden" name="formToken" value="{{ formTokenValue() }}">
<button type="submit">Send</button>
</form>Arithmetic in set and expressions (v3.10.31)
The Frond engine learned arithmetic. {% set total = price * quantity %} and {{ width + padding }} now work as expected.
MCP server (v3.10.32)
Tina4 Node.js ships a built-in MCP (Model Context Protocol) server. AI coding tools can connect to your running application and inspect routes, models, and database schema.
Bug Fixes
Frond dict[variable_key] access (v3.10.11)
Variable keys in dictionary access were ignored. The engine treated dict[myVar] as a literal string lookup instead of resolving myVar first.
{# Before fix — broken: always looked up the literal string "myVar" #}
{% set key = "name" %}
{{ user[key] }} {# returned undefined #}
{# After fix — works: resolves key to "name", then looks up user["name"] #}
{% set key = "name" %}
{{ user[key] }} {# returns the user's name #}Frond |replace filter backslash escaping (v3.10.15)
The |replace filter mangled backslashes. A replacement string containing \n would insert a literal newline instead of the two characters \n.
Frond variable resolution (v3.10.17)
Nested variable lookups in certain template constructs returned undefined. The engine now walks the scope chain correctly.
Frond inline-if with quoted strings (v3.10.19)
Inline conditionals broke when the true/false branches contained quoted strings with spaces. The parser split on whitespace inside the quotes.
Filters in if conditions (v3.10.21)
Filters inside {% if %} conditions were silently ignored. The condition evaluated the raw value instead of the filtered one.
{# Before fix — broken: |length filter ignored, condition tested the array itself #}
{% if items|length > 0 %}
{# After fix — works: |length runs first, condition compares the number #}
{% if items|length > 0 %}Stale templates in dev mode (v3.10.24)
The dev server cached compiled templates and ignored file changes. Editing a template required a server restart. The fix reads the filesystem on every request in development mode, while production mode keeps the cache.
ORM save/delete transaction safety (v3.10.25)
SQLite threw "cannot commit — no transaction is active" when the ORM called commit() outside an explicit transaction. The ORM now wraps every save() and delete() in a startTransaction()/commit()/rollback() block.
// Before fix — threw on SQLite:
const user = new User({ firstName: "Alice" });
await user.save(); // Error: cannot commit — no transaction is active
// After fix — works on all database engines:
const user = new User({ firstName: "Alice" });
await user.save(); // Transaction handled internallyFrond macro HTML escaping (v3.10.27)
Macro output was HTML-escaped when used inside {{ }} expressions. A macro that generated <div> would render as <div>. Nested macros double-escaped. Macro output is now treated as safe HTML, matching standard Twig behaviour.
js_escape and to_json auto-escaping (v3.10.17–19)
The js_escape and to_json filters produced output that Frond then HTML-escaped. A JSON string like {"key":"value"} became {"key":"value"}. These filters now wrap their output in SafeString to bypass auto-escaping.
Firebird-Specific
Migration runner fixes (v3.10.10)
The migration runner generated SQLite-style AUTOINCREMENT and TEXT types for Firebird. Firebird needs generators and VARCHAR. The runner now emits the correct DDL and generates IDs from a GEN_TINA4_MIGRATION_ID sequence.
v3.9.x — QueryBuilder, Sessions, Path Injection (March 26–27, 2026)
The v3.9 line delivered three features that changed how developers write routes and query data.
Features
QueryBuilder (v3.9.0)
A fluent SQL builder that integrates with the ORM. Chain methods to build queries without writing raw SQL.
import { User } from "./orm/User.js";
// ORM integration
const admins = await User.query()
.where("role = ?", ["admin"])
.orderBy("name")
.limit(10)
.get();
// Standalone usage
import { QueryBuilder } from "tina4-nodejs";
const results = await QueryBuilder.from("orders")
.where("total > ?", [100])
.leftJoin("customers", "orders.customer_id = customers.id")
.orderBy("total", "DESC")
.limit(20)
.get();
// Utility methods
const exists = await User.query().where("email = ?", [email]).exists();
const total = await User.query().where("active = ?", [true]).count();
const first = await User.query().where("id = ?", [1]).first();The builder supports select, where, orWhere, join, leftJoin, groupBy, having, orderBy, limit, first, count, exists, and toSql.
Path parameter injection (v3.9.0)
Route handlers receive path parameters as named function arguments. The framework inspects the handler's parameter names and injects matching values.
import { Router } from "tina4-nodejs";
// The framework injects id as a typed argument
Router.get("/users/{id:int}", async (id, request, response) => {
const user = await User.find(id);
response.json(user);
});Auto-start sessions (v3.9.0)
Every route handler receives a session object on request.session with zero configuration. No middleware to register. No setup code.
Router.post("/login", async (request, response) => {
request.session.set("userId", 42);
request.session.flash("message", "Welcome back.");
response.redirect("/dashboard");
});
Router.get("/dashboard", async (request, response) => {
const userId = request.session.get("userId");
const flash = request.session.getFlash("message");
response.render("dashboard", { userId, flash });
});The session API: get, set, delete, has, clear, destroy, save, regenerate, flash, getFlash, all.
CSRF middleware and secure-by-default (v3.9.1)
POST, PUT, PATCH, and DELETE routes require authentication by default. The framework ships a CsrfMiddleware that validates session-bound form tokens.
// Routes that modify data are protected out of the box
Router.post("/api/orders", async (request, response) => {
// Request must include a valid form token or auth header
// Otherwise the framework returns 403
});Queue parity (v3.9.1)
The queue system gained priority-based push, size(status) to count jobs by state, job.retry(), and job.topic for filtering.
NoSQL QueryBuilder and WebSocket backplane (v3.9.2)
The QueryBuilder gained MongoDB support. WebSocket servers gained a backplane for broadcasting across multiple server instances.
SameSite=Lax cookie default (v3.9.2)
Session cookies now set SameSite=Lax by default. This prevents CSRF attacks from cross-origin form submissions without breaking same-site navigation.
Breaking Changes
Secure-by-default for mutation routes (v3.9.1)
All POST, PUT, PATCH, and DELETE routes now require authentication. If your application has public mutation endpoints, mark them as open:
// BEFORE (v3.8.x) — all routes were open by default
Router.post("/api/feedback", async (request, response) => {
// Anyone could call this
});
// AFTER (v3.9.x) — opt out of auth for public endpoints
Router.post("/api/feedback", async (request, response) => {
// Now requires auth unless you explicitly open it
}).secure(false);session.delete() replaces session.unset() (v3.9.0)
The session method was renamed for cross-framework parity.
// BEFORE (v3.8.x)
request.session.unset("userId");
// AFTER (v3.9.x)
request.session.delete("userId");Bug Fixes
ESM compatibility (v3.9.4)
Internal require() calls broke on Node 22 with ESM-only configurations. All internal imports now use the ESM import() function.
Zero dependencies achieved (v3.9.3)
The better-sqlite3 native module was the last remaining npm dependency. This release replaced it with Node's built-in node:sqlite module. npm install no longer needs a C++ compiler or node-gyp.
v3.8.x — Template Engine, Typed Params, Security (March 25–26, 2026)
The v3.8 line replaced the template engine, added typed route parameters, and introduced production-grade security middleware.
Features
Typed route parameters (v3.8.0)
Route paths gained type annotations. The framework validates and converts parameters before your handler runs.
Router.get("/products/{id:int}", async (request, response) => {
// id is guaranteed to be an integer
// /products/abc returns 404 automatically
});
Router.get("/prices/{amount:float}", async (request, response) => {
// amount is a floating-point number
});
Router.get("/docs/{path:path}", async (request, response) => {
// path captures everything including slashes: /docs/api/v2/users
});Template fallback (v3.8.0)
Requesting /hello now serves src/templates/hello.twig or src/templates/hello.html when no route matches. No explicit route registration needed for static pages.
Connection pooling (v3.8.1)
Set TINA4_DB_POOL=5 in your .env file and the framework creates five database connections. Requests distribute across them in a round-robin pattern.
SecurityHeadersMiddleware (v3.8.1)
A built-in middleware that sets X-Frame-Options, Strict-Transport-Security, Content-Security-Policy, and X-Content-Type-Options on every response.
Validator class (v3.8.1)
Input validation with structured error responses:
import { Validator } from "tina4-nodejs";
Router.post("/api/users", async (request, response) => {
const errors = Validator.validate(request.body, {
email: ["required", "email"],
name: ["required", "minLength:2"],
age: ["integer", "min:18"],
});
if (errors.length > 0) {
return response.json({ errors }, 422);
}
});Upload size limit and Docker support (v3.8.1)
Set TINA4_MAX_UPLOAD_SIZE=10mb to cap file uploads. The tina4 init scaffolding now includes a multi-stage Alpine Dockerfile.
base64encode and base64decode Frond filters (v3.8.0)
{{ sensitiveId|base64encode }}
{{ encodedValue|base64decode }}Production template caching (v3.8.0)
Template lookups are cached in production mode. In development mode, the framework reads the filesystem on every request so changes appear without a restart.
Breaking Changes
Frond replaces Twig dependency (v3.8.4)
The @tina4/twig npm package was removed. The framework now uses its built-in Frond engine for all template rendering. Frond supports the same Twig syntax, but if your templates relied on Twig-specific extensions, you need to rewrite them as Frond filters.
// BEFORE (v3.7.x) — Twig as a separate dependency
// package.json included "@tina4/twig": "^1.x"
// Templates used Twig-specific extensions
// AFTER (v3.8.x) — Frond is built in, zero dependencies
// Remove @tina4/twig from package.json
// Templates use Frond filters (same Twig syntax, built-in engine)Groundwork for zero dependencies (v3.8.4)
v3.8.4 began migrating from better-sqlite3 to Node's built-in node:sqlite module. The migration completed in v3.9.3.
v3.7.x — Template Auto-Serve, Firebird Migrations (March 25, 2026)
A focused release. Two features, no breaking changes.
Features
Template auto-serve at / (v3.7.0)
Place index.html or index.twig in src/templates/ and the framework serves it at /. User-registered GET / routes take priority. When neither exists, the Tina4 landing page appears.
Firebird idempotent migrations (v3.7.0)
ALTER TABLE ADD statements on Firebird now check RDB$RELATION_FIELDS before executing. If the column exists, the migration logs "already applied" and moves on. Other databases and statement types are unaffected.
v3.6.x — Architectural Parity (March 25, 2026)
Features
src/orm/ as primary model directory (v3.6.0)
Models now live in src/orm/ by default, matching the convention across all Tina4 frameworks. The framework still scans src/models/ as a fallback.
Bug Fixes
Outdated API references (v3.6.0)
Internal references to deprecated function names (createToken instead of getToken, validateToken instead of validToken) and route parameter syntax were updated.
v3.5.x — Bundled Frontend, Middleware (March 25, 2026)
Features
Bundled tina4js.min.js (v3.5.0)
The reactive frontend library ships inside the framework. A 13.6 KB file gives your templates reactive signals, client-side routing, and API calls with zero additional installs.
session.clear() (v3.5.0)
Wipe all session data without destroying the session itself. The session ID and cookie persist.
// clear() removes data but keeps the session alive
request.session.clear();
// destroy() ends the session entirely
request.session.destroy();Standardized middleware classes (v3.5.0)
Middleware follows a naming convention: before* classes run before the handler, after* classes run after. Three built-in middleware classes ship with the framework.
v3.4.x — Database, Auth, WebSocket, Uploads (March 24, 2026)
The v3.4 line added production-grade features across several subsystems.
Features
Database class wrapper (v3.4.0)
A constructor-based pattern for database connections, replacing bare function calls:
import { Database } from "tina4-nodejs";
const db = new Database("sqlite://data.db");
const result = await db.fetch("SELECT * FROM users WHERE active = ?", [true]);DatabaseResult with columnInfo() (v3.4.0)
Query results return a DatabaseResult object. Call columnInfo() to inspect column names, types, and sizes without a separate schema query.
const result = await db.fetch("SELECT * FROM users LIMIT 1");
const columns = result.columnInfo();
// [{ name: "id", type: "INTEGER" }, { name: "email", type: "VARCHAR" }, ...]Auth class wrapper (v3.4.0)
Authentication functions grouped into a class with getToken() and validToken() as the primary API. The old names createToken and validateToken remain as aliases.
import { Auth } from "tina4-nodejs";
const token = Auth.getToken({ userId: 42, role: "admin" });
const payload = Auth.validToken(token);Redis session handler (v3.4.0)
Sessions can now persist to Redis for multi-server deployments. Set TINA4_SESSION_HANDLER=redis in your .env file.
Path-scoped WebSocket broadcast (v3.4.0)
Broadcast messages to WebSocket clients subscribed to a specific path:
Router.websocket("/chat/{room}", (connection, request) => {
connection.onMessage((message) => {
connection.broadcast(message); // Only reaches clients on the same path
});
});File uploads with raw Buffer and data_uri (v3.4.0)
Uploaded files include a raw Buffer and a data_uri template filter for embedding images directly in HTML.
Breaking Changes
WebSocket handler signature changed to 3 arguments (v3.4.0)
WebSocket handlers now receive (connection, request, params) instead of (connection, request).
// BEFORE (v3.3.x)
Router.websocket("/ws", (connection, request) => {
// No access to path params
});
// AFTER (v3.4.x)
Router.websocket("/ws/{room}", (connection, request, params) => {
const room = params.room;
});Auth function rename (v3.4.0)
getToken() and validToken() are now the primary function names. The old names createToken and validateToken continue to work as aliases but are deprecated.
// BEFORE (v3.3.x)
const token = Auth.createToken({ userId: 42 });
const valid = Auth.validateToken(token);
// AFTER (v3.4.0)
const token = Auth.getToken({ userId: 42 });
const valid = Auth.validToken(token);Queue job file extension (v3.4.0)
Queue jobs on the file backend use .queue-data instead of .json. Existing .json job files need renaming or the queue treats them as new.
File upload format (v3.4.0)
Upload objects now use { type, content } where content is base64-encoded. Legacy property names (filename, data) remain as aliases.
v3.3.x — Queue API, Field Mapping, Route Chaining (March 24, 2026)
Features
Queue API (v3.3.0)
Produce jobs, consume them with an async generator, and manage their lifecycle:
import { produce, consume, Job } from "tina4-nodejs";
// Produce a job
await produce("email-queue", { to: "user@example.com", subject: "Welcome" });
// Consume jobs
for await (const job of consume("email-queue")) {
try {
await sendEmail(job.data);
job.complete();
} catch (error) {
job.fail(error.message);
}
}Switch between SQLite, RabbitMQ, Kafka, and MongoDB backends with a single .env variable: TINA4_QUEUE_BACKEND=rabbitmq.
ORM fieldMapping (v3.3.0)
Map JavaScript property names to database column names explicitly:
class User extends BaseModel {
static fieldMapping = {
firstName: "first_name",
lastName: "last_name",
};
}Route chaining with .secure() and .cache() (v3.3.0)
Routes return a RouteRef that supports chainable modifiers:
Router.get("/api/products", handler)
.secure()
.cache(300); // Cache for 5 minutesMongoDB queue backend (v3.3.0)
The queue system gained MongoDB as a backend. Set TINA4_QUEUE_BACKEND=mongodb in your .env file.
Database session handler (v3.3.0)
Sessions can persist to any supported database. Set TINA4_SESSION_HANDLER=database.
Dev admin improvements (v3.3.0)
Routes in the dev admin panel are now clickable links that open in a new tab. The error overlay shows full request details.
v3.2.x — Flexible Route Handlers (March 22, 2026)
Features
Zero-param and single-param route handlers (v3.2.0)
Route handlers accept multiple signatures. The framework inspects the function's parameter names and injects the right objects.
// All of these work:
Router.get("/health", () => ({ status: "ok" }));
Router.get("/health", (response) => response.json({ status: "ok" }));
Router.get("/health", (request, response) => response.json({ status: "ok" }));Name your single parameter request or req and the framework passes the request object. Name it anything else and it receives the response.
v3.1.x — Response Parity, Routing API (March 21–22, 2026)
Features
Explicit routing methods (v3.1.0)
Router.get(), Router.post(), Router.put(), Router.delete(), and Router.websocket() replaced generic registration. Each method reads like what it does.
import { Router } from "tina4-nodejs";
Router.get("/users", listUsers);
Router.post("/users", createUser);
Router.put("/users/{id:int}", updateUser);
Router.delete("/users/{id:int}", deleteUser);response.file() and response.render() (v3.1.0)
Two new response methods for serving files and rendering templates:
Router.get("/download", async (request, response) => {
response.file("reports/quarterly.pdf");
});
Router.get("/dashboard", async (request, response) => {
response.render("dashboard", { user: currentUser });
});FetchResult with toPaginate() (v3.1.0)
Database queries return a FetchResult object with built-in pagination:
const result = await db.fetch("SELECT * FROM products", [], 20, 0);
const paginated = result.toPaginate();
// { data: [...], total: 156, page: 1, perPage: 20, totalPages: 8 }ORM relationships (v3.1.0)
hasMany, hasOne, and belongsTo with eager loading:
class User extends BaseModel {
static relationships = {
posts: { type: "hasMany", model: "Post", foreignKey: "user_id" },
profile: { type: "hasOne", model: "Profile", foreignKey: "user_id" },
};
}
const user = await User.find(1, { include: ["posts", "profile"] });Unified Cache, Messenger, and Queue (v3.1.0)
Switch between memory, Redis, and file-based caching with a single environment variable. The messenger and queue systems follow the same pattern. No code changes needed.
# .env
## v3.10.70 (2026-04-06)
- **New:** SSE (Server-Sent Events) support via `response.stream()` — pass a generator, framework handles chunked transfer encoding, keep-alive, and `text/event-stream` content type
- **New:** Chapter 24 added to documentation: Server-Sent Events
- Feature count: 45 (was 44)
- Full parity across Python, PHP, Ruby, Node.js
TINA4_CACHE_BACKEND=redis
TINA4_QUEUE_BACKEND=sqlitetina4 generate command (v3.1.0)
Scaffold models, routes, migrations, and middleware from the command line:
tina4 generate model User
tina4 generate route api/products
tina4 generate migration add_email_to_users
tina4 generate middleware AuthCheckFrond pre-compilation (v3.1.0)
The template engine caches compiled tokens. File rendering runs 2.8x faster than v3.0.0.
v3.0.0 — Initial Release (March 21, 2026)
The initial Node.js release. No Express. No Fastify. No dependencies.
Features
- Native node:http — The server uses Node's built-in HTTP module. Zero framework overhead.
- TypeScript-first — Strict mode, ESM only. No separate build step.
- Database adapters — SQLite, PostgreSQL, MySQL, MSSQL, and Firebird. Same API across all five.
- File-based routing —
src/routes/api/users/[id]/get.tsmaps toGET /api/users/:id. - Auto-CRUD — Generate full REST endpoints from a model definition.
- DevAdmin dashboard — A built-in developer panel with route inspection and database tools.
- AI integration — Auto-detect and configure context for seven AI coding tools.
- 1,311 tests across 43 test files.
- Configurable port and host — Default port 7148, binds to 0.0.0.0 for Docker.
import { startServer } from "tina4-nodejs";
startServer({ port: 7148 });One import. One function call. The server starts and your application is live.
Pre-Release (rc.2–rc.5)
Four release candidates preceded v3.0.0. They stabilized the scaffolding, fixed the init command, added the error overlay, refined the landing page, and established the benchmark suite. If you started a project on a release candidate, upgrade to v3.0.0 and run tina4 init to regenerate your scaffolding files.
Version Timeline
| Version | Date | Headline |
|---|---|---|
| v3.0.0 | March 21 | Initial release — zero dependencies, TypeScript-first |
| v3.1.0 | March 21 | Response parity, ORM relationships, unified cache/queue |
| v3.2.0 | March 22 | Flexible route handler signatures |
| v3.3.0 | March 24 | Queue API, field mapping, route chaining |
| v3.4.0 | March 24 | Database class, auth wrapper, Redis sessions, WebSocket broadcast |
| v3.5.0 | March 25 | Bundled frontend, standardized middleware |
| v3.6.0 | March 25 | src/orm/ as primary model directory |
| v3.7.0 | March 25 | Template auto-serve, Firebird idempotent migrations |
| v3.8.0 | March 25 | Typed route params, template fallback, Frond replaces Twig |
| v3.9.0 | March 26 | QueryBuilder, path injection, auto-start sessions |
| v3.10.0 | March 28 | Cached Frond instances, autoMap, ORM transactions, template fixes |
| v3.10.32 | March 31 | MCP server, arithmetic expressions, current stable |
Forty-two releases in eleven days. Each one a step closer to the framework the code deserves.