Skip to content

Commit 2cf0f4b

Browse files
authored
refactor!: use events api for utils with compatibility layer (#75)
BREAKING CHANGE: All `handle` exports and properties are renamed to `hanhttps://assets.grammarly.com/emoji/v1/1f610.svgdler` with some backward compatibilities. BREAKING CHANGE: Legacy handlers are promisified by default
1 parent 70f03fe commit 2cf0f4b

File tree

17 files changed

+294
-333
lines changed

17 files changed

+294
-333
lines changed

src/app.ts

Lines changed: 53 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,73 @@
1+
import type http from 'http'
12
import { withoutTrailingSlash } from 'ufo'
2-
import { lazyHandle, promisifyHandle } from './handle'
3-
import { toEventHandler, createEvent } from './event'
3+
import { defineLazyHandler } from './handler'
4+
import { toEventHandler, createEvent, isEventHandler } from './event'
45
import { createError, sendError } from './error'
56
import { send, sendStream, isStream, MIMES } from './utils'
6-
import type { IncomingMessage, ServerResponse } from './types/node'
7-
import type { Handle, LazyHandle, Middleware, PHandle } from './handle'
8-
import type { H3EventHandler } from './event'
7+
import type { Handler, LazyHandler, Middleware, PromisifiedHandler } from './types'
8+
import type { EventHandler, CompatibilityEvent } from './event'
99

1010
export interface Layer {
1111
route: string
1212
match?: Matcher
13-
handler: H3EventHandler
13+
handler: EventHandler
1414
}
1515

1616
export type Stack = Layer[]
1717

1818
export interface InputLayer {
1919
route?: string
2020
match?: Matcher
21-
handle: Handle | LazyHandle
21+
handler: Handler | LazyHandler
2222
lazy?: boolean
23+
/**
24+
* @deprecated
25+
*/
2326
promisify?: boolean
2427
}
2528

2629
export type InputStack = InputLayer[]
2730

28-
export type Matcher = (url: string, req?: IncomingMessage) => boolean
31+
export type Matcher = (url: string, event?: CompatibilityEvent) => boolean
32+
33+
export type RequestHandler = EventHandler | Handler | Middleware
2934

3035
export interface AppUse {
31-
(route: string | string [], handle: Middleware | Middleware[], options?: Partial<InputLayer>): App
32-
(route: string | string[], handle: Handle | Handle[], options?: Partial<InputLayer>): App
33-
(handle: Middleware | Middleware[], options?: Partial<InputLayer>): App
34-
(handle: Handle | Handle[], options?: Partial<InputLayer>): App
36+
(route: string | string [], handler: RequestHandler | RequestHandler[], options?: Partial<InputLayer>): App
37+
(handler: RequestHandler | Handler[], options?: Partial<InputLayer>): App
3538
(options: InputLayer): App
3639
}
3740

38-
export interface App {
39-
(req: IncomingMessage, res: ServerResponse): Promise<any>
41+
export type ApPromisifiedHandlerr = (req: http.IncomingMessage, res: http.ServerResponse) => Promise<any>
42+
43+
export interface App extends ApPromisifiedHandlerr {
4044
stack: Stack
41-
_handle: PHandle
45+
_handler: PromisifiedHandler
4246
use: AppUse
4347
}
4448

4549
export interface AppOptions {
4650
debug?: boolean
47-
onError?: (error: Error, req: IncomingMessage, res: ServerResponse) => any
51+
onError?: (error: Error, event: CompatibilityEvent) => any
4852
}
4953

5054
export function createApp (options: AppOptions = {}): App {
5155
const stack: Stack = []
5256

53-
const _handle = createHandle(stack, options)
57+
const _handler = createHandler(stack, options)
5458

55-
// @ts-ignore
56-
const app: Partial<App> = function (req: IncomingMessage, res: ServerResponse) {
57-
return _handle(req, res).catch((error: Error) => {
59+
const app: App = function (req, res) {
60+
const event = createEvent(req, res)
61+
return _handler(event).catch((error: Error) => {
5862
if (options.onError) {
59-
return options.onError(error, req, res)
63+
return options.onError(error, event)
6064
}
61-
return sendError(res, error, !!options.debug)
65+
return sendError(event, error, !!options.debug)
6266
})
63-
}
67+
} as App
6468

6569
app.stack = stack
66-
app._handle = _handle
70+
app._handler = _handler
6771

6872
// @ts-ignore
6973
app.use = (arg1, arg2, arg3) => use(app as App, arg1, arg2, arg3)
@@ -73,81 +77,79 @@ export function createApp (options: AppOptions = {}): App {
7377

7478
export function use (
7579
app: App,
76-
arg1: string | Handle | InputLayer | InputLayer[],
77-
arg2?: Handle | Partial<InputLayer> | Handle[] | Middleware | Middleware[],
80+
arg1: string | Handler | InputLayer | InputLayer[],
81+
arg2?: Handler | Partial<InputLayer> | Handler[] | Middleware | Middleware[],
7882
arg3?: Partial<InputLayer>
7983
) {
8084
if (Array.isArray(arg1)) {
8185
arg1.forEach(i => use(app, i, arg2, arg3))
8286
} else if (Array.isArray(arg2)) {
8387
arg2.forEach(i => use(app, arg1, i, arg3))
8488
} else if (typeof arg1 === 'string') {
85-
app.stack.push(normalizeLayer({ ...arg3, route: arg1, handle: arg2 as Handle }))
89+
app.stack.push(normalizeLayer({ ...arg3, route: arg1, handler: arg2 as Handler }))
8690
} else if (typeof arg1 === 'function') {
87-
app.stack.push(normalizeLayer({ ...arg2, route: '/', handle: arg1 as Handle }))
91+
app.stack.push(normalizeLayer({ ...arg2, route: '/', handler: arg1 as Handler }))
8892
} else {
8993
app.stack.push(normalizeLayer({ ...arg1 }))
9094
}
9195
return app
9296
}
9397

94-
export function createHandle (stack: Stack, options: AppOptions): PHandle {
98+
export function createHandler (stack: Stack, options: AppOptions) {
9599
const spacing = options.debug ? 2 : undefined
96-
return async function handle (req: IncomingMessage, res: ServerResponse) {
97-
const event = createEvent(req, res)
100+
return async function handle (event: CompatibilityEvent) {
101+
event.req.originalUrl = event.req.originalUrl || event.req.url || '/'
98102

99-
// @ts-ignore express/connect compatibility
100-
req.originalUrl = req.originalUrl || req.url || '/'
101-
const reqUrl = req.url || '/'
103+
const reqUrl = event.req.url || '/'
102104
for (const layer of stack) {
103105
if (layer.route.length > 1) {
104106
if (!reqUrl.startsWith(layer.route)) {
105107
continue
106108
}
107-
req.url = reqUrl.slice(layer.route.length) || '/'
109+
event.req.url = reqUrl.slice(layer.route.length) || '/'
108110
} else {
109-
req.url = reqUrl
111+
event.req.url = reqUrl
110112
}
111-
if (layer.match && !layer.match(req.url as string, req)) {
113+
if (layer.match && !layer.match(event.req.url as string, event)) {
112114
continue
113115
}
114116
const val = await layer.handler(event)
115-
if (res.writableEnded) {
117+
if (event.res.writableEnded) {
116118
return
117119
}
118120
const type = typeof val
119121
if (type === 'string') {
120-
return send(res, val, MIMES.html)
122+
return send(event, val, MIMES.html)
121123
} else if (isStream(val)) {
122-
return sendStream(res, val)
124+
return sendStream(event, val)
123125
} else if (type === 'object' || type === 'boolean' || type === 'number' /* IS_JSON */) {
124126
if (val && (val as Buffer).buffer) {
125-
return send(res, val)
127+
return send(event, val)
126128
} else if (val instanceof Error) {
127129
throw createError(val)
128130
} else {
129-
return send(res, JSON.stringify(val, null, spacing), MIMES.json)
131+
return send(event, JSON.stringify(val, null, spacing), MIMES.json)
130132
}
131133
}
132134
}
133-
if (!res.writableEnded) {
135+
if (!event.res.writableEnded) {
134136
throw createError({ statusCode: 404, statusMessage: 'Not Found' })
135137
}
136138
}
137139
}
138140

139141
function normalizeLayer (input: InputLayer) {
140-
if (input.promisify === undefined) {
141-
input.promisify = input.handle.length > 2 /* req, res, next */
142+
let handler = input.handler
143+
if (!isEventHandler(handler)) {
144+
if (input.lazy) {
145+
handler = defineLazyHandler(handler as LazyHandler)
146+
}
147+
handler = toEventHandler(handler)
142148
}
143149

144-
const handle = input.lazy
145-
? lazyHandle(input.handle as LazyHandle, input.promisify)
146-
: (input.promisify ? promisifyHandle(input.handle) : input.handle)
147-
148150
return {
149151
route: withoutTrailingSlash(input.route),
150152
match: input.match,
151-
handler: toEventHandler(handle)
152-
}
153+
handler
154+
} as Layer
153155
}

src/error.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ServerResponse } from './types/node'
1+
import type { CompatibilityEvent } from './event'
22
import { MIMES } from './utils'
33

44
/**
@@ -50,12 +50,12 @@ export function createError (input: Partial<H3Error>): H3Error {
5050
* H3 internally uses this function to handle unhandled errors.<br>
5151
* Note that calling this function will close the connection and no other data will be sent to client afterwards.
5252
*
53-
* @param res {ServerResponse} The ServerResponse object is passed as the second parameter in the handler function
53+
@param event {CompatibilityEvent} H3 event or req passed by h3 handler
5454
* @param error {H3Error|Error} Raised error
5555
* @param debug {Boolean} Whether application is in debug mode.<br>
5656
* In the debug mode the stack trace of errors will be return in response.
5757
*/
58-
export function sendError (res: ServerResponse, error: Error | H3Error, debug?: boolean) {
58+
export function sendError (event: CompatibilityEvent, error: Error | H3Error, debug?: boolean) {
5959
let h3Error: H3Error
6060
if (error instanceof H3Error) {
6161
h3Error = error
@@ -64,16 +64,16 @@ export function sendError (res: ServerResponse, error: Error | H3Error, debug?:
6464
h3Error = createError(error)
6565
}
6666

67-
if (res.writableEnded) {
67+
if (event.res.writableEnded) {
6868
return
6969
}
7070

71-
res.statusCode = h3Error.statusCode
72-
res.statusMessage = h3Error.statusMessage
71+
event.res.statusCode = h3Error.statusCode
72+
event.res.statusMessage = h3Error.statusMessage
7373

7474
const responseBody = {
75-
statusCode: res.statusCode,
76-
statusMessage: res.statusMessage,
75+
statusCode: event.res.statusCode,
76+
statusMessage: event.res.statusMessage,
7777
stack: [] as string[],
7878
data: h3Error.data
7979
}
@@ -82,6 +82,6 @@ export function sendError (res: ServerResponse, error: Error | H3Error, debug?:
8282
responseBody.stack = (h3Error.stack || '').split('\n').map(l => l.trim())
8383
}
8484

85-
res.setHeader('Content-Type', MIMES.json)
86-
res.end(JSON.stringify(responseBody, null, 2))
85+
event.res.setHeader('Content-Type', MIMES.json)
86+
event.res.end(JSON.stringify(responseBody, null, 2))
8787
}

src/event.ts

Lines changed: 72 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,97 @@
1-
import type { IncomingMessage, ServerResponse } from './types/node'
2-
import type { Handle, Middleware } from './handle'
1+
import type http from 'http'
2+
import type { IncomingMessage, ServerResponse, Handler, Middleware } from './types'
3+
import { callHandler } from './handler'
34

45
export interface H3Event {
6+
'__is_event__': true
7+
event: H3Event
58
req: IncomingMessage
6-
res: ServerResponse,
7-
next?: (err?: Error) => void
9+
res: ServerResponse
10+
/**
11+
* Request params only filled with h3 Router handlers
12+
*/
13+
params?: Record<string, any>
814
}
915

16+
export type CompatibilityEvent = H3Event | IncomingMessage | ServerResponse
17+
1018
export type _JSONValue<T=string|number|boolean> = T | T[] | Record<string, T>
1119
export type JSONValue = _JSONValue<_JSONValue>
1220
export type H3Response = void | JSONValue | Buffer
1321

14-
export interface H3EventHandler {
15-
__is_handler__?: true
16-
(event: H3Event): H3Response| Promise<H3Response>
22+
export interface EventHandler {
23+
'__is_handler__'?: true
24+
(event: CompatibilityEvent): H3Response| Promise<H3Response>
1725
}
1826

19-
export function defineEventHandler (handler: H3EventHandler) {
27+
export function defineEventHandler (handler: EventHandler) {
2028
handler.__is_handler__ = true
2129
return handler
2230
}
2331

24-
export function isEventHandler (handler: H3EventHandler | Handle | Middleware): handler is H3EventHandler {
25-
return '__is_handler__' in handler
32+
export function defineLazyEventHandler (factory: () => EventHandler | Promise<EventHandler>): EventHandler {
33+
let _promise: Promise<EventHandler>
34+
let _resolved: EventHandler
35+
const resolveHandler = () => {
36+
if (_resolved) { return Promise.resolve(_resolved) }
37+
if (!_promise) {
38+
_promise = Promise.resolve(factory()).then((r: any) => {
39+
_resolved = r.default || r
40+
return _resolved
41+
})
42+
}
43+
return _promise
44+
}
45+
return defineEventHandler((event) => {
46+
if (_resolved) {
47+
return _resolved(event)
48+
}
49+
return resolveHandler().then(handler => handler(event))
50+
})
2651
}
2752

28-
export function toEventHandler (handler: H3EventHandler | Handle | Middleware): H3EventHandler {
53+
export function isEventHandler (input: any): input is EventHandler {
54+
return '__is_handler__' in input
55+
}
56+
57+
export function toEventHandler (handler: EventHandler | Handler | Middleware): EventHandler {
2958
if (isEventHandler(handler)) {
3059
return handler
3160
}
32-
if (handler.length > 2) {
33-
return defineEventHandler((event) => {
34-
return (handler as Middleware)(event.req, event.res, event.next!)
35-
})
36-
} else {
37-
return defineEventHandler((event) => {
38-
return (handler as Handle)(event.req, event.res)
39-
})
40-
}
61+
return defineEventHandler((event) => {
62+
return callHandler(handler, event.req as IncomingMessage, event.res) as Promise<H3Response>
63+
})
4164
}
4265

43-
export function createEvent (req: IncomingMessage, res: ServerResponse): H3Event {
44-
return {
66+
export function createEvent (req: http.IncomingMessage, res: http.ServerResponse): CompatibilityEvent {
67+
const event = {
68+
__is_event__: true,
4569
req,
4670
res
47-
}
71+
} as H3Event
72+
73+
// Backward comatibility for interchangable usage of {event,req,res}.{req,res}
74+
// TODO: Remove in future versions
75+
// @ts-ignore
76+
event.event = event
77+
// @ts-ignore
78+
req.event = event
79+
// @ts-ignore
80+
req.req = req
81+
// @ts-ignore
82+
req.res = res
83+
// @ts-ignore
84+
res.event = event
85+
// @ts-ignore
86+
res.res = res
87+
// @ts-ignore
88+
res.req.res = res
89+
// @ts-ignore
90+
res.req.req = req
91+
92+
return event
93+
}
94+
95+
export function isEvent (input: any): input is H3Event {
96+
return '__is_event__' in input
4897
}

0 commit comments

Comments
 (0)