Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ jobs:
- name: Lint code
run: npm run lint

- name: Check types
run: npm run test-types

- name: Upload code coverage
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ coverage/
node_modules/
npm-debug.log
package-lock.json
*.lock
53 changes: 53 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { IncomingMessage, ServerResponse } from 'node:http'
import type {
Handler,
NextFunction,
RequestHandler,
Route as IRoute
} from './lib/route'
import Route = require('./lib/route')

export type RouterMethod<T> = <F extends Handler = RequestHandler>(path: Path, handler: F) => T
Comment thread
gameroman marked this conversation as resolved.
Outdated

export type Path = string | RegExp | Array<string | RegExp>

interface RouterOptions {
Comment thread
gameroman marked this conversation as resolved.
Outdated
caseSensitive?: boolean
mergeParams?: boolean
strict?: boolean
}

interface Router extends RequestHandler {
Comment thread
gameroman marked this conversation as resolved.
Outdated
params: Record<string, any>
stack: any[]
caseSensitive: boolean
mergeParams: boolean
strict: boolean

handle(req: IncomingMessage, res: ServerResponse, callback: NextFunction): void
param(name: string, fn: (req: any, res: any, next: NextFunction, value: any, name: string) => void): this
route(path: Path): IRoute

use: {
<F extends Handler = RequestHandler>(path: Path, handler: F): Router
<F extends Handler = RequestHandler>(handler: F): Router
Comment thread
gameroman marked this conversation as resolved.
Outdated
}

all: RouterMethod<this>
get: RouterMethod<this>
post: RouterMethod<this>
put: RouterMethod<this>
delete: RouterMethod<this>
patch: RouterMethod<this>
options: RouterMethod<this>
head: RouterMethod<this>
}

interface RouterConstructor {
(options?: RouterOptions): Router
new (options?: RouterOptions): Router
Route: typeof Route
}

declare const router: RouterConstructor
export = router

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're mixing export = with other exports, which will cause issues for anyone using skipLibCheck: false. @tsconfig/node18 enables skipLibCheck by default, which is why the CI doesn't catch this error. If you need help figuring out how to fix it, let me know.

this applies to both files.

37 changes: 37 additions & 0 deletions lib/route.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { IncomingMessage, ServerResponse } from 'node:http'

export type NextFunction = (err?: any) => void

export type RequestHandler = (req: IncomingMessage, res: ServerResponse, next: NextFunction) => void
export type ErrorRequestHandler = (err: Error, req: IncomingMessage, res: ServerResponse, next: NextFunction) => void

export type Handler = RequestHandler | ErrorRequestHandler

export type RouteMethod<T> = <F extends Handler = RequestHandler>(handler: F) => T
Comment thread
gameroman marked this conversation as resolved.
Outdated

export interface Route {
path: string

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like us to use the Path type from index.d.ts here as well, but unfortunately we'd end up breaking more than we'd gain, since it's been this way in the Express types for years.

stack: any[]
methods: Record<string, boolean>

_handlesMethod(method: string): boolean
_methods(): string[]
dispatch(req: IncomingMessage, res: ServerResponse, done: NextFunction): void

all: RouteMethod<this>
get: RouteMethod<this>
post: RouteMethod<this>
put: RouteMethod<this>
delete: RouteMethod<this>
patch: RouteMethod<this>
options: RouteMethod<this>
head: RouteMethod<this>
}
Comment thread
gameroman marked this conversation as resolved.
Outdated

interface RouteConstructor {
(path: string): Route
new (path: string): Route
Comment thread
gameroman marked this conversation as resolved.
Outdated
}

declare const Route: RouteConstructor
export = Route
13 changes: 11 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
],
"license": "MIT",
"repository": "pillarjs/router",
"type": "commonjs",
"main": "index.js",
"types": "index.d.ts",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
Expand All @@ -20,7 +23,11 @@
"path-to-regexp": "^8.0.0"
},
"devDependencies": {
"@arethetypeswrong/cli": "0.18.5",
"@tsconfig/node18": "18.2.6",
"@types/node": "18.19.130",
"finalhandler": "^2.1.0",
"expect-type": "1.4.0",
"mocha": "10.2.0",
"nyc": "15.1.0",
"run-series": "^1.1.9",
Expand All @@ -32,7 +39,8 @@
"LICENSE",
"HISTORY.md",
"README.md",
"index.js"
"index.js",
"index.d.ts"
],
"engines": {
"node": ">= 18"
Expand All @@ -43,6 +51,7 @@
"test:debug": "mocha --reporter spec --check-leaks test/ --inspect --inspect-brk",
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
"test-cov": "nyc --reporter=text npm test",
"version": "node scripts/version-history.js && git add HISTORY.md"
"version": "node scripts/version-history.js && git add HISTORY.md",
"test-types": "tsc --noEmit && attw --pack"
}
}
25 changes: 25 additions & 0 deletions test/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { IncomingMessage, ServerResponse } from "node:http";
import { expectTypeOf } from "expect-type";
import Router from "..";
import { ErrorRequestHandler } from "../lib/route";

const router = Router();

router.get("/", function (req, res) {
expectTypeOf(req).toEqualTypeOf<IncomingMessage>();
expectTypeOf(res).toEqualTypeOf<ServerResponse>();
});

router.get("/", function (req, res, next) {
expectTypeOf(req).toEqualTypeOf<IncomingMessage>();
expectTypeOf(res).toEqualTypeOf<ServerResponse>();
expectTypeOf(next).toBeFunction();
});

// typescript has no way to infer the types automatically
router.get<ErrorRequestHandler>("/", function (err, req, res, next) {
Comment on lines +22 to +23

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, typescript can not automatically infer that the callback type is ErrorRequestHandler so if you don't provide the type param, it'll just infer it's params as any

The only way to solve this is to change everything completely, changing callback's param to an object like { err, req, res, next }, instead of 4 different params, this is what hono, elysiajs and other do

This would obviously require a new major so I haven't touched anything there

So just letting know that the current logic cannot be fully expressed in typescript without manually providing the type parameter on the user's side

expectTypeOf(err).toEqualTypeOf<Error>();
expectTypeOf(req).toEqualTypeOf<IncomingMessage>();
expectTypeOf(res).toEqualTypeOf<ServerResponse>();
expectTypeOf(next).toBeFunction();
});
Comment thread
gameroman marked this conversation as resolved.
7 changes: 7 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@tsconfig/node18/tsconfig.json",
"include": ["index.d.ts", "lib/route.d.ts", "test/types.ts"],
"compilerOptions": {
"types": ["node"]
}
}
Loading