tina4-nodejs 3.13.1 → 3.13.2

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 CHANGED
@@ -1,10 +1,10 @@
1
- # CLAUDE.md — AI Developer Guide for tina4-nodejs (v3.13.1)
1
+ # CLAUDE.md — AI Developer Guide for tina4-nodejs (v3.13.2)
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.1 — The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
7
+ Tina4 for Node.js/TypeScript v3.13.2 — 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
 
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
 
5
5
 
6
- "version": "3.13.1",
6
+ "version": "3.13.2",
7
7
 
8
8
  "type": "module",
9
9
  "description": "Tina4 for Node.js/TypeScript \u2014 54 built-in features, zero dependencies",
@@ -424,7 +424,7 @@ npx tina4nodejs routes # List routes
424
424
  ## Database
425
425
 
426
426
  Default: SQLite via \`node:sqlite\`. Adapters for PostgreSQL, MySQL, MSSQL, Firebird.
427
- Set \`DATABASE_URL\` in \`.env\` (e.g. \`postgres://localhost:5432/mydb\`).
427
+ Set \`TINA4_DATABASE_URL\` in \`.env\` (e.g. \`postgres://localhost:5432/mydb\`).
428
428
 
429
429
  ## Auth
430
430
 
@@ -485,7 +485,7 @@ npx tina4nodejs routes # List routes
485
485
  ## Database
486
486
 
487
487
  Default: SQLite via \`node:sqlite\`. Adapters for PostgreSQL, MySQL, MSSQL, Firebird.
488
- Set \`DATABASE_URL\` in \`.env\`.
488
+ Set \`TINA4_DATABASE_URL\` in \`.env\`.
489
489
  `;
490
490
  }
491
491
 
@@ -593,7 +593,7 @@ npx tina4nodejs routes # List routes
593
593
  ## Database
594
594
 
