tina4-nodejs 3.13.114 → 3.13.115
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 +2 -2
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +12 -6
- package/packages/core/dist/index.js +12 -6
- package/packages/core/src/server.ts +34 -9
- package/packages/orm/dist/index.js +12 -6
- package/types/core/src/server.d.ts +23 -1
package/CLAUDE.md
CHANGED
|
@@ -13,13 +13,13 @@ Even if the skill text is not currently loaded, these are non-negotiable:
|
|
|
13
13
|
|
|
14
14
|
The full discipline lives in `.claude/skills/tina4-maintainer/SKILL.md`; this block is the always-on floor.
|
|
15
15
|
|
|
16
|
-
# CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.
|
|
16
|
+
# CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.115)
|
|
17
17
|
|
|
18
18
|
> This file helps AI assistants (Claude, Copilot, Cursor, etc.) understand and work on this codebase effectively.
|
|
19
19
|
|
|
20
20
|
## What This Project Is
|
|
21
21
|
|
|
22
|
-
Tina4 for Node.js/TypeScript v3.13.
|
|
22
|
+
Tina4 for Node.js/TypeScript v3.13.115 - The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
|
|
23
23
|
|
|
24
24
|
The philosophy: zero ceremony, batteries included, file system as source of truth.
|
|
25
25
|
|
package/package.json
CHANGED
package/packages/cli/dist/bin.js
CHANGED
|
@@ -36727,18 +36727,24 @@ async function runGlobalMiddlewarePass(middleware, req2, res) {
|
|
|
36727
36727
|
if (!res.raw.writableEnded) res.raw.end();
|
|
36728
36728
|
return true;
|
|
36729
36729
|
}
|
|
36730
|
-
|
|
36731
|
-
const
|
|
36732
|
-
const fnStr = match.handler.toString();
|
|
36730
|
+
function resolveHandlerArgs(handler, req2, res, routeParams) {
|
|
36731
|
+
const fnStr = handler.toString();
|
|
36733
36732
|
const argMatch = fnStr.match(/^(?:async\s*)?(?:function\s*\w*)?\s*\(([^)]*)\)/);
|
|
36734
36733
|
const argNames = argMatch?.[1]?.split(",").map((a) => a.trim().replace(/[:=].*/, "")) ?? [];
|
|
36735
36734
|
const filteredArgs = argNames.filter((n) => n.length > 0);
|
|
36736
|
-
if (filteredArgs.length === 0) return
|
|
36737
|
-
|
|
36735
|
+
if (filteredArgs.length === 0) return [];
|
|
36736
|
+
let unmatchedPos = 0;
|
|
36737
|
+
return filteredArgs.map((name) => {
|
|
36738
36738
|
if (name in routeParams) return routeParams[name];
|
|
36739
36739
|
if (name === "request" || name === "req") return req2;
|
|
36740
|
-
return res;
|
|
36740
|
+
if (name === "response" || name === "res") return res;
|
|
36741
|
+
return unmatchedPos++ === 0 ? req2 : res;
|
|
36741
36742
|
});
|
|
36743
|
+
}
|
|
36744
|
+
async function invokeRouteHandler(match, req2, res) {
|
|
36745
|
+
const routeParams = req2.params || {};
|
|
36746
|
+
const args = resolveHandlerArgs(match.handler, req2, res, routeParams);
|
|
36747
|
+
if (args.length === 0) return await match.handler();
|
|
36742
36748
|
return await match.handler(...args);
|
|
36743
36749
|
}
|
|
36744
36750
|
async function renderIfTemplateRoute(match, res, result) {
|
|
@@ -36706,18 +36706,24 @@ async function runGlobalMiddlewarePass(middleware, req2, res) {
|
|
|
36706
36706
|
if (!res.raw.writableEnded) res.raw.end();
|
|
36707
36707
|
return true;
|
|
36708
36708
|
}
|
|
36709
|
-
|
|
36710
|
-
const
|
|
36711
|
-
const fnStr = match.handler.toString();
|
|
36709
|
+
function resolveHandlerArgs(handler, req2, res, routeParams) {
|
|
36710
|
+
const fnStr = handler.toString();
|
|
36712
36711
|
const argMatch = fnStr.match(/^(?:async\s*)?(?:function\s*\w*)?\s*\(([^)]*)\)/);
|
|
36713
36712
|
const argNames = argMatch?.[1]?.split(",").map((a) => a.trim().replace(/[:=].*/, "")) ?? [];
|
|
36714
36713
|
const filteredArgs = argNames.filter((n) => n.length > 0);
|
|
36715
|
-
if (filteredArgs.length === 0) return
|
|
36716
|
-
|
|
36714
|
+
if (filteredArgs.length === 0) return [];
|
|
36715
|
+
let unmatchedPos = 0;
|
|
36716
|
+
return filteredArgs.map((name) => {
|
|
36717
36717
|
if (name in routeParams) return routeParams[name];
|
|
36718
36718
|
if (name === "request" || name === "req") return req2;
|
|
36719
|
-
return res;
|
|
36719
|
+
if (name === "response" || name === "res") return res;
|
|
36720
|
+
return unmatchedPos++ === 0 ? req2 : res;
|
|
36720
36721
|
});
|
|
36722
|
+
}
|
|
36723
|
+
async function invokeRouteHandler(match, req2, res) {
|
|
36724
|
+
const routeParams = req2.params || {};
|
|
36725
|
+
const args = resolveHandlerArgs(match.handler, req2, res, routeParams);
|
|
36726
|
+
if (args.length === 0) return await match.handler();
|
|
36721
36727
|
return await match.handler(...args);
|
|
36722
36728
|
}
|
|
36723
36729
|
async function renderIfTemplateRoute(match, res, result) {
|
|
@@ -999,24 +999,49 @@ async function runGlobalMiddlewarePass(
|
|
|
999
999
|
* or nothing - and each parameter is resolved by its name: a path param wins,
|
|
1000
1000
|
* then `request`/`req`, then the response.
|
|
1001
1001
|
*/
|
|
1002
|
-
|
|
1003
|
-
|
|
1002
|
+
/**
|
|
1003
|
+
* Resolve a handler's argument list to `[reqOrParam, resOrParam, ...]` values.
|
|
1004
|
+
*
|
|
1005
|
+
* By-name path preserved (route-param names, `req`/`request`, `res`/`response`)
|
|
1006
|
+
* so existing code keeps its DX. Any remaining unmatched name falls back to
|
|
1007
|
+
* POSITIONAL binding: first unmatched -> request, rest -> response. That makes
|
|
1008
|
+
* dispatch bundler-safe by construction: Bun `--compile` and terser/esbuild
|
|
1009
|
+
* identifier-mangling rename `(req, res)` to `(req2, r$0)` — the by-name path
|
|
1010
|
+
* misses, the positional fallback catches, and the handler still receives the
|
|
1011
|
+
* request as its first argument. Fixes #56.
|
|
1012
|
+
*
|
|
1013
|
+
* Exported so the regression test can pin the behaviour directly, without a
|
|
1014
|
+
* live HTTP server.
|
|
1015
|
+
*/
|
|
1016
|
+
export function resolveHandlerArgs(
|
|
1017
|
+
handler: unknown,
|
|
1004
1018
|
req: Tina4Request,
|
|
1005
1019
|
res: Tina4Response,
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
const fnStr = (
|
|
1020
|
+
routeParams: Record<string, unknown>,
|
|
1021
|
+
): unknown[] {
|
|
1022
|
+
const fnStr = (handler as { toString(): string }).toString();
|
|
1009
1023
|
const argMatch = fnStr.match(/^(?:async\s*)?(?:function\s*\w*)?\s*\(([^)]*)\)/);
|
|
1010
1024
|
const argNames = argMatch?.[1]?.split(",").map((a: string) => a.trim().replace(/[:=].*/, "")) ?? [];
|
|
1011
1025
|
const filteredArgs = argNames.filter((n: string) => n.length > 0);
|
|
1026
|
+
if (filteredArgs.length === 0) return [];
|
|
1012
1027
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
const args = filteredArgs.map((name: string) => {
|
|
1028
|
+
let unmatchedPos = 0;
|
|
1029
|
+
return filteredArgs.map((name: string) => {
|
|
1016
1030
|
if (name in routeParams) return routeParams[name];
|
|
1017
1031
|
if (name === "request" || name === "req") return req;
|
|
1018
|
-
return res;
|
|
1032
|
+
if (name === "response" || name === "res") return res;
|
|
1033
|
+
return (unmatchedPos++ === 0) ? req : res;
|
|
1019
1034
|
});
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
async function invokeRouteHandler(
|
|
1038
|
+
match: { handler: unknown },
|
|
1039
|
+
req: Tina4Request,
|
|
1040
|
+
res: Tina4Response,
|
|
1041
|
+
): Promise<unknown> {
|
|
1042
|
+
const routeParams = req.params || {};
|
|
1043
|
+
const args = resolveHandlerArgs(match.handler, req, res, routeParams);
|
|
1044
|
+
if (args.length === 0) return await (match.handler as any)();
|
|
1020
1045
|
return await (match.handler as any)(...args);
|
|
1021
1046
|
}
|
|
1022
1047
|
|
|
@@ -25065,18 +25065,24 @@ async function runGlobalMiddlewarePass(middleware, req2, res) {
|
|
|
25065
25065
|
if (!res.raw.writableEnded) res.raw.end();
|
|
25066
25066
|
return true;
|
|
25067
25067
|
}
|
|
25068
|
-
|
|
25069
|
-
const
|
|
25070
|
-
const fnStr = match.handler.toString();
|
|
25068
|
+
function resolveHandlerArgs(handler, req2, res, routeParams) {
|
|
25069
|
+
const fnStr = handler.toString();
|
|
25071
25070
|
const argMatch = fnStr.match(/^(?:async\s*)?(?:function\s*\w*)?\s*\(([^)]*)\)/);
|
|
25072
25071
|
const argNames = argMatch?.[1]?.split(",").map((a) => a.trim().replace(/[:=].*/, "")) ?? [];
|
|
25073
25072
|
const filteredArgs = argNames.filter((n) => n.length > 0);
|
|
25074
|
-
if (filteredArgs.length === 0) return
|
|
25075
|
-
|
|
25073
|
+
if (filteredArgs.length === 0) return [];
|
|
25074
|
+
let unmatchedPos = 0;
|
|
25075
|
+
return filteredArgs.map((name) => {
|
|
25076
25076
|
if (name in routeParams) return routeParams[name];
|
|
25077
25077
|
if (name === "request" || name === "req") return req2;
|
|
25078
|
-
return res;
|
|
25078
|
+
if (name === "response" || name === "res") return res;
|
|
25079
|
+
return unmatchedPos++ === 0 ? req2 : res;
|
|
25079
25080
|
});
|
|
25081
|
+
}
|
|
25082
|
+
async function invokeRouteHandler(match, req2, res) {
|
|
25083
|
+
const routeParams = req2.params || {};
|
|
25084
|
+
const args = resolveHandlerArgs(match.handler, req2, res, routeParams);
|
|
25085
|
+
if (args.length === 0) return await match.handler();
|
|
25080
25086
|
return await match.handler(...args);
|
|
25081
25087
|
}
|
|
25082
25088
|
async function renderIfTemplateRoute(match, res, result) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
-
import type { Tina4Config } from "./types.js";
|
|
2
|
+
import type { Tina4Config, Tina4Request, Tina4Response } from "./types.js";
|
|
3
3
|
import { Router } from "./router.js";
|
|
4
4
|
import { MiddlewareChain } from "./middleware.js";
|
|
5
5
|
/** How long a graceful shutdown waits for in-flight requests, in seconds. */
|
|
@@ -167,6 +167,28 @@ export declare function stop(): void;
|
|
|
167
167
|
* Useful for testing and embedding.
|
|
168
168
|
*/
|
|
169
169
|
export declare function handle(rawReq: IncomingMessage, rawRes: ServerResponse): Promise<void>;
|
|
170
|
+
/**
|
|
171
|
+
* Invoke a matched route's handler, binding path params BY NAME.
|
|
172
|
+
*
|
|
173
|
+
* A handler declares whatever it needs - `(id, request, response)`, `(req, res)`,
|
|
174
|
+
* or nothing - and each parameter is resolved by its name: a path param wins,
|
|
175
|
+
* then `request`/`req`, then the response.
|
|
176
|
+
*/
|
|
177
|
+
/**
|
|
178
|
+
* Resolve a handler's argument list to `[reqOrParam, resOrParam, ...]` values.
|
|
179
|
+
*
|
|
180
|
+
* By-name path preserved (route-param names, `req`/`request`, `res`/`response`)
|
|
181
|
+
* so existing code keeps its DX. Any remaining unmatched name falls back to
|
|
182
|
+
* POSITIONAL binding: first unmatched -> request, rest -> response. That makes
|
|
183
|
+
* dispatch bundler-safe by construction: Bun `--compile` and terser/esbuild
|
|
184
|
+
* identifier-mangling rename `(req, res)` to `(req2, r$0)` — the by-name path
|
|
185
|
+
* misses, the positional fallback catches, and the handler still receives the
|
|
186
|
+
* request as its first argument. Fixes #56.
|
|
187
|
+
*
|
|
188
|
+
* Exported so the regression test can pin the behaviour directly, without a
|
|
189
|
+
* live HTTP server.
|
|
190
|
+
*/
|
|
191
|
+
export declare function resolveHandlerArgs(handler: unknown, req: Tina4Request, res: Tina4Response, routeParams: Record<string, unknown>): unknown[];
|
|
170
192
|
/**
|
|
171
193
|
* Everything one request's dispatch needs beyond req/res: the resolved
|
|
172
194
|
* router, middleware chain, filesystem roots and template engine a boot
|