tina4-nodejs 3.1.2 → 3.2.1
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/README.md
CHANGED
|
@@ -42,7 +42,35 @@ cd my-app && tina4 serve
|
|
|
42
42
|
|
|
43
43
|
Open http://localhost:7148 — your app is running.
|
|
44
44
|
|
|
45
|
-
>
|
|
45
|
+
<details>
|
|
46
|
+
<summary><strong>Without the Tina4 CLI</strong></summary>
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# 1. Create project
|
|
50
|
+
mkdir my-app && cd my-app
|
|
51
|
+
npm init -y
|
|
52
|
+
npm install tina4-nodejs
|
|
53
|
+
|
|
54
|
+
# 2. Create entry point
|
|
55
|
+
cat > app.ts << 'EOF'
|
|
56
|
+
import { startServer } from "tina4-nodejs";
|
|
57
|
+
startServer({ port: 7148, host: "0.0.0.0" });
|
|
58
|
+
EOF
|
|
59
|
+
|
|
60
|
+
# 3. Create .env
|
|
61
|
+
echo 'TINA4_DEBUG=true' > .env
|
|
62
|
+
echo 'TINA4_LOG_LEVEL=ALL' >> .env
|
|
63
|
+
|
|
64
|
+
# 4. Create route directory
|
|
65
|
+
mkdir -p src/routes
|
|
66
|
+
|
|
67
|
+
# 5. Run
|
|
68
|
+
npx tsx app.ts
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Open http://localhost:7148
|
|
72
|
+
|
|
73
|
+
</details>
|
|
46
74
|
|
|
47
75
|
---
|
|
48
76
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tina4-nodejs",
|
|
3
|
-
"version": "3.1
|
|
3
|
+
"version": "3.2.1",
|
|
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"],
|
|
@@ -166,22 +166,24 @@ export function renderErrorOverlay(error: Error, request?: any): string {
|
|
|
166
166
|
// ── Request info ──
|
|
167
167
|
const requestPairs: Array<[string, string]> = [];
|
|
168
168
|
if (request != null) {
|
|
169
|
-
for (const attr of ["method", "url", "path"]) {
|
|
170
|
-
|
|
169
|
+
for (const attr of ["method", "url", "path", "ip"]) {
|
|
170
|
+
requestPairs.push([attr, request[attr] != null ? String(request[attr]) : "(none)"]);
|
|
171
171
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
requestPairs.push([
|
|
172
|
+
const dictFields: Array<[string, unknown]> = [
|
|
173
|
+
["headers", request.headers],
|
|
174
|
+
["params", request.params],
|
|
175
|
+
["query", request.query],
|
|
176
|
+
["body", request.body],
|
|
177
|
+
];
|
|
178
|
+
for (const [label, val] of dictFields) {
|
|
179
|
+
if (val != null && typeof val === "object" && Object.keys(val as object).length > 0) {
|
|
180
|
+
for (const [k, v] of Object.entries(val as Record<string, unknown>)) {
|
|
181
|
+
requestPairs.push([`${label}.${k}`, String(v)]);
|
|
182
|
+
}
|
|
183
|
+
} else if (val != null && typeof val === "string" && val !== "") {
|
|
184
|
+
requestPairs.push([label, val]);
|
|
185
|
+
} else {
|
|
186
|
+
requestPairs.push([label, val == null ? "(none)" : "(empty)"]);
|
|
185
187
|
}
|
|
186
188
|
}
|
|
187
189
|
}
|
|
@@ -44,7 +44,8 @@ export async function discoverRoutes(routesDir: string): Promise<RouteDefinition
|
|
|
44
44
|
|
|
45
45
|
function filePathToPattern(relativePath: string): string {
|
|
46
46
|
// Remove the filename (get.ts, post.ts, etc.) to get the directory path
|
|
47
|
-
|
|
47
|
+
// Normalise backslashes for Windows compatibility
|
|
48
|
+
const parts = relativePath.replace(/\\/g, "/").split("/").slice(0, -1);
|
|
48
49
|
|
|
49
50
|
// Convert directory segments to URL pattern
|
|
50
51
|
// File system uses [id] notation, but URL patterns use {id} to match Python
|
|
@@ -553,7 +553,21 @@ ${reset}
|
|
|
553
553
|
if (!proceed || res.raw.writableEnded) return;
|
|
554
554
|
}
|
|
555
555
|
|
|
556
|
-
|
|
556
|
+
// Support (), (response), (request), or (request, response) handler signatures
|
|
557
|
+
// When 1 param: if named request/req, pass request; otherwise pass response
|
|
558
|
+
let result: unknown;
|
|
559
|
+
if (match.handler.length === 0) {
|
|
560
|
+
result = await (match.handler as any)();
|
|
561
|
+
} else if (match.handler.length === 1) {
|
|
562
|
+
const fnStr = match.handler.toString();
|
|
563
|
+
const paramMatch = fnStr.match(/^(?:async\s+)?(?:function\s*)?\(?\s*(\w+)/);
|
|
564
|
+
const paramName = paramMatch?.[1]?.toLowerCase() ?? "";
|
|
565
|
+
result = (paramName === "request" || paramName === "req")
|
|
566
|
+
? await match.handler(req as any)
|
|
567
|
+
: await match.handler(res as any);
|
|
568
|
+
} else {
|
|
569
|
+
result = await match.handler(req, res);
|
|
570
|
+
}
|
|
557
571
|
|
|
558
572
|
// If the route exports a template and the handler returned a plain object,
|
|
559
573
|
// render it through the template engine instead of sending as JSON.
|