Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add typescript definition #110

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 33 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,85 +37,101 @@ 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 typescript @types/node

- 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: typescript @types/node

- 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: typescript @types/node

- 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: typescript @types/node

- 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: typescript @types/node

- 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: typescript @types/node

- name: Node.js 4.x
node-version: "4.9"
npm-i: mocha@5.2.0 nyc@11.9.0 supertest@3.4.2
npm-rm: typescript @types/node

- name: Node.js 5.x
node-version: "5.12"
npm-i: mocha@5.2.0 nyc@11.9.0 supertest@3.4.2
npm-rm: typescript @types/node

- name: Node.js 6.x
node-version: "6.17"
npm-i: mocha@6.2.2 nyc@14.1.1 supertest@3.4.2
npm-rm: typescript @types/node

- name: Node.js 7.x
node-version: "7.10"
npm-i: mocha@6.2.2 nyc@14.1.1 supertest@6.1.6
npm-rm: typescript @types/node

- name: Node.js 8.x
node-version: "8.17"
npm-i: mocha@7.2.0
npm-i: mocha@7.2.0 @types/node@^8.0.0

- name: Node.js 9.x
node-version: "9.11"
npm-i: mocha@7.2.0
npm-i: mocha@7.2.0 @types/node@^9.0.0

- name: Node.js 10.x
node-version: "10.24"
npm-i: mocha@8.4.0
npm-i: mocha@8.4.0 @types/node@^10.0.0

- name: Node.js 11.x
node-version: "11.15"
npm-i: mocha@8.4.0
npm-i: mocha@8.4.0 @types/node@^11.0.0

- name: Node.js 12.x
node-version: "12.22"
npm-i: mocha@9.2.2
npm-i: mocha@9.2.2 @types/node@^12.0.0

- name: Node.js 13.x
node-version: "13.14"
npm-i: mocha@9.2.2
npm-i: mocha@9.2.2 @types/node@^13.0.0

- name: Node.js 14.x
node-version: "14.21"
node-version: "14.21"
npm-i: \@types/node@^14.0.0

- name: Node.js 15.x
node-version: "15.14"
npm-i: \@types/node@^15.0.0

- name: Node.js 16.x
node-version: "16.19"
npm-i: \@types/node@^16.0.0

- name: Node.js 17.x
node-version: "17.9"
npm-i: \@types/node@^17.0.0

- name: Node.js 18.x
node-version: "18.14"
npm-i: \@types/node@^18.0.0

- name: Node.js 19.x
node-version: "19.7"
npm-i: \@types/node@^18.0.0 # no 19 as yet


steps:
- uses: actions/checkout@v3
Expand Down Expand Up @@ -182,6 +198,14 @@ jobs:
npm test
fi

- name: Test type definition
shell: bash
run: |
# testing types requires >= node@8
if [[ "$(cut -d. -f1 <<< "${{ matrix.node-version }}")" -ge 8 ]]; then
npm run test-types
fi

- name: Lint code
if: steps.list_env.outputs.eslint != ''
run: npm run lint
Expand All @@ -194,6 +218,7 @@ jobs:
mkdir ./coverage
mv "./${{ matrix.name }}" "./coverage/${{ matrix.name }}"
fi

- name: Upload code coverage
uses: actions/upload-artifact@v3

Expand Down
122 changes: 122 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@

import { OutgoingMessage } from "http";

export default Router;

type HttpMethods =
'get' |
'post' |
'put' |
'head' |
'delete' |
'options' |
'trace' |
'copy' |
'lock' |
'mkcol' |
'move' |
'purge' |
'propfind' |
'proppatch' |
'unlock' |
'report' |
'mkactivity' |
'checkout' |
'merge' |
'm-search' |
'notify' |
'subscribe' |
'unsubscribe' |
'patch' |
'search' |
'connect'
Comment on lines +6 to +32
Copy link
Author

Choose a reason for hiding this comment

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

Take from methods lib


export interface RouterOptions {
strict?: boolean;
caseSensitive?: boolean;
mergeParams?: boolean;
}

export interface IncomingRequest {
url?: string;
method?: string;
originalUrl?: string;
params?: Record<string, string>;
Copy link
Author

Choose a reason for hiding this comment

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

Assumption: params are always strings.

}

interface BaseRoutedRequest extends IncomingRequest {
baseUrl: string;
next?: NextFunction;
route?: IRoute;
}

export type RoutedRequest = BaseRoutedRequest & {
[key: string]: any;
}

export interface NextFunction {
(err?: any): void;
}

