-
-
Notifications
You must be signed in to change notification settings - Fork 104
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 files #100
base: master
Are you sure you want to change the base?
Changes from 11 commits
654e8c7
ba7e63b
0bead50
1d5bb74
8b503dc
4fc5c25
2cc21cd
e941dc2
8b08465
e7d495b
f384d5a
4aae496
1cbf48b
4a034cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,27 +34,32 @@ jobs: | |
- name: Node.js 0.8 | ||
node-version: "0.8" | ||
npm-i: [email protected] [email protected] | ||
npm-rm: nyc | ||
npm-rm: nyc typescript @types/node | ||
|
||
- name: Node.js 0.10 | ||
node-version: "0.10" | ||
npm-i: [email protected] [email protected] [email protected] | ||
npm-rm: typescript @types/node | ||
|
||
- name: Node.js 0.12 | ||
node-version: "0.12" | ||
npm-i: [email protected] [email protected] [email protected] | ||
npm-rm: typescript @types/node | ||
|
||
- name: io.js 1.x | ||
node-version: "1.8" | ||
npm-i: [email protected] [email protected] [email protected] | ||
npm-rm: typescript @types/node | ||
|
||
- name: io.js 2.x | ||
node-version: "2.5" | ||
npm-i: [email protected] [email protected] [email protected] | ||
npm-rm: typescript @types/node | ||
|
||
- name: io.js 3.x | ||
node-version: "3.3" | ||
npm-i: [email protected] [email protected] [email protected] | ||
npm-rm: typescript @types/node | ||
|
||
- name: Node.js 4.x | ||
node-version: "4.9" | ||
|
@@ -161,6 +166,10 @@ jobs: | |
if: steps.list_env.outputs.eslint != '' | ||
run: npm run lint | ||
|
||
- name: Test types | ||
if: steps.list_env.outputs.typescript != '' | ||
run: npm run test-types | ||
|
||
- name: Collect code coverage | ||
uses: coverallsapp/github-action@master | ||
if: steps.list_env.outputs.nyc != '' | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
import * as http from 'http'; | ||
|
||
export = Router; | ||
|
||
declare namespace Router { | ||
|
||
interface RouterOptions { | ||
strict?: boolean; | ||
caseSensitive?: boolean; | ||
mergeParams?: boolean; | ||
} | ||
|
||
interface Layer { | ||
name: string, | ||
handle: RequestHandler | ErrorRequestHandler, | ||
handle_request: RequestHandler, | ||
handle_error: ErrorRequestHandler, | ||
match: (path: string) => boolean, | ||
} | ||
|
||
interface IncomingRequest extends http.IncomingMessage { | ||
originalUrl?: string, | ||
params?: { | ||
[key: string]: any, | ||
}, | ||
} | ||
|
||
interface RoutedRequest extends IncomingRequest { | ||
baseUrl: string, | ||
next?: NextFunction, | ||
route?: IRoute | ||
} | ||
|
||
type RequestParamHandler = (req: IncomingRequest, res: http.ServerResponse, next: NextFunction, value: string, name: string) => void; | ||
|
||
type RouteHandler = (req: RoutedRequest, res: http.ServerResponse, next: NextFunction) => void; | ||
type RequestHandler = (req: IncomingRequest, res: http.ServerResponse, next: NextFunction) => void; | ||
|
||
type NextFunction = (err?: Error | "route" | "router") => void; | ||
type Callback = (err?: Error) => void; | ||
|
||
type ErrorRequestHandler = (err: Error, req: IncomingRequest, res: http.ServerResponse, next: NextFunction) => void; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We may not want the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the more appropriate type for that then? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is probably fine. It does have to be truthy, though, so not sure if that helps at all. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed it to |
||
|
||
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; | ||
} | ||
|
||
interface IRouter { | ||
/** | ||
* Map the given param placeholder `name`(s) to the given callback(s). | ||
* | ||
* Parameter mapping is used to provide pre-conditions to routes | ||
* which use normalized placeholders. For example a _:user_id_ parameter | ||
* could automatically load a user's information from the database without | ||
* any additional code, | ||
* | ||
* The callback uses the same signature as middleware, the only differencing | ||
* being that the value of the placeholder is passed, in this case the _id_ | ||
* of the user. Once the `next()` function is invoked, just like middleware | ||
* it will continue on to execute the route, or subsequent parameter functions. | ||
* | ||
* app.param('user_id', function(req, res, next, id){ | ||
* User.find(id, function(err, user){ | ||
* if (err) { | ||
* next(err); | ||
* } else if (user) { | ||
* req.user = user; | ||
* next(); | ||
* } else { | ||
* next(new Error('failed to load user')); | ||
* } | ||
* }); | ||
* }); | ||
*/ | ||
param(name: string, handler: RequestParamHandler): this; | ||
|
||
/** | ||
* Special-cased "all" method, applying the given route `path`, | ||
* middleware, and callback to _every_ HTTP method. | ||
*/ | ||
all: IRouterMatcher<this>; | ||
|
||
use: IRouterHandler<this> & IRouterMatcher<this>; | ||
|
||
handle: (req: http.IncomingMessage, res: http.ServerResponse, cb: Callback) => void; | ||
|
||
route(prefix: PathParams): IRoute; | ||
// Stack of configured routes | ||
stack: Layer[]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a general question, are the typings supposed to include all properties and methods including private ones, so should this be removed since it is not a public property? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right. API documented private in the code should be defined private in TS as well. I didn't pay much attention to that since
I'll go over it and add access modifiers where appropriate next week. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dougwilson, I looked into it a bit, but don't know a good way to implement it. I'd leave it as it is for now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume the way to implement is to not have it in here at all. My understanding (which is weak, please correct me if wrong) is that the TSD is just the public API, which is why there is no mechanism to mark something as private. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Commented out |
||
|
||
// Common HTTP methods | ||
delete: IRouterMatcher<this>; | ||
get: IRouterMatcher<this>; | ||
head: IRouterMatcher<this>; | ||
options: IRouterMatcher<this>; | ||
patch: IRouterMatcher<this>; | ||
post: IRouterMatcher<this>; | ||
put: IRouterMatcher<this>; | ||
|
||
// Exotic HTTP methods | ||
acl: IRouterMatcher<this>; | ||
bind: IRouterMatcher<this>; | ||
checkout: IRouterMatcher<this>; | ||
connect: IRouterMatcher<this>; | ||
copy: IRouterMatcher<this>; | ||
link: IRouterMatcher<this>; | ||
lock: IRouterMatcher<this>; | ||
"m-search": IRouterMatcher<this>; | ||
merge: IRouterMatcher<this>; | ||
mkactivity: IRouterMatcher<this>; | ||
mkcalendar: IRouterMatcher<this>; | ||
mkcol: IRouterMatcher<this>; | ||
move: IRouterMatcher<this>; | ||
notify: IRouterMatcher<this>; | ||
pri: IRouterMatcher<this>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this actually a method on there?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I took that from Now... this is not ideal obviously and I'm sure there's a way to handle supporting different versions better, but my current approach has been "close is better than nothing". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, gotcha. I was using Node.js 16.1. So that does bring up a good point: I guess this should be all the methods possible in the various Node.js versions? I wonder what other methods got removed; probably should go through all the versions this module supports and union them together. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I asked around in TS discord and they didn't recall a better way to handle it than what you proposed - adding collecting all of them from all versions. |
||
propfind: IRouterMatcher<this>; | ||
proppatch: IRouterMatcher<this>; | ||
purge: IRouterMatcher<this>; | ||
rebind: IRouterMatcher<this>; | ||
report: IRouterMatcher<this>; | ||
search: IRouterMatcher<this>; | ||
source: IRouterMatcher<this>; | ||
subscribe: IRouterMatcher<this>; | ||
trace: IRouterMatcher<this>; | ||
unbind: IRouterMatcher<this>; | ||
unlink: IRouterMatcher<this>; | ||
unlock: IRouterMatcher<this>; | ||
unsubscribe: IRouterMatcher<this>; | ||
} | ||
|
||
interface IRoute { | ||
path: string; | ||
stack: Layer[]; | ||
|
||
all: IRouterHandler<this>; | ||
|
||
// Common HTTP methods | ||
delete: IRouterHandler<this>; | ||
get: IRouterHandler<this>; | ||
head: IRouterHandler<this>; | ||
options: IRouterHandler<this>; | ||
patch: IRouterHandler<this>; | ||
post: IRouterHandler<this>; | ||
put: IRouterHandler<this>; | ||
|
||
// Exotic HTTP methods | ||
acl: IRouterHandler<this>; | ||
bind: IRouterHandler<this>; | ||
checkout: IRouterHandler<this>; | ||
connect: IRouterHandler<this>; | ||
copy: IRouterHandler<this>; | ||
link: IRouterHandler<this>; | ||
lock: IRouterHandler<this>; | ||
"m-search": IRouterHandler<this>; | ||
merge: IRouterHandler<this>; | ||
mkactivity: IRouterHandler<this>; | ||
mkcalendar: IRouterHandler<this>; | ||
mkcol: IRouterHandler<this>; | ||
move: IRouterHandler<this>; | ||
notify: IRouterHandler<this>; | ||
pri: IRouterHandler<this>; | ||
propfind: IRouterHandler<this>; | ||
proppatch: IRouterHandler<this>; | ||
purge: IRouterHandler<this>; | ||
rebind: IRouterHandler<this>; | ||
report: IRouterHandler<this>; | ||
search: IRouterHandler<this>; | ||
source: IRouterHandler<this>; | ||
subscribe: IRouterHandler<this>; | ||
trace: IRouterHandler<this>; | ||
unbind: IRouterHandler<this>; | ||
unlink: IRouterHandler<this>; | ||
unlock: IRouterHandler<this>; | ||
unsubscribe: IRouterHandler<this>; | ||
} | ||
|
||
interface RouterConstructor extends IRouter { | ||
new(options?: RouterOptions): IRouter & ((req: http.IncomingMessage, res: http.ServerResponse, cb: Callback) => void); | ||
} | ||
|
||
} | ||
|
||
declare const Router: Router.RouterConstructor; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { createServer, IncomingMessage, ServerResponse } from 'http'; | ||
import { | ||
default as Router, | ||
RouterOptions, | ||
IncomingRequest, | ||
RouteHandler, | ||
IRoute, | ||
RoutedRequest | ||
} from '..'; | ||
|
||
const options: RouterOptions = { | ||
strict: false, | ||
caseSensitive: false, | ||
mergeParams: false | ||
}; | ||
|
||
const r = new Router(); | ||
const router = new Router(options); | ||
const routerHandler: RouteHandler = (req, res) => { | ||
res.end('FIN'); | ||
}; | ||
|
||
router.get('/', routerHandler); | ||
router.post('/', routerHandler); | ||
router.delete('/', routerHandler); | ||
router.patch('/', routerHandler); | ||
router.options('/', routerHandler); | ||
router.head('/', routerHandler); | ||
router.unsubscribe('/', routerHandler); | ||
|
||
// param | ||
router.param('user_id', (req, res, next, id) => { | ||
const val: string = id; | ||
next(); | ||
}); | ||
|
||
// middleware | ||
router.use((req, res, next) => { | ||
next(); | ||
}); | ||
|
||
const api: IRoute = router.route('/api/'); | ||
|
||
router.route('/') | ||
.all((req, res, next) => { | ||
const url: string = req.baseUrl; | ||
next(); | ||
}) | ||
.get((req, res) => { | ||
res.setHeader('x-header', 'value'); | ||
res.end('.'); | ||
}); | ||
|
||
|
||
// test router handling | ||
var server = createServer(function(req: IncomingMessage, res: ServerResponse) { | ||
router(req, res, (err) => { | ||
if (err) { | ||
const e: Error = err; | ||
} | ||
// | ||
}) | ||
router.handle(req, res, (err) => { | ||
// | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to figure out where it went wrong in the TS, and I think this line is the culprit: inside of the
router.param
callback, there isreq.params
,req.route
, and all the other things available. I think that this should be the "routed request" type here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed that to
RoutedRequest
.