diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..c8cfd379 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +src/Router.ts diff --git a/package.json b/package.json index 5514c90c..1e774116 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "itty-router", - "version": "4.0.8", + "version": "4.0.9", "description": "A tiny, zero-dependency router, designed to make beautiful APIs in any environment.", "type": "module", "main": "./index.js", diff --git a/src/Router.ts b/src/Router.ts index 53d25c1b..8f33db11 100644 --- a/src/Router.ts +++ b/src/Router.ts @@ -3,21 +3,21 @@ export type GenericTraps = { } export type RequestLike = { - method: string - url: string + method: string, + url: string, } & GenericTraps export type IRequestStrict = { - method: string - url: string - route: string + method: string, + url: string, + route: string, params: { - [key: string]: string - } + [key: string]: string, + }, query: { - [key: string]: string | string[] | undefined - } - proxy?: any + [key: string]: string | string[] | undefined, + }, + proxy?: any, } & Request export type IRequest = IRequestStrict & GenericTraps @@ -34,112 +34,77 @@ export type RouteHandler = { export type RouteEntry = [string, RegExp, RouteHandler[], string] // this is the generic "Route", which allows per-route overrides -export type Route = < - RequestType = IRequest, - Args extends any[] = any[], - RT = RouterType ->( +export type Route = ( path: string, ...handlers: RouteHandler[] ) => RT // this is an alternative UniveralRoute, accepting generics (from upstream), but without // per-route overrides -export type UniversalRoute< - RequestType = IRequest, - Args extends any[] = any[] -> = ( +export type UniversalRoute = ( path: string, ...handlers: RouteHandler[] ) => RouterType, Args> // helper function to detect equality in types (used to detect custom Request on router) -type Equal = (() => T extends X ? 1 : 2) extends () => T extends Y - ? 1 - : 2 - ? true - : false +type Equal = (() => T extends X ? 1 : 2) extends (() => T extends Y ? 1 : 2) ? true : false; export type CustomRoutes = { - [key: string]: R + [key: string]: R, } export type RouterType = { - __proto__: RouterType - routes: RouteEntry[] - handle: ( - request: RequestLike, - ...extra: Equal extends true ? A : Args - ) => Promise - all: R - delete: R - get: R - head: R - options: R - patch: R - post: R - put: R + __proto__: RouterType, + routes: RouteEntry[], + handle: (request: RequestLike, ...extra: Equal extends true ? A : Args) => Promise + all: R, + delete: R, + get: R, + head: R, + options: R, + patch: R, + post: R, + put: R, } & CustomRoutes export const Router = < RequestType = IRequest, Args extends any[] = any[], - RouteType = Equal extends true - ? Route - : UniversalRoute ->({ base = '', routes = [] }: RouterOptions = {}): RouterType< - RouteType, - Args -> => + RouteType = Equal extends true ? Route : UniversalRoute +>({ base = '', routes = [] }: RouterOptions = {}): RouterType => // @ts-expect-error TypeScript doesn't know that Proxy makes this work ({ - __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: RouteHandler[]) => - routes.push([ - prop.toUpperCase(), - RegExp( - `^${ - (path = (base + '/' + route).replace(/\/+(\/|$)/g, '$1')) // strip double & trailing splash - .replace(/(\/?\.?):(\w+)\+/g, '($1(?<$2>*))') // greedy params - .replace(/(\/?\.?):(\w+)/g, '($1(?<$2>[^$1/]+?))') // named params and image format - .replace(/\./g, '\\.') // dot in path - .replace(/(\/?)\*/g, '($1.*)?') // wildcard - }/*$` - ), - handlers, // embed handlers - path, // embed clean route path - ]) && receiver, - } - ), + __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: RouteHandler[]) => + routes.push( + [ + prop.toUpperCase(), + RegExp(`^${(path = (base + '/' + route).replace(/\/+(\/|$)/g, '$1')) // strip double & trailing splash + .replace(/(\/?\.?):(\w+)\+/g, '($1(?<$2>*))') // greedy params + .replace(/(\/?\.?):(\w+)/g, '($1(?<$2>[^$1/]+?))') // named params and image format + .replace(/\./g, '\\.') // dot in path + .replace(/(\/?)\*/g, '($1.*)?') // wildcard + }/*$`), + handlers, // embed handlers + path, // embed clean route path + ] + ) && receiver + }), routes, - async handle(request: RequestLike, ...args) { - let response, - match, - url = new URL(request.url), - query: any = (request.query = { __proto__: null }) + async handle (request: RequestLike, ...args) { + let response, match, url = new URL(request.url), query: any = request.query = { __proto__: null } for (let [k, v] of url.searchParams) { query[k] = query[k] === undefined ? v : [query[k], v].flat() } for (let [method, regex, handlers, path] of routes) { - if ( - (method === request.method || method === 'ALL') && - (match = url.pathname.match(regex)) - ) { - request.params = match.groups || {} // embed params in request - request.route = path // embed route path in request + if ((method === request.method || method === 'ALL') && (match = url.pathname.match(regex))) { + request.params = match.groups || {} // embed params in request + request.route = path // embed route path in request for (let handler of handlers) { - if ( - (response = await handler(request.proxy || request, ...args)) !== - undefined - ) - return response + if ((response = await handler(request.proxy || request, ...args)) !== undefined) return response } } } - }, + } })