From a406f8c1356a65d28bad8044f0ff4a6e74fc9f7f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 Aug 2025 16:46:32 +0000 Subject: [PATCH 1/3] Initial plan From b7e1d2bcaa2d21ec80ef6df6e8e64f5fcd270941 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 Aug 2025 16:54:18 +0000 Subject: [PATCH 2/3] Add TypeScript generic support for Request and Response types Co-authored-by: naorpeled <6171622+naorpeled@users.noreply.github.com> --- index.d.ts | 59 +++++++++++++++++----------- index.test-d.ts | 102 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 22 deletions(-) diff --git a/index.d.ts b/index.d.ts index d87a09e..80e095f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -44,21 +44,33 @@ export declare interface App { [namespace: string]: Package; } -export declare type Middleware = ( - req: Request, - res: Response, +export declare type Middleware< + TReq = Request, + TRes = Response +> = ( + req: TReq, + res: TRes, next: NextFunction ) => void; -export declare type ErrorHandlingMiddleware = ( + +export declare type ErrorHandlingMiddleware< + TReq = Request, + TRes = Response +> = ( 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< + TReq = Request, + TRes = Response +> = ( + req: TReq, + res: TRes, next?: NextFunction ) => void | any | Promise; @@ -77,7 +89,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< + TReq = Request, + TRes = Response +> = (req: TReq, res: TRes) => void; export declare type METHODS = | 'GET' | 'POST' @@ -136,18 +151,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 +175,7 @@ export declare class Request { rawHeaders?: { [key: string]: string | undefined; }; - body: any; + body: TBody; rawBody: string; route: ''; requestContext: APIGatewayEventRequestContext; @@ -196,7 +211,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 +234,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..b5d2ccf 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -47,6 +47,78 @@ 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 = (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 +337,33 @@ expectType(apiError); expectType(apiError.message); expectType(apiError.code); expectType(apiError.detail); + +// 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); From a38ee14cf44f636d6cc45a1b2d12baace4e1bf22 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 Aug 2025 16:57:48 +0000 Subject: [PATCH 3/3] Add comprehensive TypeScript tests and fix formatting Co-authored-by: naorpeled <6171622+naorpeled@users.noreply.github.com> --- index.d.ts | 23 +++++++-------------- index.test-d.ts | 54 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/index.d.ts b/index.d.ts index 80e095f..cef7b6d 100644 --- a/index.d.ts +++ b/index.d.ts @@ -44,19 +44,13 @@ export declare interface App { [namespace: string]: Package; } -export declare type Middleware< - TReq = Request, - TRes = Response -> = ( +export declare type Middleware = ( req: TReq, res: TRes, next: NextFunction ) => void; -export declare type ErrorHandlingMiddleware< - TReq = Request, - TRes = Response -> = ( +export declare type ErrorHandlingMiddleware = ( error: Error, req: TReq, res: TRes, @@ -65,10 +59,7 @@ export declare type ErrorHandlingMiddleware< export declare type ErrorCallback = (error?: Error) => void; -export declare type HandlerFunction< - TReq = Request, - TRes = Response -> = ( +export declare type HandlerFunction = ( req: TReq, res: TRes, next?: NextFunction @@ -89,10 +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< - TReq = Request, - TRes = Response -> = (req: TReq, res: TRes) => void; +export declare type FinallyFunction = ( + req: TReq, + res: TRes +) => void; export declare type METHODS = | 'GET' | 'POST' diff --git a/index.test-d.ts b/index.test-d.ts index b5d2ccf..87de602 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -93,7 +93,11 @@ expectError(customRes.send({ invalid: 'data' })); type CustomRequest = Request; type CustomResponse = Response; -const typedMiddleware: Middleware = (req, res, next) => { +const typedMiddleware: Middleware = ( + req, + res, + next +) => { expectType(req.params.id); expectType(req.params.userId); expectType(res.json({ success: true, message: 'middleware test' })); @@ -104,7 +108,10 @@ expectType>(typedMiddleware); // Test generic handler function types type FullCustomRequest = Request; -const typedHandler: HandlerFunction = (req, res) => { +const typedHandler: HandlerFunction = ( + req, + res +) => { expectType(req.params.id); expectType(req.query.filter); expectType(req.body.name); @@ -113,11 +120,16 @@ const typedHandler: HandlerFunction = (req, r expectType>(typedHandler); // Test generic error handling middleware -const typedErrorMiddleware: ErrorHandlingMiddleware = (error, req, res, next) => { +const typedErrorMiddleware: ErrorHandlingMiddleware< + CustomRequest, + CustomResponse +> = (error, req, res, next) => { expectType(req.params.id); res.json({ success: false, message: error.message }); }; -expectType>(typedErrorMiddleware); +expectType>( + typedErrorMiddleware +); const apiGwV1Event: APIGatewayProxyEvent = { body: '{"test":"body"}', @@ -338,6 +350,40 @@ 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);