tina4-nodejs 3.13.71 → 3.13.73
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.
- package/CLAUDE.md +3 -3
- package/package.json +1 -1
- package/packages/core/gallery/auth/src/routes/api/gallery/auth/login/post.ts +2 -2
- package/packages/core/gallery/auth/src/routes/api/gallery/auth/verify/get.ts +2 -2
- package/packages/core/gallery/auth/src/routes/gallery/auth/get.ts +2 -2
- package/packages/core/gallery/database/src/routes/api/gallery/db/notes/get.ts +3 -3
- package/packages/core/gallery/database/src/routes/api/gallery/db/notes/post.ts +3 -3
- package/packages/core/gallery/database/src/routes/api/gallery/db/tables/get.ts +3 -3
- package/packages/core/gallery/error-overlay/src/routes/api/gallery/crash/get.ts +1 -1
- package/packages/core/gallery/orm/src/routes/api/gallery/products/get.ts +1 -1
- package/packages/core/gallery/orm/src/routes/api/gallery/products/post.ts +1 -1
- package/packages/core/gallery/queue/src/lib/queueDb.ts +2 -2
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/consume/post.ts +1 -1
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/fail/post.ts +1 -1
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/produce/post.ts +1 -1
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/retry/post.ts +1 -1
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/status/get.ts +1 -1
- package/packages/core/gallery/queue/src/routes/gallery/queue/get.ts +1 -1
- package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/get.ts +1 -1
- package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/post.ts +1 -1
- package/packages/core/gallery/templates/src/routes/gallery/page/get.ts +1 -1
- package/packages/core/public/js/tina4-dev-admin.js +203 -170
- package/packages/core/public/js/tina4-dev-admin.min.js +203 -170
- package/packages/core/src/devAdmin.ts +263 -38
- package/packages/core/src/request.ts +32 -6
- package/packages/frond/src/engine.ts +157 -4
- package/packages/orm/src/migration.ts +50 -30
package/CLAUDE.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.
|
|
1
|
+
# CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.73)
|
|
2
2
|
|
|
3
3
|
> This file helps AI assistants (Claude, Copilot, Cursor, etc.) understand and work on this codebase effectively.
|
|
4
4
|
|
|
5
5
|
## What This Project Is
|
|
6
6
|
|
|
7
|
-
Tina4 for Node.js/TypeScript v3.13.
|
|
7
|
+
Tina4 for Node.js/TypeScript v3.13.73 - The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
|
|
8
8
|
|
|
9
9
|
The philosophy: zero ceremony, batteries included, file system as source of truth.
|
|
10
10
|
|
|
@@ -839,7 +839,7 @@ syncModels(discoveredModels); // auto-create tables / add columns
|
|
|
839
839
|
|
|
840
840
|
- SQL files live in `migrations/`, named `NNNNNN_description.sql` (sequential) or `YYYYMMDDHHMMSS_description.sql` (timestamp), and are split on the `;` delimiter.
|
|
841
841
|
- Files are applied in **numeric-prefix order** (`9_` before `10_` — a plain lexical sort misorders unpadded prefixes because `"10" < "9"`). A file with no numeric/timestamp prefix sorts **after** the numbered ones (lexically) and logs a `Log.warning` — its order is undefined.
|
|
842
|
-
- State is tracked
|
|
842
|
+
- State is tracked in the `tina4_migration` table (auto-created per engine, canonical columns `id, migration_name VARCHAR(500) NOT NULL UNIQUE, description VARCHAR(500), batch INTEGER NOT NULL DEFAULT 1, executed_at VARCHAR(50) NOT NULL, passed INTEGER NOT NULL DEFAULT 1` - identical across all four frameworks). A migration is **applied** when a row exists for it with `passed = 1` (the applied-read is `WHERE passed = 1`). `migrate()` writes **only `passed = 1` rows**, and it does so **delete-before-insert**: on success it DELETEs any existing row for that `migration_name` and then INSERTs the fresh `passed = 1` row (the shared `recordApplied()` helper, mirroring the Python master's `_record_applied()`), so the table holds **at most one row per `migration_name`** - latest state wins. A FAILED migration file is rolled back and **no row is written** for it (it is NOT recorded as `passed = 0`; the record step is never reached), and the run STOPS (the `migrate()` summary's `failed[]` carries the failure). The public `recordMigration(name, batch, passed)` API can write a `passed = 0` row (and one may be carried over from an older table); any `passed = 0` row is treated as **not applied**. Because the success path deletes any existing row for the `migration_name` before the `passed = 1` INSERT, a leftover `passed = 0` row **re-applies cleanly** on the next `migrate()` - the stale row is superseded rather than colliding on the UNIQUE `migration_name` (that collision previously wedged a re-run). Fix the bad file and re-run.
|
|
843
843
|
- **Each migration FILE is wrapped in its own transaction.** On a failure the file rolls back and `migrate()` **STOPS** — later files are never applied on top of a missing earlier one (parity with Python/PHP/Ruby). Already-applied files stay applied. The explicit `tina4 migrate` CLI surfaces a non-empty `failed[]` as a non-zero exit; startup auto-migration logs it and the service still boots (see `TINA4_AUTO_MIGRATE` above).
|
|
844
844
|
- **Atomicity caveat:** per-file transactions are truly atomic only on engines with **transactional DDL (PostgreSQL)**. MySQL, Firebird, and SQLite auto-commit DDL, so a multi-statement migration that fails midway on those engines leaves earlier statements applied — keep one logical change per file. `CREATE TABLE` and `ALTER TABLE ... ADD` are made idempotent on Firebird/MSSQL (existence-checked via `RDB$RELATION_FIELDS` / `tableExists`) so a re-run with a raw `CREATE`/`ADD` does not error "object already exists"; SQLite/MySQL/PostgreSQL support `IF NOT EXISTS` and are left to the engine. Only a genuine already-exists is skipped — every other error still raises.
|
|
845
845
|
- The stored-proc block delimiters (`$$ … $$` / `// … //`) are extracted before splitting, but a `//` preceded by a colon is **not** treated as a delimiter, so a URL (`https://…`) or any `://` literal inside a migration is never swallowed as an opaque block.
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Gallery: Auth — login endpoint, returns a JWT token. */
|
|
2
|
-
import type { Tina4Request, Tina4Response } from "
|
|
2
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
3
3
|
|
|
4
4
|
export default async function (req: Tina4Request, res: Tina4Response) {
|
|
5
5
|
const body = (req.body as Record<string, unknown>) ?? {};
|
|
@@ -7,7 +7,7 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
7
7
|
const password = (body.password as string) ?? "";
|
|
8
8
|
|
|
9
9
|
if (username && password) {
|
|
10
|
-
// In a real app: import { Auth } from "
|
|
10
|
+
// In a real app: import { Auth } from "tina4-nodejs";
|
|
11
11
|
// const auth = new Auth();
|
|
12
12
|
// const token = auth.createToken({ username, role: "user" });
|
|
13
13
|
// For the gallery demo, generate a simple base64 token
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/** Gallery: Auth — verify a JWT token. */
|
|
2
|
-
import type { Tina4Request, Tina4Response } from "
|
|
2
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
3
3
|
|
|
4
4
|
export default async function (req: Tina4Request, res: Tina4Response) {
|
|
5
5
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
6
6
|
const token = url.searchParams.get("token") ?? "";
|
|
7
7
|
|
|
8
|
-
// In a real app: import { Auth } from "
|
|
8
|
+
// In a real app: import { Auth } from "tina4-nodejs";
|
|
9
9
|
// const auth = new Auth();
|
|
10
10
|
// const isValid = auth.validateToken(token);
|
|
11
11
|
// For the gallery demo, just check the token has 3 parts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Gallery: Auth — JWT login with a visual demo page. */
|
|
2
|
-
import type { Tina4Request, Tina4Response } from "
|
|
2
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
3
3
|
|
|
4
4
|
export default async function (_req: Tina4Request, res: Tina4Response) {
|
|
5
5
|
const html = `<!DOCTYPE html>
|
|
@@ -44,7 +44,7 @@ export default async function (_req: Tina4Request, res: Tina4Response) {
|
|
|
44
44
|
<div class="card bg-dark mt-4" style="border:1px solid #334155;">
|
|
45
45
|
<div class="card-body">
|
|
46
46
|
<h6 style="color:#e2e8f0;">How it works</h6>
|
|
47
|
-
<pre style="background:#0f172a;color:#4ade80;padding:1rem;border-radius:0.5rem;font-size:0.8rem;"><code>import { Auth } from "
|
|
47
|
+
<pre style="background:#0f172a;color:#4ade80;padding:1rem;border-radius:0.5rem;font-size:0.8rem;"><code>import { Auth } from "tina4-nodejs";
|
|
48
48
|
const auth = new Auth();
|
|
49
49
|
const token = auth.createToken({ username: "admin" });
|
|
50
50
|
const payload = auth.getPayload(token);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/** Gallery: Database — list notes from the gallery database. */
|
|
2
|
-
import type { Tina4Request, Tina4Response } from "
|
|
2
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
3
3
|
|
|
4
4
|
export default async function (_req: Tina4Request, res: Tina4Response) {
|
|
5
5
|
try {
|
|
6
|
-
const orm = await import("
|
|
7
|
-
const db = orm.initDatabase({ type: "sqlite", path: "./data/gallery.db" });
|
|
6
|
+
const orm = await import("tina4-nodejs/orm");
|
|
7
|
+
const db = await orm.initDatabase({ type: "sqlite", path: "./data/gallery.db" });
|
|
8
8
|
const result = await db.fetch("SELECT * FROM gallery_notes ORDER BY id DESC", undefined, 50);
|
|
9
9
|
return res.json(result.records ?? []);
|
|
10
10
|
} catch (e: unknown) {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/** Gallery: Database — create a note in the gallery database. */
|
|
2
|
-
import type { Tina4Request, Tina4Response } from "
|
|
2
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
3
3
|
|
|
4
4
|
export default async function (req: Tina4Request, res: Tina4Response) {
|
|
5
5
|
try {
|
|
6
6
|
const body = (req.body as Record<string, unknown>) ?? {};
|
|
7
|
-
const orm = await import("
|
|
8
|
-
const db = orm.initDatabase({ type: "sqlite", path: "./data/gallery.db" });
|
|
7
|
+
const orm = await import("tina4-nodejs/orm");
|
|
8
|
+
const db = await orm.initDatabase({ type: "sqlite", path: "./data/gallery.db" });
|
|
9
9
|
await db.insert("gallery_notes", {
|
|
10
10
|
title: (body.title as string) ?? "Untitled",
|
|
11
11
|
body: (body.body as string) ?? "",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/** Gallery: Database — list tables in the gallery database. */
|
|
2
|
-
import type { Tina4Request, Tina4Response } from "
|
|
2
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
3
3
|
|
|
4
4
|
export default async function (_req: Tina4Request, res: Tina4Response) {
|
|
5
5
|
try {
|
|
6
|
-
const orm = await import("
|
|
7
|
-
const db = orm.initDatabase({ type: "sqlite", path: "./data/gallery.db" });
|
|
6
|
+
const orm = await import("tina4-nodejs/orm");
|
|
7
|
+
const db = await orm.initDatabase({ type: "sqlite", path: "./data/gallery.db" });
|
|
8
8
|
|
|
9
9
|
await db.execute(`
|
|
10
10
|
CREATE TABLE IF NOT EXISTS gallery_notes (
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - Request details (method, path, headers)
|
|
8
8
|
* - Environment info (framework version, Node.js version)
|
|
9
9
|
*/
|
|
10
|
-
import type { Tina4Request, Tina4Response } from "
|
|
10
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
11
11
|
|
|
12
12
|
export default async function (_req: Tina4Request, _res: Tina4Response) {
|
|
13
13
|
// Simulate a realistic error — accessing a missing property
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Gallery: ORM — list products (demo data). */
|
|
2
|
-
import type { Tina4Request, Tina4Response } from "
|
|
2
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
3
3
|
|
|
4
4
|
export default async function (_req: Tina4Request, res: Tina4Response) {
|
|
5
5
|
return res.json({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Gallery: ORM — create a product (demo). */
|
|
2
|
-
import type { Tina4Request, Tina4Response } from "
|
|
2
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
3
3
|
|
|
4
4
|
export default async function (req: Tina4Request, res: Tina4Response) {
|
|
5
5
|
const data = (req.body as Record<string, unknown>) ?? {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Shared SQLite database helper for the queue gallery demo. */
|
|
2
|
-
import type { DatabaseAdapter } from "
|
|
2
|
+
import type { DatabaseAdapter } from "tina4-nodejs/orm";
|
|
3
3
|
|
|
4
4
|
let _db: DatabaseAdapter | null = null;
|
|
5
5
|
|
|
@@ -7,7 +7,7 @@ export const MAX_RETRIES = 3;
|
|
|
7
7
|
|
|
8
8
|
export async function getQueueDb(): Promise<DatabaseAdapter> {
|
|
9
9
|
if (_db) return _db;
|
|
10
|
-
const orm = await import("
|
|
10
|
+
const orm = await import("tina4-nodejs/orm");
|
|
11
11
|
_db = await orm.initDatabase({ type: "sqlite", path: "./data/gallery_queue.db" });
|
|
12
12
|
try {
|
|
13
13
|
_db.execute(`CREATE TABLE IF NOT EXISTS tina4_queue (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Gallery: Queue — consume (complete) the next pending message. */
|
|
2
|
-
import type { Tina4Request, Tina4Response } from "
|
|
2
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
3
3
|
import { getQueueDb, now } from "../../../../lib/queueDb.js";
|
|
4
4
|
|
|
5
5
|
export default async function (_req: Tina4Request, res: Tina4Response) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Gallery: Queue — deliberately fail the next pending message. */
|
|
2
|
-
import type { Tina4Request, Tina4Response } from "
|
|
2
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
3
3
|
import { getQueueDb, now } from "../../../../lib/queueDb.js";
|
|
4
4
|
|
|
5
5
|
export default async function (_req: Tina4Request, res: Tina4Response) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Gallery: Queue — produce a message into the queue. */
|
|
2
|
-
import type { Tina4Request, Tina4Response } from "
|
|
2
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
3
3
|
import { getQueueDb, now } from "../../../../lib/queueDb.js";
|
|
4
4
|
|
|
5
5
|
export default async function (req: Tina4Request, res: Tina4Response) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Gallery: Queue — retry failed messages (re-queue under max retries). */
|
|
2
|
-
import type { Tina4Request, Tina4Response } from "
|
|
2
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
3
3
|
import { getQueueDb, now, MAX_RETRIES } from "../../../../lib/queueDb.js";
|
|
4
4
|
|
|
5
5
|
export default async function (_req: Tina4Request, res: Tina4Response) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Gallery: Queue — list all messages with statuses. */
|
|
2
|
-
import type { Tina4Request, Tina4Response } from "
|
|
2
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
3
3
|
import { getQueueDb, MAX_RETRIES } from "../../../../lib/queueDb.js";
|
|
4
4
|
|
|
5
5
|
export default async function (_req: Tina4Request, res: Tina4Response) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Gallery: Queue — interactive queue demo with visual web UI. */
|
|
2
|
-
import type { Tina4Request, Tina4Response } from "
|
|
2
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
3
3
|
|
|
4
4
|
export default async function (_req: Tina4Request, res: Tina4Response) {
|
|
5
5
|
const html = `<!DOCTYPE html>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Gallery: REST API — simple JSON GET endpoint. */
|
|
2
|
-
import type { Tina4Request, Tina4Response } from "
|
|
2
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
3
3
|
|
|
4
4
|
export default async function (req: Tina4Request, res: Tina4Response) {
|
|
5
5
|
return res.json({ message: "Hello from Tina4!", method: "GET" });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Gallery: REST API — simple JSON POST endpoint. */
|
|
2
|
-
import type { Tina4Request, Tina4Response } from "
|
|
2
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
3
3
|
|
|
4
4
|
export default async function (req: Tina4Request, res: Tina4Response) {
|
|
5
5
|
const data = (req.body as Record<string, unknown>) ?? {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Gallery: Templates — render an HTML page with dynamic data via template export. */
|
|
2
|
-
import type { Tina4Request, Tina4Response } from "
|
|
2
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
3
3
|
|
|
4
4
|
export const template = "gallery_page.twig";
|
|
5
5
|
|