tina4-nodejs 3.10.87 → 3.10.89
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 +1 -1
- package/packages/frond/src/engine.ts +130 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tina4-nodejs",
|
|
3
|
-
"version": "3.10.
|
|
3
|
+
"version": "3.10.89",
|
|
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,126 @@ 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
|
+
* Used by both the `|dump` filter and the `dump()` global function.
|
|
34
|
+
* Output is a plain string; callers wrap it in <pre> and mark it safe so
|
|
35
|
+
* the template engine 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
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Render a value as a pre-formatted, HTML-escaped dump wrapped in <pre> tags.
|
|
121
|
+
* Returns a SafeString so the template engine does not double-escape the
|
|
122
|
+
* entities. Shared by the `|dump` filter and the `dump()` global function.
|
|
123
|
+
*
|
|
124
|
+
* Gated on TINA4_DEBUG=true. In production (TINA4_DEBUG unset or false)
|
|
125
|
+
* dump output is suppressed entirely to avoid leaking internal state,
|
|
126
|
+
* stack-traceable object shapes, or sensitive values into rendered HTML.
|
|
127
|
+
*/
|
|
128
|
+
function renderDump(value: unknown): SafeString {
|
|
129
|
+
const debugMode = (process.env.TINA4_DEBUG ?? "").toLowerCase() === "true";
|
|
130
|
+
if (!debugMode) {
|
|
131
|
+
return new SafeString("");
|
|
132
|
+
}
|
|
133
|
+
const dumped = inspectValue(value);
|
|
134
|
+
const escaped = dumped
|
|
135
|
+
.replace(/&/g, "&")
|
|
136
|
+
.replace(/</g, "<")
|
|
137
|
+
.replace(/>/g, ">")
|
|
138
|
+
.replace(/"/g, """);
|
|
139
|
+
return new SafeString(`<pre>${escaped}</pre>`);
|
|
140
|
+
}
|
|
141
|
+
|
|
22
142
|
type TokenType = "TEXT" | "VAR" | "BLOCK" | "COMMENT";
|
|
23
143
|
type Token = [TokenType, string];
|
|
24
144
|
|
|
@@ -1167,6 +1287,11 @@ export class Frond {
|
|
|
1167
1287
|
this.globals.form_token = (descriptor?: string) => _generateFormToken(descriptor || "");
|
|
1168
1288
|
this.globals.formTokenValue = (descriptor?: string) => _generateFormTokenValue(descriptor || "");
|
|
1169
1289
|
this.globals.form_token_value = (descriptor?: string) => _generateFormTokenValue(descriptor || "");
|
|
1290
|
+
|
|
1291
|
+
// Debug helper: {{ dump(x) }} — gated on TINA4_DEBUG, see renderDump().
|
|
1292
|
+
// Available alongside the |dump filter so both styles work:
|
|
1293
|
+
// {{ user|dump }} and {{ dump(user) }}
|
|
1294
|
+
this.globals.dump = (value: unknown) => renderDump(value);
|
|
1170
1295
|
}
|
|
1171
1296
|
|
|
1172
1297
|
sandbox(filters?: string[], tags?: string[], vars?: string[]): Frond {
|
|
@@ -1590,7 +1715,11 @@ export class Frond {
|
|
|
1590
1715
|
case "keys": value = (typeof value === "object" && value !== null && !Array.isArray(value)) ? Object.keys(value) : []; continue;
|
|
1591
1716
|
case "values": value = (typeof value === "object" && value !== null && !Array.isArray(value)) ? Object.values(value) : []; continue;
|
|
1592
1717
|
case "json_encode": value = JSON.stringify(value); continue;
|
|
1593
|
-
case "dump":
|
|
1718
|
+
case "dump":
|
|
1719
|
+
// Delegates to renderDump(), which is gated on TINA4_DEBUG.
|
|
1720
|
+
// In production this emits an empty SafeString (no leaked state).
|
|
1721
|
+
value = renderDump(value);
|
|
1722
|
+
continue;
|
|
1594
1723
|
case "nl2br": value = String(value).replace(/\n/g, "<br>\n"); continue;
|
|
1595
1724
|
case "unique": value = Array.isArray(value) ? [...new Set(value)] : value; continue;
|
|
1596
1725
|
case "sort": value = Array.isArray(value) ? [...value].sort() : value; continue;
|