diff --git a/CHANGELOG.md b/CHANGELOG.md index bbf2084..58ec6f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Changelog +- **v5.0.3** + - fixed: (TypeScript) middleware corrupting downstream request types and args - **v5.0.2** - fixed: AutoRouter was missing the router-level generics support of the other 2 routers. - fixed: All 3 routers had their 3rd generic argument, ResponseType added per the spec. diff --git a/examples/types/additional-arguments.ts b/examples/types/additional-arguments.ts new file mode 100644 index 0000000..6ad8933 --- /dev/null +++ b/examples/types/additional-arguments.ts @@ -0,0 +1,56 @@ +import { RequestHandler } from '../../src/types' +import { IRequestStrict } from '../../src/types' +import { Router } from '../../src/Router' +import { IttyRouter } from '../../src/IttyRouter' +import { AutoRouter } from '../../src/AutoRouter' +import { IRequest } from 'itty-router' + +// we define our environment +type Environment = { age: number } +type Pet = { name: string } + +// and now both args combined (that Workers send to the .fetch()) +type Args = [Environment] +type AlternativeArgs = [Pet] + +// creating some middleware that needs access to CF variables +export const withUser: RequestHandler = + (request, env) => { + request.user = 'Kevin' + env.age = 123 + env.name = 'Kevin' // invalid + } + +const router = Router() + +router + // before middleware + .get('/', (request, env) => { + request.user = 'kevin' // invalid (strict) + env.whatever = 123 // valid (any) + env.age = 123 // valid (any) + }) + + // route-level overrides + .get('/', (request, env) => { + request.foo = 'bar' // invalid + env.whatever = 123 // invalid + env.age = 123 // valid + }) + // route-level overrides + .get('/', (request, env) => { + request.foo = 'bar' // invalid + env.age = 123 // invalid + env.name = 'Mittens' // valid + }) + + // after middleware + .get('/', withUser, (request, env) => { + request.user = 'Kevin' + env.age = 123 // valid + env.whatever = 123 // invalid + }) + + .get('/', (request, env) => { + env.age = 'foo' // valid (any) + }) diff --git a/examples/types/global-and-route-level-request.ts b/examples/types/global-and-route-level-request.ts index 5bbc7e5..b4cbd96 100644 --- a/examples/types/global-and-route-level-request.ts +++ b/examples/types/global-and-route-level-request.ts @@ -1,7 +1,10 @@ -import { IRequestStrict } from 'IttyRouter' -import { Router } from 'Router' +import { IRequestStrict } from '../../src/types' +import { Router } from '../../src/Router' -const router = Router() +type Pet = { name: string } +type List = number[] + +const router = Router() type FooRequest = { foo: string @@ -10,6 +13,11 @@ type FooRequest = { router .get('/basic', () => new Response('Success!')) .get('/text', () => 'Success!') + .get('/text', (request, env) => { + env.name = 'foo' + env.bar = 'baz' // invalid + }) + .get('/', (r, env) => env.foo ) // .get('/params/:foo', ({ foo }) => foo) // should NOT work .get('/params/:foo', ({ foo }) => foo) // should work .get('/json', () => ({ foo: 'bar' })) diff --git a/examples/types/middleware.ts b/examples/types/middleware.ts index 7642bbf..2ad26b4 100644 --- a/examples/types/middleware.ts +++ b/examples/types/middleware.ts @@ -1,41 +1,37 @@ import { IRequestStrict, IRequest, RequestHandler } from '../../src/types' -import { IttyRouter } from 'IttyRouter' +import { IttyRouter } from '../../src/IttyRouter' type UserRequest = { user: string } & IRequestStrict +// we define a *strict* router for this demo const router = IttyRouter() -const withUser: RequestHandler = (request) => { +// middleware with explicit request-type (generic) +const withUser: RequestHandler = (request) => { request.user = 'Kevin' } router - // upstream request sees the request as IRequest (default), so anything goes + // request will be IRequestStrict here, thus no user property .get('/', (request) => { - request.user = 123 // not OK + request.user = 'Kevin' // invalid }) - // then we add the middleware defined above as + // then we add the middleware defined above, allowing the handler chain to inherit the request type .all('*', withUser, (request) => { request.user = 'Kevin' - request.user = 123 // NOT ok - }) - - // and if we ever need to restore control, add the generic back in - .get('/', (request) => { - request.user = 'Kevin' - request.user = 123 // NOT ok }) + // request will be back to IRequestStrict here, thus no user property .get('/', (request) => { - request.user = 'Kevin' // still ok - request.user = 123 // NOT ok + request.user = 'Kevin' // invalid }) - router.get('/', (request) => { - request.user = 123 // NOT ok + // and if we ever need to restore control, add the generic back in + .get('/', (request) => { + request.user = 'Kevin' }) export default router diff --git a/src/IttyRouter.ts b/src/IttyRouter.ts index c6305ea..bbed2a3 100644 --- a/src/IttyRouter.ts +++ b/src/IttyRouter.ts @@ -9,13 +9,15 @@ import { export const IttyRouter = < RequestType extends IRequest = IRequest, Args extends any[] = any[], - ResponseType = any ->({ base = '', routes = [], ...other }: IttyRouterOptions = {}): IttyRouterType => + ResponseType = any, + GlobalRequestType = RequestType, +>({ base = '', routes = [], ...other }: IttyRouterOptions = {}): IttyRouterType => +// @ts-ignore ({ __proto__: new Proxy({}, { // @ts-expect-error (we're adding an expected prop "path" to the get) get: (target: any, prop: string, receiver: object, path: string) => - (route: string, ...handlers: RequestHandler[]) => + (route: string, ...handlers: RequestHandler[]) => routes.push( [ prop.toUpperCase(), @@ -53,4 +55,4 @@ export const IttyRouter = < if ((response = await handler(request.proxy ?? request, ...args)) != null) return response } }, - } as IttyRouterType) + }) diff --git a/src/types/IttyRouterType.ts b/src/types/IttyRouterType.ts index e94ce9d..4279e64 100644 --- a/src/types/IttyRouterType.ts +++ b/src/types/IttyRouterType.ts @@ -8,17 +8,18 @@ import { CustomRoutes } from './CustomRoutes' export type IttyRouterType< R = IRequest, A extends any[] = any[], - ResponseType = any + ResponseType = any, + GlobalRequestType = R, > = { __proto__: IttyRouterType routes: RouteEntry[] fetch: (request: RequestLike, ...extra: Args) => Promise - all: Route - delete: Route - get: Route - head: Route - options: Route - patch: Route - post: Route - put: Route -} & CustomRoutes> & GenericTraps + all: Route + delete: Route + get: Route + head: Route + options: Route + patch: Route + post: Route + put: Route +} & CustomRoutes> & GenericTraps diff --git a/src/types/Route.ts b/src/types/Route.ts index b4c10d6..fac6254 100644 --- a/src/types/Route.ts +++ b/src/types/Route.ts @@ -11,4 +11,4 @@ export type Route< >( path: string, ...handlers: RequestHandler[] -) => IttyRouterType +) => IttyRouterType