From 8c6f61b3fa63a721c0c7369945fc1105759a4d68 Mon Sep 17 00:00:00 2001 From: Insanerx <290829829+insanerx@users.noreply.github.com> Date: Fri, 14 Aug 2026 20:09:27 +0300 Subject: [PATCH 1/2] feat: implement type-safe route parameters using path string parsing and generic handler interfaces --- package.json | 2 +- src/core/router.ts | 75 +++++++++++++++++++++++++--------- src/core/types.ts | 39 +++++++++++++++--- src/utils/requestCtx.ts | 10 ++--- tests/unit/core/server.test.ts | 2 +- tests/unit/core/types.test.ts | 35 ++++++++++++++++ 6 files changed, 130 insertions(+), 33 deletions(-) create mode 100644 tests/unit/core/types.test.ts diff --git a/package.json b/package.json index 8b61c2b..42cd62b 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "dev": "nodemon", "test": "tsx tests/run.ts", "lint": "eslint src/**/*.ts", - "format": "prettier --write \"src/**/*.ts\"", + "format": "prettier --write \"{src,tests}/**/*.ts\"", "check": "pnpm run lint && pnpm run format", "pack:npm": "node scripts/pack.js", "release": "node scripts/release.js" diff --git a/src/core/router.ts b/src/core/router.ts index 0b28bb3..fa51de9 100644 --- a/src/core/router.ts +++ b/src/core/router.ts @@ -89,7 +89,7 @@ export class Router { * return ctx.json({ users: [] }); * }); */ - get(path: string, ...handlers: VoltenHandler[]): void; + get
(path: P, ...handlers: VoltenHandler
[]): void; /** * Registers a GET route with custom route options and handlers. * @@ -97,9 +97,16 @@ export class Router { * @param {RouteOptions} options - Route options config (e.g. body limit). * @param {...VoltenHandler[]} handlers - One or more handler functions. */ - get(path: string, options: RouteOptions, ...handlers: VoltenHandler[]): void; - get(path: string, arg2: RouteOptions | VoltenHandler, ...handlers: VoltenHandler[]): void { - const { options, routeHandlers } = this.identifyParamType(arg2, ...handlers); + get
(path: P, options: RouteOptions, ...handlers: VoltenHandler
[]): void; + get
( + path: P, + arg2: RouteOptions | VoltenHandler
, + ...handlers: VoltenHandler
[] + ): void { + const { options, routeHandlers } = this.identifyParamType( + arg2 as RouteOptions | VoltenHandler, + ...(handlers as unknown as VoltenHandler[]), + ); const handlersWithMiddleware = [...this.middleware, ...routeHandlers]; this.routes.push({ method: "GET", path, options, handlers: handlersWithMiddleware }); } @@ -116,7 +123,7 @@ export class Router { * return ctx.status(201).json({ created: true }); * }); */ - post(path: string, ...handlers: VoltenHandler[]): void; + post
(path: P, ...handlers: VoltenHandler
[]): void; /** * Registers a POST route with custom route options and handlers. * @@ -124,9 +131,16 @@ export class Router { * @param {RouteOptions} options - Route options config (e.g. body limit). * @param {...VoltenHandler[]} handlers - One or more handler functions. */ - post(path: string, options: RouteOptions, ...handlers: VoltenHandler[]): void; - post(path: string, arg2: RouteOptions | VoltenHandler, ...handlers: VoltenHandler[]): void { - const { options, routeHandlers } = this.identifyParamType(arg2, ...handlers); + post
(path: P, options: RouteOptions, ...handlers: VoltenHandler
[]): void; + post
( + path: P, + arg2: RouteOptions | VoltenHandler
, + ...handlers: VoltenHandler
[] + ): void { + const { options, routeHandlers } = this.identifyParamType( + arg2 as RouteOptions | VoltenHandler, + ...(handlers as unknown as VoltenHandler[]), + ); const handlersWithMiddleware = [...this.middleware, ...routeHandlers]; this.routes.push({ method: "POST", path, options, handlers: handlersWithMiddleware }); } @@ -142,7 +156,7 @@ export class Router { * return ctx.json({ updated: true }); * }); */ - patch(path: string, ...handlers: VoltenHandler[]): void; + patch
(path: P, ...handlers: VoltenHandler
[]): void; /** * Registers a PATCH route with custom route options and handlers. * @@ -150,9 +164,16 @@ export class Router { * @param {RouteOptions} options - Route options config (e.g. body limit). * @param {...VoltenHandler[]} handlers - One or more handler functions. */ - patch(path: string, options: RouteOptions, ...handlers: VoltenHandler[]): void; - patch(path: string, arg2: RouteOptions | VoltenHandler, ...handlers: VoltenHandler[]): void { - const { options, routeHandlers } = this.identifyParamType(arg2, ...handlers); + patch
(path: P, options: RouteOptions, ...handlers: VoltenHandler
[]): void; + patch
( + path: P, + arg2: RouteOptions | VoltenHandler
, + ...handlers: VoltenHandler
[] + ): void { + const { options, routeHandlers } = this.identifyParamType( + arg2 as RouteOptions | VoltenHandler, + ...(handlers as unknown as VoltenHandler[]), + ); const handlersWithMiddleware = [...this.middleware, ...routeHandlers]; this.routes.push({ method: "PATCH", path, options, handlers: handlersWithMiddleware }); } @@ -168,7 +189,7 @@ export class Router { * return ctx.json({ replaced: true }); * }); */ - put(path: string, ...handlers: VoltenHandler[]): void; + put
(path: P, ...handlers: VoltenHandler
[]): void; /** * Registers a PUT route with custom route options and handlers. * @@ -176,9 +197,16 @@ export class Router { * @param {RouteOptions} options - Route options config (e.g. body limit). * @param {...VoltenHandler[]} handlers - One or more handler functions. */ - put(path: string, options: RouteOptions, ...handlers: VoltenHandler[]): void; - put(path: string, arg2: RouteOptions | VoltenHandler, ...handlers: VoltenHandler[]): void { - const { options, routeHandlers } = this.identifyParamType(arg2, ...handlers); + put
(path: P, options: RouteOptions, ...handlers: VoltenHandler
[]): void; + put
( + path: P, + arg2: RouteOptions | VoltenHandler
, + ...handlers: VoltenHandler
[] + ): void { + const { options, routeHandlers } = this.identifyParamType( + arg2 as RouteOptions | VoltenHandler, + ...(handlers as unknown as VoltenHandler[]), + ); const handlersWithMiddleware = [...this.middleware, ...routeHandlers]; this.routes.push({ method: "PUT", path, options, handlers: handlersWithMiddleware }); } @@ -194,7 +222,7 @@ export class Router { * return ctx.json({ deleted: true }); * }); */ - delete(path: string, ...handlers: VoltenHandler[]): void; + delete
(path: P, ...handlers: VoltenHandler
[]): void; /** * Registers a DELETE route with custom route options and handlers. * @@ -202,9 +230,16 @@ export class Router { * @param {RouteOptions} options - Route options config. * @param {...VoltenHandler[]} handlers - One or more handler functions. */ - delete(path: string, options: RouteOptions, ...handlers: VoltenHandler[]): void; - delete(path: string, arg2: RouteOptions | VoltenHandler, ...handlers: VoltenHandler[]): void { - const { options, routeHandlers } = this.identifyParamType(arg2, ...handlers); + delete
(path: P, options: RouteOptions, ...handlers: VoltenHandler
[]): void; + delete
( + path: P, + arg2: RouteOptions | VoltenHandler
, + ...handlers: VoltenHandler
[]
+ ): void {
+ const { options, routeHandlers } = this.identifyParamType(
+ arg2 as RouteOptions | VoltenHandler,
+ ...(handlers as unknown as VoltenHandler[]),
+ );
const handlersWithMiddleware = [...this.middleware, ...routeHandlers];
this.routes.push({ method: "DELETE", path, options, handlers: handlersWithMiddleware });
}
diff --git a/src/core/types.ts b/src/core/types.ts
index 8e527e7..cf6f6c8 100644
--- a/src/core/types.ts
+++ b/src/core/types.ts
@@ -6,15 +6,42 @@ import { Readable } from "stream";
export type Next = () => Promise = (
+ ctx: RequestContext ,
+ next: Next,
+) => Promise = (
+ ctx: RequestContext ,
+) => Promise = (
+ ctx: RequestContext ,
+) => Promise = (
+ err: VoltenError,
+ ctx: RequestContext ,
+) => Promise = (
+ err: VoltenError,
+ ctx: RequestContext ,
+) => void;
export type NativeErrorHandler = (
err: VoltenError,
@@ -22,7 +49,7 @@ export type NativeErrorHandler = (
res: ServerResponse,
) => Promise {
public _app: App = Object.create(null) as ExtractParams ;
public inited: boolean = false;
private queryString!: string;
@@ -83,7 +83,7 @@ export class RequestContext {
this.queryString = queryStr;
this.queryValue = null;
- this.params = Object.create(null) as Params;
+ this.params = Object.create(null) as ExtractParams ;
const headers = req.headers;
this._headers = headers;
this.method = req.method ?? "GET";
@@ -152,7 +152,7 @@ export class RequestContext {
this._res = null;
this._route = null;
this._headers = null;
- this.params = Object.create(null) as Params;
+ this.params = Object.create(null) as ExtractParams ;
this.state = {};
this.queryValue = null;
this._bodyPromise = undefined;
diff --git a/tests/unit/core/server.test.ts b/tests/unit/core/server.test.ts
index 369326d..87940d6 100644
--- a/tests/unit/core/server.test.ts
+++ b/tests/unit/core/server.test.ts
@@ -22,7 +22,7 @@ function captureLogs(fn: () => void): any[] {
test("App logger: default logger works and logs warn level by default", () => {
const app = new App();
-
+
const logs = captureLogs(() => {
app.logger.info("should not log");
app.logger.warn("this is a warning");
diff --git a/tests/unit/core/types.test.ts b/tests/unit/core/types.test.ts
new file mode 100644
index 0000000..496dec3
--- /dev/null
+++ b/tests/unit/core/types.test.ts
@@ -0,0 +1,35 @@
+import { test } from "node:test";
+import assert from "node:assert/strict";
+import { App } from "../../../src/core/server.ts";
+import type { ExtractParams } from "../../../src/core/types.ts";
+
+type Equals