tina4-nodejs 3.10.17 → 3.10.19

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.10.15)
1
+ # CLAUDE.md — AI Developer Guide for tina4-nodejs (v3.10.19)
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.10.15 — a convention-over-configuration structural paradigm. **Not a framework.** The developer writes TypeScript; Tina4 is invisible infrastructure.
7
+ Tina4 for Node.js/TypeScript v3.10.19 — a convention-over-configuration structural paradigm. **Not a framework.** 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tina4-nodejs",
3
- "version": "3.10.17",
3
+ "version": "3.10.19",
4
4
  "type": "module",
5
5
  "description": "This is not a framework. Tina4 for Node.js/TypeScript — zero deps, 38 built-in features.",
6
6
  "keywords": ["tina4", "framework", "web", "api", "orm", "graphql", "websocket", "typescript"],
@@ -308,6 +308,23 @@ function evalExpr(expr: string, context: Record<string, unknown>): unknown {
308
308
  }
309
309
  }
310
310
 
311
+ // Parenthesized sub-expression: (expr) — strip parens and evaluate inner
312
+ if (expr.length >= 2 && expr[0] === "(" && expr.endsWith(")")) {
313
+ let depth = 0;
314
+ let matched = true;
315
+ for (let pi = 0; pi < expr.length; pi++) {
316
+ if (expr[pi] === "(") depth++;
317
+ else if (expr[pi] === ")") depth--;
318
+ if (depth === 0 && pi < expr.length - 1) {
319
+ matched = false;
320
+ break;
321
+ }
322
+ }
323
+ if (matched) {
324
+ return evalExpr(expr.slice(1, -1), context);
325
+ }
326
+ }
327
+
311
328
  // Ternary: condition ? true_val : false_val
312
329
  // Match carefully to handle nested ternaries
313
330
  const ternaryIdx = findTernary(expr);
@@ -323,11 +340,17 @@ function evalExpr(expr: string, context: Record<string, unknown>): unknown {
323
340
  }
324
341
  }
325
342
 
326
- // Jinja2-style inline if: value if condition else other_value
327
- const inlineIfMatch = expr.match(/^(.+?)\s+if\s+(.+?)\s+else\s+(.+)$/);
328
- if (inlineIfMatch) {
329
- const cond = evalExpr(inlineIfMatch[2], context);
330
- return cond ? evalExpr(inlineIfMatch[1], context) : evalExpr(inlineIfMatch[3], context);
343
+ // Jinja2-style inline if: value if condition else other_value — quote-aware
344
+ const ifIdx = findOutsideQuotes(expr, " if ");
345
+ if (ifIdx >= 0) {
346
+ const elseIdx = findOutsideQuotes(expr, " else ");
347
+ if (elseIdx >= 0 && elseIdx > ifIdx) {
348
+ const valuePart = expr.slice(0, ifIdx).trim();
349
+ const condPart = expr.slice(ifIdx + 4, elseIdx).trim();
350
+ const elsePart = expr.slice(elseIdx + 6).trim();
351
+ const cond = evalExpr(condPart, context);
352
+ return cond ? evalExpr(valuePart, context) : evalExpr(elsePart, context);
353
+ }
331
354
  }
332
355
 
333
356
  // Null coalescing: value ?? "default"
@@ -944,9 +967,9 @@ const BUILTIN_FILTERS: Record<string, FilterFn> = {
944
967
  dump: (v) => JSON.stringify(v),
945
968
  formToken: (v?: unknown) => _generateFormToken(v != null ? String(v) : ""),
946
969
  form_token: (v?: unknown) => _generateFormToken(v != null ? String(v) : ""),
947
- tojson: (v, indent) => indent !== undefined ? JSON.stringify(v, null, parseInt(String(indent), 10)) : JSON.stringify(v),
948
- to_json: (v, indent) => indent !== undefined ? JSON.stringify(v, null, parseInt(String(indent), 10)) : JSON.stringify(v),
949
- js_escape: (v) => String(v).replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"),
970
+ tojson: (v, indent) => new SafeString(indent !== undefined ? JSON.stringify(v, null, parseInt(String(indent), 10)) : JSON.stringify(v)),
971
+ to_json: (v, indent) => new SafeString(indent !== undefined ? JSON.stringify(v, null, parseInt(String(indent), 10)) : JSON.stringify(v)),
972
+ js_escape: (v) => new SafeString(String(v).replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t")),
950
973
  };
951
974
 
952
975
  // ── Form Token ────────────────────────────────────────────────