tina4-nodejs 3.10.87 → 3.10.88

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tina4-nodejs",
3
- "version": "3.10.87",
3
+ "version": "3.10.88",
4
4
  "type": "module",
5
5
  "description": "Tina4 for Node.js/TypeScript — 54 built-in features, zero dependencies",
6
6
  "keywords": ["tina4", "framework", "web", "api", "orm", "graphql", "websocket", "typescript"],
@@ -19,6 +19,103 @@ class SafeString {
19
19
  toString() { return this.value; }
20
20
  }
21
21
 
22
+ /**
23
+ * Produce a human-readable, debugger-friendly inspection of any value.
24
+ *
25
+ * Equivalent to PHP's var_dump, Python's repr, and Ruby's inspect. Unlike
26
+ * JSON.stringify (the previous implementation) this handles:
27
+ * - Circular references (marked as [Circular])
28
+ * - BigInt (shown as `123n`)
29
+ * - undefined, Symbol, and function values (shown inline, not dropped)
30
+ * - Date, Map, Set, Error, RegExp (shown with their type and contents)
31
+ * - Class instances (shown with the class name prefix)
32
+ *
33
+ * Intended for the `|dump` filter in templates. Output is a plain string;
34
+ * the filter wraps it in <pre> and marks it safe so the template engine
35
+ * doesn't double-escape.
36
+ */
37
+ function inspectValue(value: unknown, seen: WeakSet<object> = new WeakSet(), depth = 0): string {
38
+ // Primitives
39
+ if (value === null) return "null";
40
+ if (value === undefined) return "undefined";
41
+ if (typeof value === "string") return JSON.stringify(value);
42
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
43
+ if (typeof value === "bigint") return `${value.toString()}n`;
44
+ if (typeof value === "symbol") return value.toString();
45
+ if (typeof value === "function") {
46
+ const name = value.name || "(anonymous)";
47
+ return `[Function: ${name}]`;
48
+ }
49
+
50
+ // value is now object (including arrays, Date, Map, Set, etc.)
51
+ const obj = value as object;
52
+
53
+ // Cycle detection
54
+ if (seen.has(obj)) return "[Circular]";
55
+ seen.add(obj);
56
+
57
+ // Depth cap — prevents runaway recursion on enormous graphs
58
+ if (depth > 8) return "[...]";
59
+
60
+ try {
61
+ // Date
62
+ if (obj instanceof Date) {
63
+ return `Date(${obj.toISOString()})`;
64
+ }
65
+
66
+ // RegExp
67
+ if (obj instanceof RegExp) {
68
+ return obj.toString();
69
+ }
70
+
71
+ // Error
72
+ if (obj instanceof Error) {
73
+ return `${obj.constructor.name}(${JSON.stringify(obj.message)})`;
74
+ }
75
+
76
+ // Map
77
+ if (obj instanceof Map) {
78
+ if (obj.size === 0) return "Map(0) {}";
79
+ const entries: string[] = [];
80
+ for (const [k, v] of obj) {
81
+ entries.push(`${inspectValue(k, seen, depth + 1)} => ${inspectValue(v, seen, depth + 1)}`);
82
+ }
83
+ return `Map(${obj.size}) { ${entries.join(", ")} }`;
84
+ }
85
+
86
+ // Set
87
+ if (obj instanceof Set) {
88
+ if (obj.size === 0) return "Set(0) {}";
89
+ const items: string[] = [];
90
+ for (const v of obj) {
91
+ items.push(inspectValue(v, seen, depth + 1));
92
+ }
93
+ return `Set(${obj.size}) { ${items.join(", ")} }`;
94
+ }
95
+
96
+ // Array
97
+ if (Array.isArray(obj)) {
98
+ if (obj.length === 0) return "[]";
99
+ const items = obj.map((v) => inspectValue(v, seen, depth + 1));
100
+ return `[${items.join(", ")}]`;
101
+ }
102
+
103
+ // Plain object or class instance
104
+ const keys = Object.keys(obj);
105
+ const className = obj.constructor && obj.constructor.name !== "Object"
106
+ ? `${obj.constructor.name} `
107
+ : "";
108
+ if (keys.length === 0) return `${className}{}`;
109
+ const props = keys.map((k) => {
110
+ const v = (obj as Record<string, unknown>)[k];
111
+ return `${k}: ${inspectValue(v, seen, depth + 1)}`;
112
+ });
113
+ return `${className}{ ${props.join(", ")} }`;
114
+ } finally {
115
+ seen.delete(obj);
116
+ }
117
+ }
118
+
22
119
  type TokenType = "TEXT" | "VAR" | "BLOCK" | "COMMENT";
23
120
  type Token = [TokenType, string];
24
121
 
@@ -1590,7 +1687,18 @@ export class Frond {
1590
1687
  case "keys": value = (typeof value === "object" && value !== null && !Array.isArray(value)) ? Object.keys(value) : []; continue;
1591
1688
  case "values": value = (typeof value === "object" && value !== null && !Array.isArray(value)) ? Object.values(value) : []; continue;
1592
1689
  case "json_encode": value = JSON.stringify(value); continue;
1593
- case "dump": value = JSON.stringify(value); continue;
1690
+ case "dump": {
1691
+ // Use a safe inspector rather than JSON.stringify — handles
1692
+ // circular refs, BigInt, Map/Set, Error, Date, class instances.
1693
+ const dumped = inspectValue(value);
1694
+ const escaped = dumped
1695
+ .replace(/&/g, "&amp;")
1696
+ .replace(/</g, "&lt;")
1697
+ .replace(/>/g, "&gt;")
1698
+ .replace(/"/g, "&quot;");
1699
+ value = new SafeString(`<pre>${escaped}</pre>`);
1700
+ continue;
1701
+ }
1594
1702
  case "nl2br": value = String(value).replace(/\n/g, "<br>\n"); continue;
1595
1703
  case "unique": value = Array.isArray(value) ? [...new Set(value)] : value; continue;
1596
1704
  case "sort": value = Array.isArray(value) ? [...value].sort() : value; continue;