tina4-nodejs 3.10.16 → 3.10.18

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.18)
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.18 — 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.16",
3
+ "version": "3.10.18",
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"],
@@ -136,7 +136,9 @@ function resolveVar(expr: string, context: Record<string, unknown>): unknown {
136
136
  }
137
137
 
138
138
  // Dotted path with bracket access — split on . and [...] but not . inside parentheses
139
+ // Track which parts came from bracket access (need variable resolution)
139
140
  const parts: string[] = [];
141
+ const fromBracket: boolean[] = [];
140
142
  {
141
143
  let current = "";
142
144
  let depth = 0;
@@ -152,27 +154,30 @@ function resolveVar(expr: string, context: Record<string, unknown>): unknown {
152
154
  if (ch === '(') { depth++; current += ch; continue; }
153
155
  if (ch === ')') { depth--; current += ch; continue; }
154
156
  if (ch === '.' && depth === 0) {
155
- if (current) parts.push(current);
157
+ if (current) { parts.push(current); fromBracket.push(false); }
156
158
  current = "";
157
159
  continue;
158
160
  }
159
161
  if (ch === '[' && depth === 0) {
160
- if (current) parts.push(current);
162
+ if (current) { parts.push(current); fromBracket.push(false); }
161
163
  current = "";
162
164
  const end = expr.indexOf(']', i + 1);
163
165
  if (end !== -1) {
164
166
  parts.push(expr.slice(i + 1, end));
167
+ fromBracket.push(true);
165
168
  i = end;
166
169
  }
167
170
  continue;
168
171
  }
169
172
  current += ch;
170
173
  }
171
- if (current) parts.push(current);
174
+ if (current) { parts.push(current); fromBracket.push(false); }
172
175
  }
173
176
 
174
177
  let value: unknown = context;
175
- for (const part of parts) {
178
+ for (let pi = 0; pi < parts.length; pi++) {
179
+ const part = parts[pi];
180
+ const isBracket = fromBracket[pi];
176
181
  if (value === null || value === undefined) return null;
177
182
 
178
183
  // Check for method call: name(args)
@@ -206,10 +211,13 @@ function resolveVar(expr: string, context: Record<string, unknown>): unknown {
206
211
  const asNum = parseInt(part, 10);
207
212
  if (!isNaN(asNum) && String(asNum) === part) {
208
213
  key = asNum;
209
- } else {
210
- // Try to resolve as a variable from context
214
+ } else if (isBracket) {
215
+ // Only resolve as a variable from context for bracket-derived parts
211
216
  const resolved = context[part];
212
217
  key = resolved !== undefined ? String(resolved) : part;
218
+ } else {
219
+ // Dot-derived parts or root — use the part name directly as the key
220
+ key = part;
213
221
  }
214
222
  }
215
223
 
@@ -666,45 +674,14 @@ function parseFilterChain(expr: string): [string, [string, string[]][]] {
666
674
  return [variable, filters];
667
675
  }
668
676
 
669
- function processEscapes(s: string): string {
670
- let out = "";
671
- for (let i = 0; i < s.length; i++) {
672
- if (s[i] === "\\" && i + 1 < s.length) {
673
- const nxt = s[i + 1];
674
- switch (nxt) {
675
- case "n": out += "\n"; i++; break;
676
- case "t": out += "\t"; i++; break;
677
- case "\\": out += "\\"; i++; break;
678
- case "'": out += "'"; i++; break;
679
- case '"': out += '"'; i++; break;
680
- default: out += "\\"; break;
681
- }
682
- } else {
683
- out += s[i];
684
- }
685
- }
686
- return out;
687
- }
688
-
689
677
  function parseArgs(raw: string): string[] {
690
678
  const args: string[] = [];
691
679
  let current = "";
692
680
  let inQuote: string | null = null;
693
681
  let wasQuoted = false;
694
682
  let depth = 0;
695
- let escaped = false;
696
683
 
697
684
  for (const ch of raw) {
698
- if (escaped) {
699
- current += ch;
700
- escaped = false;
701
- continue;
702
- }
703
- if (ch === "\\" && inQuote) {
704
- current += ch;
705
- escaped = true;
706
- continue;
707
- }
708
685
  if (inQuote) {
709
686
  if (ch === inQuote) {
710
687
  inQuote = null;
@@ -723,7 +700,7 @@ function parseArgs(raw: string): string[] {
723
700
  if (ch === "(") { depth++; current += ch; continue; }
724
701
  if (ch === ")") { depth--; current += ch; continue; }
725
702
  if (ch === "," && depth === 0) {
726
- args.push(wasQuoted ? processEscapes(current) : current.trim());
703
+ args.push(wasQuoted ? current : current.trim());
727
704
  current = "";
728
705
  wasQuoted = false;
729
706
  continue;
@@ -731,7 +708,7 @@ function parseArgs(raw: string): string[] {
731
708
  current += ch;
732
709
  }
733
710
 
734
- const final = wasQuoted ? processEscapes(current) : current.trim();
711
+ const final = wasQuoted ? current : current.trim();
735
712
  if (final !== "" || wasQuoted) {
736
713
  args.push(final);
737
714
  }
@@ -967,9 +944,9 @@ const BUILTIN_FILTERS: Record<string, FilterFn> = {
967
944
  dump: (v) => JSON.stringify(v),
968
945
  formToken: (v?: unknown) => _generateFormToken(v != null ? String(v) : ""),
969
946
  form_token: (v?: unknown) => _generateFormToken(v != null ? String(v) : ""),
970
- to_json: (v) => JSON.stringify(v).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026"),
971
- tojson: (v) => JSON.stringify(v).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026"),
972
- js_escape: (v) => String(v).replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r"),
947
+ tojson: (v, indent) => new SafeString(indent !== undefined ? JSON.stringify(v, null, parseInt(String(indent), 10)) : JSON.stringify(v)),
948
+ to_json: (v, indent) => new SafeString(indent !== undefined ? JSON.stringify(v, null, parseInt(String(indent), 10)) : JSON.stringify(v)),
949
+ 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")),
973
950
  };
974
951
 
975
952
  // ── Form Token ────────────────────────────────────────────────