Skip to content

Commit

Permalink
fixed request/args TS flow?!
Browse files Browse the repository at this point in the history
  • Loading branch information
kwhitley committed Dec 15, 2023
1 parent 5858f48 commit 2559423
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 55 deletions.
4 changes: 2 additions & 2 deletions example/bun-flow-advanced.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ type CustomRequest = {

type RouterArgs = [env: Env]

const router = Router<CustomRequest>()
const router = Router()

router
.get('/test', () => 'Success!')
.get('/type-check', (request, env) => {
request
request.
env.
})
.get('/foo/:bar/:baz?', ({ bar, baz }) => ({ bar, baz }))
Expand Down
21 changes: 17 additions & 4 deletions example/e2e middleware.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
import { IRequest, Router } from 'Router'
import { IRequest, IRequestStrict, Router } from 'Router'

type FooRequest = {
foo: string
} & IRequest

const router = Router<FooRequest>()
type HasFoo = {
foo?: string
} & IRequest

type HasBar = {
bar?: string
} & IRequestStrict

const router = Router()

// MIDDLEWARE: adds foo to the request
const withFoo = (request: IRequest) => {
const withFoo = (request: HasFoo & IRequest) => {
request.foo = 'bar' // no return = next handler gets fired (making this middleware)
}

// MIDDLEWARE: adds foo to the request
const withBar = (request: HasBar & IRequest) => {
request.bar = 'baz' // no return = next handler gets fired (making this middleware)
}

// ADD ROUTES
router
.all('*', withFoo) // we add Foo as global upstream middleware, but this could go anywhere
.all('*', withFoo, withBar) // we add Foo as global upstream middleware, but this could go anywhere

.get('/foo-test', (request) => {
return request.foo // 'bar'
Expand Down
52 changes: 52 additions & 0 deletions example/universal-vs-overrides.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// WITH FLOW (overriding options, and enabling CORS)
import { Router, error, flow } from '../src'
import { IRequest, IRequestStrict } from '../src'

type Env = {
KV: object
}

type CustomRequest = {
foo: string
} & IRequestStrict

type RouterArgs = [env: Env]
type RouterArgs2 = [env: Env, ctx: ExecutionContext]

const universalRouter =
Router<CustomRequest, RouterArgs>()
.get('/type-check', (request, env) => {
request.foo // should be found
env.KV // should be found
env.KB // should NOT be found
})
.get<IRequestStrict>('/type-check', (request, env) => {
request.foo // should NOT be found
env.KB // should NOT be found *******
env.KV // should be found *******
})
.puppy('/type-check', (request, env) => {
request.foo // should be found
env.KV // should be found
env.KB // should NOT be found
})
.puppy<CustomRequest, RouterArgs2>('/type-check', (request, env) => {
request.foo // should be found
env.KV // should be found
env.KB // should NOT be found
})

const overrideRouter =
Router()
.get('/type-check', (request, env, ctx) => {
request.whatever // should ignore
env.whatever // should ignore
ctx.whatever // should ignore
})
.get<IRequestStrict, RouterArgs2>('/type-check', (request, env, ctx) => {
request.foo // should NOT be found
ctx.waitUntil // should infer
})
.puppy()

universalRouter.handle({ method: 'GET', url: '' }, {})
85 changes: 36 additions & 49 deletions src/Router.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
export type GenericTraps = {
[key: string]: any
}
export type GenericTraps = Record<string, any>

export type RequestLike = {
method: string,
Expand All @@ -27,74 +25,63 @@ export type RouterOptions = {
routes?: RouteEntry[]
}

export type RouteHandler<I = IRequest, Args = any[]> = {
export type RouteHandler<R = IRequest, Args = any[]> = {
// @ts-expect-error - TS never likes this syntax
(request: I, ...args: Args): any
(request: R, ...args: Args): any
}

export type RouteEntry = [string, RegExp, RouteHandler[], string]

// this is the generic "Route", which allows per-route overrides
export type Route = <RequestType = IRequest, Args = any[], RT = RouterType>(
path: string,
...handlers: RouteHandler<RequestType, Args>[]
) => 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 Route<R = IRequest, A = any[]> = <RequestType = R, Args = A, RT = RouterType>(

Check failure on line 36 in src/Router.ts

View workflow job for this annotation

GitHub Actions / build

'RT' is defined but never used
path: string,
...handlers: RouteHandler<RequestType, Args>[]
) => RouterType<UniversalRoute<RequestType, Args>, Args>

// helper function to detect equality in types (used to detect custom Request on router)
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false

// used to determine if router generics have been modified
type IsUntouched<Request, Args> = Args extends [] ? Equal<Request, IRequest> extends true ? true : false : false
// @ts-ignore foo

Check failure on line 39 in src/Router.ts

View workflow job for this annotation

GitHub Actions / build

Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free
) => RouterType<RequestType, Args>

export type CustomRoutes<R = Route> = {
[key: string]: R,
}

export type RouterType<R = Route, Args extends any[] = any[]> = {
export type RouterType<R = IRequest, A extends any[] = any[]> = {
__proto__: RouterType<R>,
routes: RouteEntry[],
handle: <A extends any[] = Args>(request: RequestLike, ...extra: Equal<R, Args> extends true ? A : Args) => Promise<any>
all: R,
delete: R,
get: R,
head: R,
options: R,
patch: R,
post: R,
put: R,
} & CustomRoutes<R>
handle: <Args extends any[] = A>(request: RequestLike, ...extra: Args) => Promise<any>
all: Route<R, A>,
delete: Route<R, A>,
get: Route<R, A>,
head: Route<R, A>,
options: Route<R, A>,
patch: Route<R, A>,
post: Route<R, A>,
put: Route<R, A>,
} & CustomRoutes<Route<R, A>>

export const Router = <
RequestType = IRequest,
Args extends any[] = any[],
RouteType = IsUntouched<RequestType, Args> extends true ? Route : UniversalRoute<RequestType, Args>
>({ base = '', routes = [] }: RouterOptions = {}): RouterType<RouteType, Args> =>
Args extends any[] = any[]
>({ base = '', routes = [] }: RouterOptions = {}): RouterType<RequestType, Args> =>
// @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<I>[]) =>
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
get: (target: any, prop: string, receiver: object, path: string) =>
(route: string, ...handlers: RouteHandler<RequestType, Args>[]) =>
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
}/*$`),
// @ts-ignore why not

Check failure on line 80 in src/Router.ts

View workflow job for this annotation

GitHub Actions / build

Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free
handlers, // embed handlers
path, // embed clean route path
]
) && receiver
}),
routes,
async handle (request: RequestLike, ...args) {
Expand Down

0 comments on commit 2559423

Please sign in to comment.