595
595
  Default: SQLite via \`node:sqlite\`. Adapters for PostgreSQL, MySQL, MSSQL, Firebird.
596
- Set \`DATABASE_URL\` in \`.env\` (e.g. \`sqlite:///path/to/db.sqlite\`, \`postgres://localhost:5432/mydb\`).
596
+ Set \`TINA4_DATABASE_URL\` in \`.env\` (e.g. \`sqlite:///path/to/db.sqlite\`, \`postgres://localhost:5432/mydb\`).
597
597
 
598
598
  ## Auth
599
599
 
@@ -693,7 +693,7 @@ npx tina4nodejs routes # List routes
693
693
  ## Database
694
694
 
695
695
  Default: SQLite via \`node:sqlite\`. Adapters for PostgreSQL, MySQL, MSSQL, Firebird.
696
- Set \`DATABASE_URL\` in \`.env\`.
696
+ Set \`TINA4_DATABASE_URL\` in \`.env\`.
697
697
 
698
698
  ## Auth
699
699
 
@@ -251,30 +251,70 @@ function resolveIncludes(
251
251
  // ── Math Evaluation ──────────────────────────────────────────────
252
252
 
253
253
  function evalMath(scss: string): string {
254
- return scss.replace(
254
+ // Mixed-unit arithmetic is left verbatim — that is exactly what CSS calc()
255
+ // is for, and folding it silently produces invalid output (see tina4-nodejs#1).
256
+ // Math inside calc(...) is preserved untouched on the same principle: the
257
+ // author asked the browser to compute it.
258
+ //
259
+ // Rules for folding:
260
+ // * Both operands unitless → fold
261
+ // * Same unit on both operands → fold, keep unit
262
+ // * One operand unitless for * or / → fold, keep the other unit
263
+ // * Anything else (mixed units on +/-, etc) → leave verbatim
264
+
265
+ // Step 1 — mask calc(...) ranges so the math regex cannot eat into them.
266
+ const placeholders: string[] = [];
267
+ const masked = scss.replace(/calc\([^()]*\)/g, (m) => {
268
+ placeholders.push(m);
269
+ return `\x00CALC${placeholders.length - 1}\x00`;
270
+ });
271
+
272
+ // Step 2 — run the math fold on what remains.
273
+ const folded = masked.replace(
255
274
  /([\d.]+)([a-z%]*)\s*([+\-*/])\s*([\d.]+)([a-z%]*)/g,
256
- (_m, n1: string, u1: string, op: string, n2: string, _u2: string) => {
257
- try {
258
- const num1 = parseFloat(n1);
259
- const num2 = parseFloat(n2);
260
- let result: number;
261
- switch (op) {
262
- case "+": result = num1 + num2; break;
263
- case "-": result = num1 - num2; break;
264
- case "*": result = num1 * num2; break;
265
- case "/": result = num2 !== 0 ? num1 / num2 : 0; break;
266
- default: return _m;
267
- }
268
- const unit = u1 || "";
269
- if (result === Math.floor(result)) {
270
- return `${Math.floor(result)}${unit}`;
271
- }
272
- return `${result.toFixed(2)}${unit}`;
273
- } catch {
274
- return _m;
275
+ (full, n1: string, u1: string, op: string, n2: string, u2: string) => {
276
+ const num1 = parseFloat(n1);
277
+ const num2 = parseFloat(n2);
278
+ if (Number.isNaN(num1) || Number.isNaN(num2)) return full;
279
+
280
+ const unit1 = u1 || "";
281
+ const unit2 = u2 || "";
282
+
283
+ // Decide result unit; bail if units are incompatible.
284
+ let unit: string;
285
+ if (unit1 === unit2) {
286
+ unit = unit1;
287
+ } else if ((op === "*" || op === "/") && unit1 === "") {
288
+ unit = unit2;
289
+ } else if ((op === "*" || op === "/") && unit2 === "") {
290
+ unit = unit1;
291
+ } else {
292
+ return full;
275
293
  }
294
+
295
+ let result: number;
296
+ switch (op) {
297
+ case "+": result = num1 + num2; break;
298
+ case "-": result = num1 - num2; break;
299
+ case "*": result = num1 * num2; break;
300
+ case "/":
301
+ if (num2 === 0) return full;
302
+ result = num1 / num2;
303
+ break;
304
+ default: return full;
305
+ }
306
+
307
+ if (result === Math.floor(result)) {
308
+ return `${Math.floor(result)}${unit}`;
309
+ }
310
+ return `${result.toFixed(2)}${unit}`;
276
311
  }
277
312
  );
313
+
314
+ // Step 3 — restore the calc() ranges verbatim.
315
+ return folded.replace(/\x00CALC(\d+)\x00/g, (_m, idx: string) => {
316
+ return placeholders[parseInt(idx, 10)];
317
+ });
278
318
  }
279
319
 
280
320
  // ── Nesting Flattener ────────────────────────────────────────────
@@ -5,7 +5,7 @@
5
5
  * Stores sessions in a `tina4_session` table with JSON data and expiry.
6
6
  *
7
7
  * Configure via environment variables:
8
- * DATABASE_URL (default: "sqlite:///data/tina4_sessions.db")
8
+ * TINA4_DATABASE_URL (default: "sqlite:///data/tina4_sessions.db")
9
9
  *
10
10
  * The handler dynamically imports `better-sqlite3` and throws a clear
11
11
  * error if the package is not installed.
@@ -20,7 +20,7 @@ interface SessionData {
20
20
  }
21
21
 
22
22
  export interface DatabaseSessionConfig {
23
- /** SQLite database file path (default: extracted from DATABASE_URL or "data/tina4_sessions.db") */
23
+ /** SQLite database file path (default: extracted from TINA4_DATABASE_URL or "data/tina4_sessions.db") */
24
24
  dbPath?: string;
25
25
  }
26
26
 
@@ -206,7 +206,7 @@ export class BaseModel {
206
206
 
207
207
  /**
208
208
  * Get the database adapter for this model.
209
- * If no adapter is registered, attempts auto-discovery from DATABASE_URL.
209
+ * If no adapter is registered, attempts auto-discovery from TINA4_DATABASE_URL.
210
210
  * SQLite URLs are initialised synchronously. Other engines require initDatabase()
211
211
  * to be called before first use.
212
212
  */
@@ -229,12 +229,12 @@ export class BaseModel {
229
229
  return adapter;
230
230
  }
231
231
  throw new Error(
232
- `DATABASE_URL is set to a non-SQLite engine ("${parsed.type}"). ` +
232
+ `TINA4_DATABASE_URL is set to a non-SQLite engine ("${parsed.type}"). ` +
233
233
  `Call await initDatabase() at startup before using ORM models.`,
234
234
  );
235
235
  }
236
236
  throw new Error(
237
- "No database adapter configured. Call initDatabase() or set DATABASE_URL in .env.",
237
+ "No database adapter configured. Call initDatabase() or set TINA4_DATABASE_URL in .env.",
238
238
  );
239
239
  }
240
240
  }
@@ -60,7 +60,7 @@ export interface DatabaseConfig {
60
60
  }
61
61
 
62
62
  /**
63
- * Parsed result from a DATABASE_URL connection string.
63
+ * Parsed result from a TINA4_DATABASE_URL connection string.
64
64
  */
65
65
  export interface ParsedDatabaseUrl {
66
66
  type: "sqlite" | "postgres" | "mysql" | "mssql" | "firebird" | "mongodb" | "odbc";
@@ -75,7 +75,7 @@ export interface ParsedDatabaseUrl {
75
75
  }
76
76
 
77
77
  /**
78
- * Parse a DATABASE_URL connection string into its components.
78
+ * Parse a TINA4_DATABASE_URL connection string into its components.
79
79
  *
80
80
  * Supported formats:
81
81
  * sqlite:///path/to/db.sqlite
@@ -325,10 +325,10 @@ export class Database {
325
325
 
326
326
  /**
327
327
  * Create a Database from an environment variable.
328
- * @param envKey - Name of the env var holding the connection URL. Defaults to "DATABASE_URL".
328
+ * @param envKey - Name of the env var holding the connection URL. Defaults to "TINA4_DATABASE_URL".
329
329
  * @param pool - Number of pooled connections (0 = single, N>0 = round-robin)
330
330
  */
331
- static async fromEnv(envKey = "DATABASE_URL", pool: number = 0): Promise<Database> {
331
+ static async fromEnv(envKey = "TINA4_DATABASE_URL", pool: number = 0): Promise<Database> {
332
332
  const url = process.env[envKey];
333
333
  if (!url) {
334
334
  throw new Error(`Environment variable "${envKey}" is not set.`);