diff --git a/index.d.ts b/index.d.ts index d87a09e..cef7b6d 100644 --- a/index.d.ts +++ b/index.d.ts @@ -44,21 +44,24 @@ export declare interface App { [namespace: string]: Package; } -export declare type Middleware = ( - req: Request, - res: Response, +export declare type Middleware = ( + req: TReq, + res: TRes, next: NextFunction ) => void; -export declare type ErrorHandlingMiddleware = ( + +export declare type ErrorHandlingMiddleware = ( error: Error, - req: Request, - res: Response, + req: TReq, + res: TRes, next: NextFunction ) => void; + export declare type ErrorCallback = (error?: Error) => void; -export declare type HandlerFunction = ( - req: Request, - res: Response, + +export declare type HandlerFunction = ( + req: TReq, + res: TRes, next?: NextFunction ) => void | any | Promise; @@ -77,7 +80,10 @@ export declare type LoggerFunctionAdditionalInfo = export declare type NextFunction = () => void; export declare type TimestampFunction = () => string; export declare type SerializerFunction = (body: object) => string; -export declare type FinallyFunction = (req: Request, res: Response) => void; +export declare type FinallyFunction = ( + req: TReq, + res: TRes +) => void; export declare type METHODS = | 'GET' | 'POST' @@ -136,18 +142,18 @@ export declare interface Options { s3Config?: S3ClientConfig; } -export declare class Request { +export declare class Request< + TParams = { [key: string]: string | undefined }, + TQuery = { [key: string]: string | undefined }, + TBody = any +> { app: API; version: string; id: string; - params: { - [key: string]: string | undefined; - }; + params: TParams; method: string; path: string; - query: { - [key: string]: string | undefined; - }; + query: TQuery; multiValueQuery: { [key: string]: string[] | undefined; }; @@ -160,7 +166,7 @@ export declare class Request { rawHeaders?: { [key: string]: string | undefined; }; - body: any; + body: TBody; rawBody: string; route: ''; requestContext: APIGatewayEventRequestContext; @@ -196,7 +202,7 @@ export declare class Request { [key: string]: any; } -export declare class Response { +export declare class Response { status(code: number): this; sendStatus(code: number): void; @@ -219,11 +225,11 @@ export declare class Response { callback?: ErrorCallback ): Promise; - send(body: any): void; + send(body: TBody): void; - json(body: any): void; + json(body: TBody): void; - jsonp(body: any): void; + jsonp(body: TBody): void; html(body: any): void; diff --git a/index.test-d.ts b/index.test-d.ts index e4b68bd..87de602 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -47,6 +47,90 @@ expectType<{ [key: string]: string | undefined }>(req.headers); expectType(req.body); expectType<{ [key: string]: string }>(req.cookies); +// Test generic Request types +interface CustomParams { + id: string; + userId: string; +} + +interface CustomQuery { + filter?: string; + limit?: string; +} + +interface CustomBody { + name: string; + email: string; +} + +interface CustomResponseBody { + success: boolean; + message: string; + data?: any; +} + +// Test that custom types can be used with Request and Response generics +const customReq = {} as Request; +expectType(customReq.params); +expectType(customReq.query); +expectType(customReq.body); +expectType(customReq.params.id); +expectType(customReq.params.userId); +expectType(customReq.query.filter); +expectType(customReq.query.limit); +expectType(customReq.body.name); +expectType(customReq.body.email); + +const customRes = {} as Response; +expectType(customRes.json({ success: true, message: 'test' })); +expectType(customRes.send({ success: true, message: 'test', data: {} })); + +// Test that sending wrong type should cause type error +expectError(customRes.json({ wrong: 'type' })); +expectError(customRes.send({ invalid: 'data' })); + +// Test generic middleware types +type CustomRequest = Request; +type CustomResponse = Response; + +const typedMiddleware: Middleware = ( + req, + res, + next +) => { + expectType(req.params.id); + expectType(req.params.userId); + expectType(res.json({ success: true, message: 'middleware test' })); + next(); +}; +expectType>(typedMiddleware); + +// Test generic handler function types +type FullCustomRequest = Request; + +const typedHandler: HandlerFunction = ( + req, + res +) => { + expectType(req.params.id); + expectType(req.query.filter); + expectType(req.body.name); + res.json({ success: true, message: `Hello ${req.body.name}` }); +}; +expectType>(typedHandler); + +// Test generic error handling middleware +const typedErrorMiddleware: ErrorHandlingMiddleware< + CustomRequest, + CustomResponse +> = (error, req, res, next) => { + expectType(req.params.id); + res.json({ success: false, message: error.message }); +}; +expectType>( + typedErrorMiddleware +); + const apiGwV1Event: APIGatewayProxyEvent = { body: '{"test":"body"}', headers: { 'content-type': 'application/json' }, @@ -265,3 +349,67 @@ expectType(apiError); expectType(apiError.message); expectType(apiError.code); expectType(apiError.detail); + +// Test specific example from the GitHub issue +interface MyDefinedRequest extends Request { + params: { + thingId: string; + anotherThingId: string; + }; + context: Context & { metrics: any }; // MetricsLogger was not imported +} + +interface MyDefinedResponseBody { + hello: string; + foo: string; +} + +// Test the exact use case from the issue +const issueExampleHandler = ( + request: MyDefinedRequest, + response: Response +) => { + // request.params should be strongly typed + expectType(request.params.thingId); + expectType(request.params.anotherThingId); + + // response.json should expect MyDefinedResponseBody + response.json({ hello: 'world', foo: 'bar' }); + + // This should cause a TypeScript error + expectError(response.json({ wrong: 'type' })); +}; + +expectType< + (request: MyDefinedRequest, response: Response) => void +>(issueExampleHandler); + +// Test backwards compatibility - default types should work the same as before +const defaultReq = {} as Request; +expectType<{ [key: string]: string | undefined }>(defaultReq.params); +expectType<{ [key: string]: string | undefined }>(defaultReq.query); +expectType(defaultReq.body); + +const defaultRes = {} as Response; +expectType(defaultRes.json({ any: 'value' })); +expectType(defaultRes.send('any string')); +expectType(defaultRes.send({ any: 'object' })); + +const defaultMiddleware: Middleware = (req, res, next) => { + expectType<{ [key: string]: string | undefined }>(req.params); + expectType<{ [key: string]: string | undefined }>(req.query); + expectType(req.body); + expectType(res.json({ any: 'value' })); + expectType(res.send('any')); + next(); +}; +expectType(defaultMiddleware); + +const defaultHandler: HandlerFunction = (req, res) => { + expectType<{ [key: string]: string | undefined }>(req.params); + expectType<{ [key: string]: string | undefined }>(req.query); + expectType(req.body); + expectType(res.json({ any: 'value' })); + expectType(res.send('any')); +}; +expectType(defaultHandler);