type IRoute = Record<HttpMethods, IRouterHandler<IRoute>> & {
path: string;
all: IRouterHandler<IRoute>;
}

type RequestParamHandler = (
req: IncomingRequest,
res: OutgoingMessage,
next: NextFunction,
value: string,
name: string
) => void;

export interface RouteHandler {
(req: RoutedRequest, res: OutgoingMessage, next: NextFunction): void;
}

export interface RequestHandler {
(req: IncomingRequest, res: OutgoingMessage, next: NextFunction): void;
}

type ErrorRequestHandler = (
err: any,
req: IncomingRequest,
res: OutgoingMessage,
next: NextFunction
) => void;

type PathParams = string | RegExp | Array<string | RegExp>;

type RequestHandlerParams =
| RouteHandler
| ErrorRequestHandler
| Array<RouteHandler | ErrorRequestHandler>;

interface IRouterMatcher<T> {
(path: PathParams, ...handlers: RouteHandler[]): T;
(path: PathParams, ...handlers: RequestHandlerParams[]): T;
}

interface IRouterHandler<T> {
(...handlers: RouteHandler[]): T;
(...handlers: RequestHandlerParams[]): T;
}

type IRouter = Record<HttpMethods, IRouterMatcher<IRouter>> & {
param(name: string, handler: RequestParamHandler): IRouter;
param(
callback: (name: string, matcher: RegExp) => RequestParamHandler
): IRouter;
all: IRouterMatcher<IRouter>;
use: IRouterHandler<IRouter> & IRouterMatcher<IRouter>;
handle: RequestHandler;
route(prefix: PathParams): IRoute;
}

interface RouterConstructor {
new (options?: RouterOptions): IRouter & RequestHandler;
(options?: RouterOptions): IRouter & RequestHandler;
}

declare var Router: RouterConstructor;
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,29 @@
"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",
"finalhandler": "1.2.0",
"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/",
"LICENSE",
"HISTORY.md",
"README.md",
"SECURITY.md",
"index.js"
"index.js",
"index.d.ts"
],
"typesVersions": {
">=4.0": {"*": ["index.d.ts"]}
Copy link
Author

Choose a reason for hiding this comment

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

Limited to downstream typescript@>4.0.0

},
"engines": {
"node": ">= 0.8"
},
Expand All @@ -43,6 +49,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"
}
}
89 changes: 89 additions & 0 deletions test/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { createServer, OutgoingMessage } from 'http';
import Router, {
RouterOptions,
RouteHandler,
NextFunction,
RoutedRequest,
IncomingRequest
} from '..';

const options: RouterOptions = {
strict: false,
caseSensitive: false,
mergeParams: false
};

// new constructor
new Router().all('/', (req, res, next) => {})
// direct call
Router().all('/', (req, res, next) => {})

const router = new Router(options);
const routerHandler: RouteHandler = (req, res, next) => {
res.setHeader('Content-Type', 'plain/text');
res.write('Hello')
res.end('world')
};

// test verb methods
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) => {
type TReq = Expect<Equal<typeof req, IncomingRequest>>
type TRes = Expect<Equal<typeof res, OutgoingMessage>>
type TNext = Expect<Equal<typeof next, NextFunction>>
type P1 = Expect<Equal<typeof id, string>>
});

// middleware
router.use((req, res, next) => {
type TReq = Expect<Equal<typeof req, RoutedRequest>>
type TRes = Expect<Equal<typeof res, OutgoingMessage>>
type TNext = Expect<Equal<typeof next, NextFunction>>
next();
});

// RoutedRequest is extended with properties without type errors
router.use((req, res, next) => {
req.extendable = 'extendable'
next();
});

router.route('/')
.all((req, res, next) => {
type TReq = Expect<Equal<typeof req, RoutedRequest>>
type TRes = Expect<Equal<typeof res, OutgoingMessage>>
type TNext = Expect<Equal<typeof next, NextFunction>>
next();
})
.get((req, res, next) => {
type TReq = Expect<Equal<typeof req, RoutedRequest>>
type TRes = Expect<Equal<typeof res, OutgoingMessage>>
type TNext = Expect<Equal<typeof next, NextFunction>>
});


// valid for router from createServer
createServer(function(req, res) {
router(req, res, (err) => {})
router.handle(req, res, (err) => {})
})


// Type test helper methods
type Compute<T> = T extends (...args: any[]) => any ? T : { [K in keyof T]: Compute<T[K]> }

type Equal<X, Y> = (<T>() => T extends Compute<X> ? 1 : 2) extends <T>() => T extends Compute<Y> ? 1 : 2 ? true : false

type Expect<T extends true> = T extends true ? true : never