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
- > **Alternative** (without Rust CLI): `npm install tina4-nodejs` then create `app.ts`
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.2",
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
- if (request[attr] != null) requestPairs.push([attr, String(request[attr])]);
169
+ for (const attr of ["method", "url", "path", "ip"]) {
170
+ requestPairs.push([attr, request[attr] != null ? String(request[attr]) : "(none)"]);
171
171
  }
172
- if (request.headers && typeof request.headers === "object") {
173
- for (const [hk, hv] of Object.entries(request.headers)) {
174
- requestPairs.push([`headers.${hk}`, String(hv)]);
175
- }
176
- }
177
- if (request.params && typeof request.params === "object") {
178
- for (const [pk, pv] of Object.entries(request.params)) {
179
- requestPairs.push([`params.${pk}`, String(pv)]);
180
- }
181
- }
182
- if (request.query && typeof request.query === "object") {
183
- for (const [qk, qv] of Object.entries(request.query)) {
184
- requestPairs.push([`query.${qk}`, String(qv)]);
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
- const parts = relativePath.split("/").slice(0, -1);
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
- const result = await match.handler(req, res);
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.