mirror of
https://github.com/honojs/hono.git
synced 2024-11-21 18:18:57 +01:00
fix(cloudflare-pages): Expose Cloudflare Pages type parameters (#3065)
This commit is contained in:
parent
f2908d62fb
commit
f393730d44
@ -2,12 +2,31 @@ import { getCookie } from '../../helper/cookie'
|
||||
import { Hono } from '../../hono'
|
||||
import { HTTPException } from '../../http-exception'
|
||||
import type { EventContext } from './handler'
|
||||
import { handle, handleMiddleware } from './handler'
|
||||
import { handle, handleMiddleware, serveStatic } from './handler'
|
||||
|
||||
type Env = {
|
||||
Bindings: {
|
||||
TOKEN: string
|
||||
eventContext: EventContext
|
||||
}
|
||||
}
|
||||
|
||||
function createEventContext(
|
||||
context: Partial<EventContext<Env['Bindings']>>
|
||||
): EventContext<Env['Bindings']> {
|
||||
return {
|
||||
data: {},
|
||||
env: {
|
||||
...context.env,
|
||||
ASSETS: { fetch: vi.fn(), ...context.env?.ASSETS },
|
||||
TOKEN: context.env?.TOKEN ?? 'HONOISCOOL',
|
||||
},
|
||||
functionPath: '_worker.js',
|
||||
next: vi.fn(),
|
||||
params: {},
|
||||
passThroughOnException: vi.fn(),
|
||||
request: new Request('http://localhost/api/foo'),
|
||||
waitUntil: vi.fn(),
|
||||
...context,
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,17 +34,29 @@ describe('Adapter for Cloudflare Pages', () => {
|
||||
it('Should return 200 response', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
ASSETS: { fetch },
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const waitUntil = vi.fn()
|
||||
const passThroughOnException = vi.fn()
|
||||
const eventContext = createEventContext({
|
||||
request,
|
||||
env,
|
||||
waitUntil,
|
||||
passThroughOnException,
|
||||
})
|
||||
const app = new Hono<Env>()
|
||||
const appFetchSpy = vi.spyOn(app, 'fetch')
|
||||
app.get('/api/foo', (c) => {
|
||||
const reqInEventContext = c.env.eventContext.request
|
||||
return c.json({ TOKEN: c.env.TOKEN, requestURL: reqInEventContext.url })
|
||||
return c.json({ TOKEN: c.env.TOKEN, requestURL: c.req.url })
|
||||
})
|
||||
const handler = handle(app)
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const res = await handler({ request, env })
|
||||
const res = await handler(eventContext)
|
||||
expect(appFetchSpy).toHaveBeenCalledWith(
|
||||
request,
|
||||
{ ...env, eventContext },
|
||||
{ waitUntil, passThroughOnException }
|
||||
)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({
|
||||
TOKEN: 'HONOISCOOL',
|
||||
@ -35,6 +66,7 @@ describe('Adapter for Cloudflare Pages', () => {
|
||||
|
||||
it('Should not use `basePath()` if path argument is not passed', async () => {
|
||||
const request = new Request('http://localhost/api/error')
|
||||
const eventContext = createEventContext({ request })
|
||||
const app = new Hono().basePath('/api')
|
||||
|
||||
app.onError((e) => {
|
||||
@ -46,9 +78,7 @@ describe('Adapter for Cloudflare Pages', () => {
|
||||
|
||||
const handler = handle(app)
|
||||
// It does throw the error if app is NOT "subApp"
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
expect(() => handler({ request })).toThrowError('Custom Error')
|
||||
expect(() => handler(eventContext)).toThrowError('Custom Error')
|
||||
})
|
||||
})
|
||||
|
||||
@ -59,9 +89,8 @@ describe('Middleware adapter for Cloudflare Pages', () => {
|
||||
Cookie: 'my_cookie=1234',
|
||||
},
|
||||
})
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const next = vi.fn().mockResolvedValue(Response.json('From Cloudflare Pages'))
|
||||
const eventContext = createEventContext({ request, next })
|
||||
const handler = handleMiddleware(async (c, next) => {
|
||||
const cookie = getCookie(c, 'my_cookie')
|
||||
|
||||
@ -70,10 +99,7 @@ describe('Middleware adapter for Cloudflare Pages', () => {
|
||||
return c.json({ cookie, response: await c.res.json() })
|
||||
})
|
||||
|
||||
const next = vi.fn().mockResolvedValue(Response.json('From Cloudflare Pages'))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const res = await handler({ request, env, next })
|
||||
const res = await handler(eventContext)
|
||||
|
||||
expect(next).toHaveBeenCalled()
|
||||
|
||||
@ -85,9 +111,6 @@ describe('Middleware adapter for Cloudflare Pages', () => {
|
||||
|
||||
it('Should return the middleware response when exceptions are handled', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware(async (c, next) => {
|
||||
await next()
|
||||
|
||||
@ -95,9 +118,8 @@ describe('Middleware adapter for Cloudflare Pages', () => {
|
||||
})
|
||||
|
||||
const next = vi.fn().mockRejectedValue(new Error('Error from next()'))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const res = await handler({ request, env, next })
|
||||
const eventContext = createEventContext({ request, next })
|
||||
const res = await handler(eventContext)
|
||||
|
||||
expect(next).toHaveBeenCalled()
|
||||
|
||||
@ -108,17 +130,13 @@ describe('Middleware adapter for Cloudflare Pages', () => {
|
||||
|
||||
it('Should return the middleware response if next() is not called', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware(async (c) => {
|
||||
return c.json({ response: 'Skip Cloudflare Pages' })
|
||||
})
|
||||
|
||||
const next = vi.fn()
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const res = await handler({ request, env, next })
|
||||
const eventContext = createEventContext({ request, next })
|
||||
const res = await handler(eventContext)
|
||||
|
||||
expect(next).not.toHaveBeenCalled()
|
||||
|
||||
@ -129,15 +147,11 @@ describe('Middleware adapter for Cloudflare Pages', () => {
|
||||
|
||||
it('Should return the Pages response if the middleware does not return a response', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware((c, next) => next())
|
||||
|
||||
const next = vi.fn().mockResolvedValue(Response.json('From Cloudflare Pages'))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const res = await handler({ request, env, next })
|
||||
const eventContext = createEventContext({ request, next })
|
||||
const res = await handler(eventContext)
|
||||
|
||||
expect(next).toHaveBeenCalled()
|
||||
|
||||
@ -146,18 +160,14 @@ describe('Middleware adapter for Cloudflare Pages', () => {
|
||||
|
||||
it('Should handle a HTTPException by returning error.getResponse()', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware(() => {
|
||||
const res = new Response('Unauthorized', { status: 401 })
|
||||
throw new HTTPException(401, { res })
|
||||
})
|
||||
|
||||
const next = vi.fn()
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const res = await handler({ request, env, next })
|
||||
const eventContext = createEventContext({ request, next })
|
||||
const res = await handler(eventContext)
|
||||
|
||||
expect(next).not.toHaveBeenCalled()
|
||||
|
||||
@ -167,17 +177,13 @@ describe('Middleware adapter for Cloudflare Pages', () => {
|
||||
|
||||
it('Should handle an HTTPException thrown by next()', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware((c, next) => next())
|
||||
|
||||
const next = vi
|
||||
.fn()
|
||||
.mockRejectedValue(new HTTPException(401, { res: Response.json('Unauthorized') }))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const res = await handler({ request, env, next })
|
||||
const eventContext = createEventContext({ request, next })
|
||||
const res = await handler(eventContext)
|
||||
|
||||
expect(next).toHaveBeenCalled()
|
||||
|
||||
@ -186,58 +192,81 @@ describe('Middleware adapter for Cloudflare Pages', () => {
|
||||
|
||||
it('Should handle an Error thrown by next()', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware((c, next) => next())
|
||||
|
||||
const next = vi.fn().mockRejectedValue(new Error('Error from next()'))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
await expect(handler({ request, env, next })).rejects.toThrowError('Error from next()')
|
||||
const eventContext = createEventContext({ request, next })
|
||||
await expect(handler(eventContext)).rejects.toThrowError('Error from next()')
|
||||
expect(next).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('Should handle a non-Error thrown by next()', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware((c, next) => next())
|
||||
|
||||
const next = vi.fn().mockRejectedValue('Error from next()')
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
await expect(handler({ request, env, next })).rejects.toThrowError('Error from next()')
|
||||
const eventContext = createEventContext({ request, next })
|
||||
await expect(handler(eventContext)).rejects.toThrowError('Error from next()')
|
||||
expect(next).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('Should rethrow an Error', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware(() => {
|
||||
throw new Error('Something went wrong')
|
||||
})
|
||||
|
||||
const next = vi.fn()
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
await expect(handler({ request, env, next })).rejects.toThrowError('Something went wrong')
|
||||
const eventContext = createEventContext({ request, next })
|
||||
await expect(handler(eventContext)).rejects.toThrowError('Something went wrong')
|
||||
expect(next).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('Should rethrow non-Error exceptions', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware(() => Promise.reject('Something went wrong'))
|
||||
const next = vi.fn()
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
await expect(handler({ request, env, next })).rejects.toThrowError('Something went wrong')
|
||||
const eventContext = createEventContext({ request, next })
|
||||
await expect(handler(eventContext)).rejects.toThrowError('Something went wrong')
|
||||
expect(next).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('serveStatic()', () => {
|
||||
it('Should pass the raw request to ASSETS.fetch', async () => {
|
||||
const assetsFetch = vi.fn().mockResolvedValue(new Response('foo.png'))
|
||||
const request = new Request('http://localhost/foo.png')
|
||||
const env = {
|
||||
ASSETS: { fetch: assetsFetch },
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
|
||||
const eventContext = createEventContext({ request, env })
|
||||
const app = new Hono<Env>()
|
||||
app.use(serveStatic())
|
||||
const handler = handle(app)
|
||||
const res = await handler(eventContext)
|
||||
|
||||
expect(assetsFetch).toHaveBeenCalledWith(request)
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('foo.png')
|
||||
})
|
||||
|
||||
it('Should respond with 404 if ASSETS.fetch returns a 404 response', async () => {
|
||||
const assetsFetch = vi.fn().mockResolvedValue(new Response(null, { status: 404 }))
|
||||
const request = new Request('http://localhost/foo.png')
|
||||
const env = {
|
||||
ASSETS: { fetch: assetsFetch },
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
|
||||
const eventContext = createEventContext({ request, env })
|
||||
const app = new Hono<Env>()
|
||||
app.use(serveStatic())
|
||||
const handler = handle(app)
|
||||
const res = await handler(eventContext)
|
||||
|
||||
expect(assetsFetch).toHaveBeenCalledWith(request)
|
||||
expect(res.status).toBe(404)
|
||||
})
|
||||
})
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Context } from '../../context'
|
||||
import type { Hono } from '../../hono'
|
||||
import { HTTPException } from '../../http-exception'
|
||||
import type { Env, Input, MiddlewareHandler } from '../../types'
|
||||
import type { BlankSchema, Env, Input, MiddlewareHandler, Schema } from '../../types'
|
||||
|
||||
// Ref: https://github.com/cloudflare/workerd/blob/main/types/defines/pages.d.ts
|
||||
|
||||
@ -27,26 +27,25 @@ declare type PagesFunction<
|
||||
Data extends Record<string, unknown> = Record<string, unknown>
|
||||
> = (context: EventContext<Env, Params, Data>) => Response | Promise<Response>
|
||||
|
||||
interface HandleInterface {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(app: Hono<any, any, any>): (eventContext: EventContext) => Response | Promise<Response>
|
||||
}
|
||||
|
||||
export const handle: HandleInterface = (app: Hono) => (eventContext: EventContext) => {
|
||||
return app.fetch(
|
||||
eventContext.request,
|
||||
{ ...eventContext.env, eventContext },
|
||||
{
|
||||
waitUntil: eventContext.waitUntil,
|
||||
passThroughOnException: eventContext.passThroughOnException,
|
||||
}
|
||||
)
|
||||
}
|
||||
export const handle =
|
||||
<E extends Env = Env, S extends Schema = BlankSchema, BasePath extends string = '/'>(
|
||||
app: Hono<E, S, BasePath>
|
||||
): PagesFunction<E['Bindings']> =>
|
||||
(eventContext) => {
|
||||
return app.fetch(
|
||||
eventContext.request,
|
||||
{ ...eventContext.env, eventContext },
|
||||
{
|
||||
waitUntil: eventContext.waitUntil,
|
||||
passThroughOnException: eventContext.passThroughOnException,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function handleMiddleware<E extends Env = any, P extends string = any, I extends Input = {}>(
|
||||
middleware: MiddlewareHandler<E, P, I>
|
||||
): PagesFunction {
|
||||
): PagesFunction<E['Bindings']> {
|
||||
return async (executionCtx) => {
|
||||
const context = new Context(executionCtx.request, {
|
||||
env: executionCtx.env,
|
||||
|
Loading…
Reference in New Issue
Block a user