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 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.114)
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.114 - The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tina4-nodejs",
3
- "version": "3.13.114",
3
+ "version": "3.13.115",
4
4
  "type": "module",
5
5
  "description": "Tina4 for Node.js/TypeScript - native TypeScript conventions and shared Tina4 contracts",
6
6
  "keywords": [
@@ -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
- async function invokeRouteHandler(match, req2, res) {
36731
- const routeParams = req2.params || {};
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 await match.handler();
36737
- const args = filteredArgs.map((name) => {
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
- async function invokeRouteHandler(match, req2, res) {
36710
- const routeParams = req2.params || {};
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 await match.handler();
36716
- const args = filteredArgs.map((name) => {
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
- async function invokeRouteHandler(
1003
- match: { handler: unknown },
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
- ): Promise<unknown> {
1007
- const routeParams = req.params || {};
1008
- const fnStr = (match.handler as { toString(): string }).toString();
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
- if (filteredArgs.length === 0) return await (match.handler as any)();
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
- async function invokeRouteHandler(match, req2, res) {
25069
- const routeParams = req2.params || {};
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 await match.handler();
25075
- const args = filteredArgs.map((name) => {
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