diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd384f40..449f1e1e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,27 +37,32 @@ jobs: - name: Node.js 0.8 node-version: "0.8" npm-i: mocha@2.5.3 supertest@1.1.0 - npm-rm: nyc + npm-rm: nyc @types/node typescript - name: Node.js 0.10 node-version: "0.10" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 + npm-rm: @types/node typescript - name: Node.js 0.12 node-version: "0.12" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 + npm-rm: @types/node typescript - name: io.js 1.x node-version: "1.8" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 + npm-rm: @types/node typescript - name: io.js 2.x node-version: "2.5" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 + npm-rm: @types/node typescript - name: io.js 3.x node-version: "3.3" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 + npm-rm: @types/node typescript - name: Node.js 4.x node-version: "4.9" @@ -182,6 +187,10 @@ jobs: npm test fi + - name: Test type definition + if: steps.list_env.outputs.typescript != '' + run: npm run test-types + - name: Lint code if: steps.list_env.outputs.eslint != '' run: npm run lint diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 00000000..e321ce48 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,96 @@ +import Methods from "methods"; +import { OutgoingMessage } from "http"; + +export default Router; + +type Method = typeof Methods[number]; + +type LowercaseMethods = Lowercase; + +export interface RouterOptions { + strict?: boolean; + caseSensitive?: boolean; + mergeParams?: boolean; +} + +export interface IncomingRequest { + url?: string; + method?: string; + originalUrl?: string; + params?: Record; +} + +export interface RoutedRequest extends IncomingRequest { + baseUrl: string; + next?: NextFunction; + route?: IRoute; +} + +export interface NextFunction { + (err?: any): void; +} + +type IRoute = Record> & { + path: string; + stack: any; + all: IRouterHandler; +} + +type RequestParamHandler = ( + req: IncomingRequest, + res: OutgoingMessage, + next: NextFunction, + value: any, + name: string +) => any; + +export interface RouteHandler { + (req: RoutedRequest, res: OutgoingMessage, next: NextFunction): any; +} + +export interface RequestHandler { + (req: IncomingRequest, res: OutgoingMessage, next: NextFunction): any; +} + +type ErrorRequestHandler = ( + err: any, + req: IncomingRequest, + res: any, + next: NextFunction +) => any; + +type PathParams = string | RegExp | Array; + +type RequestHandlerParams = + | RouteHandler + | ErrorRequestHandler + | Array; + +interface IRouterMatcher { + (path: PathParams, ...handlers: RouteHandler[]): T; + (path: PathParams, ...handlers: RequestHandlerParams[]): T; +} + +interface IRouterHandler { + (...handlers: RouteHandler[]): T; + (...handlers: RequestHandlerParams[]): T; +} + +type IRouter = Record> & { + param(name: string, handler: RequestParamHandler): IRouter; + param( + callback: (name: string, matcher: RegExp) => RequestParamHandler + ): IRouter; + all: IRouterMatcher; + use: IRouterHandler & IRouterMatcher; + handle: RequestHandler; + route(prefix: PathParams): IRoute; + stack: any[]; +} + +interface RouterConstructor { + new (options?: RouterOptions): IRouter & RequestHandler; + (options?: RouterOptions): IRouter & RequestHandler; +} + +declare var Router: RouterConstructor; diff --git a/package.json b/package.json index 5279b120..e4a91db7 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "license": "MIT", "repository": "pillarjs/router", "dependencies": { + "@types/methods": "^1.1.1", "array-flatten": "3.0.0", "debug": "2.6.9", "methods": "~1.1.2", @@ -18,6 +19,7 @@ "utils-merge": "1.0.1" }, "devDependencies": { + "@types/node": "^18.0.0", "after": "0.8.2", "eslint": "8.34.0", "eslint-plugin-markdown": "3.0.0", @@ -25,7 +27,8 @@ "mocha": "10.2.0", "nyc": "15.1.0", "safe-buffer": "5.2.1", - "supertest": "6.3.3" + "supertest": "6.3.3", + "typescript": "^5.0.0" }, "files": [ "lib/", @@ -33,8 +36,10 @@ "HISTORY.md", "README.md", "SECURITY.md", - "index.js" + "index.js", + "index.d.ts" ], + "types": "index.d.ts", "engines": { "node": ">= 0.8" }, @@ -43,6 +48,7 @@ "test": "mocha --reporter spec --bail --check-leaks test/", "test-ci": "nyc --reporter=lcov --reporter=text npm test", "test-cov": "nyc --reporter=text npm test", + "test-types": "tsc --project tsconfig.json --noEmit", "version": "node scripts/version-history.js && git add HISTORY.md" } } diff --git a/test/types.ts b/test/types.ts new file mode 100644 index 00000000..5eb4ab9e --- /dev/null +++ b/test/types.ts @@ -0,0 +1,81 @@ +import { createServer, OutgoingMessage } from 'http'; +import Router, { + RouterOptions, + RouteHandler, + NextFunction, + RoutedRequest +} from '..'; + +const options: RouterOptions = { + strict: false, + caseSensitive: false, + mergeParams: false +}; + +const r = new Router(); +const r2 = Router() + +const router = new Router(options); +const routerHandler: RouteHandler = (req, res, next) => { + res.setHeader('Content-Type', 'plain/text'); + res.write('Hello') + res.end('world') +}; + +router.get('/', routerHandler); +router.post('/', routerHandler); +router.delete('/', routerHandler); +router.patch('/', routerHandler); +router.options('/', routerHandler); +router.head('/', routerHandler); +router.bind('/', routerHandler); +router.connect('/', routerHandler); +router.trace('/', routerHandler); +router['m-search']('/', routerHandler); + + +// param +router.param('user_id', (req, res, next, id) => {}); + +// middleware +router.use((req, res, next) => { + type TReq = Expect> + type TRes = Expect> + type TNext = Expect> + next(); +}); + +const api = router.route('/api/'); + +router.route('/') +.all((req, res, next) => { + type TReq = Expect> + type TRes = Expect> + type TNext = Expect> + next(); +}) +.get((req, res, next) => { + type TReq = Expect> + type TRes = Expect> + type TNext = Expect> +}); + + +// test router handling + +var server = createServer(function(req, res) { + router(req, res, (err) => { + // + }) + router.handle(req, res, (err) => { + // + }) +}) + + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Compute = T extends (...args: any[]) => any ? T : { [K in keyof T]: Compute } + +export type Equal = (() => T extends Compute ? 1 : 2) extends () => T extends Compute ? 1 : 2 ? true : false + +export type Expect = T extends true ? true : never diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..9afe7be6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6" + ], + "esModuleInterop": true, + "noImplicitAny": false, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "types": ["node"], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "include": [ + "test/**/*" + ], + "files": [ + "index.d.ts" + ] +} \ No newline at end of file