h3 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -3,34 +3,30 @@ import { createRouter as createRouter$1 } from 'radix3';
3
3
  import destr from 'destr';
4
4
  import { parse, serialize } from 'cookie-es';
5
5
 
6
- const defineHandle = (handler) => handler;
6
+ const defineHandler = (handler) => handler;
7
+ const defineHandle = defineHandler;
7
8
  const defineMiddleware = (middleware) => middleware;
8
- function promisifyHandle(handle) {
9
+ function promisifyHandler(handler) {
9
10
  return function(req, res) {
10
- return callHandle(handle, req, res);
11
+ return callHandler(handler, req, res);
11
12
  };
12
13
  }
13
- function callHandle(handle, req, res) {
14
+ const promisifyHandle = promisifyHandler;
15
+ function callHandler(handler, req, res) {
14
16
  return new Promise((resolve, reject) => {
15
17
  const next = (err) => err ? reject(err) : resolve(void 0);
16
18
  try {
17
- const returned = handle(req, res, next);
18
- if (returned !== void 0) {
19
- resolve(returned);
20
- } else {
21
- res.once("close", next);
22
- res.once("error", next);
23
- }
19
+ return resolve(handler(req, res, next));
24
20
  } catch (err) {
25
21
  next(err);
26
22
  }
27
23
  });
28
24
  }
29
- function lazyHandle(handle, promisify) {
25
+ function defineLazyHandler(handler, promisify) {
30
26
  let _promise;
31
27
  const resolve = () => {
32
28
  if (!_promise) {
33
- _promise = Promise.resolve(handle()).then((r) => promisify ? promisifyHandle(r.default || r) : r.default || r);
29
+ _promise = Promise.resolve(handler()).then((r) => promisify ? promisifyHandler(r.default || r) : r.default || r);
34
30
  }
35
31
  return _promise;
36
32
  };
@@ -38,26 +34,91 @@ function lazyHandle(handle, promisify) {
38
34
  return resolve().then((h) => h(req, res));
39
35
  };
40
36
  }
41
- function useBase(base, handle) {
37
+ const lazyHandle = defineLazyHandler;
38
+ function useBase(base, handler) {
42
39
  base = withoutTrailingSlash(base);
43
40
  if (!base) {
44
- return handle;
41
+ return handler;
45
42
  }
46
43
  return function(req, res) {
47
44
  req.originalUrl = req.originalUrl || req.url || "/";
48
45
  req.url = withoutBase(req.url || "/", base);
49
- return handle(req, res);
46
+ return handler(req, res);
47
+ };
48
+ }
49
+
50
+ function defineEventHandler(handler) {
51
+ handler.__is_handler__ = true;
52
+ return handler;
53
+ }
54
+ function defineLazyEventHandler(factory) {
55
+ let _promise;
56
+ let _resolved;
57
+ const resolveHandler = () => {
58
+ if (_resolved) {
59
+ return Promise.resolve(_resolved);
60
+ }
61
+ if (!_promise) {
62
+ _promise = Promise.resolve(factory()).then((r) => {
63
+ const handler = r.default || r;
64
+ if (typeof handler !== "function") {
65
+ throw new TypeError("Invalid lazy handler result. It should be a function:", handler);
66
+ }
67
+ _resolved = toEventHandler(r.default || r);
68
+ return _resolved;
69
+ });
70
+ }
71
+ return _promise;
72
+ };
73
+ return defineEventHandler((event) => {
74
+ if (_resolved) {
75
+ return _resolved(event);
76
+ }
77
+ return resolveHandler().then((handler) => handler(event));
78
+ });
79
+ }
80
+ function isEventHandler(input) {
81
+ return "__is_handler__" in input;
82
+ }
83
+ function toEventHandler(handler) {
84
+ if (isEventHandler(handler)) {
85
+ return handler;
86
+ }
87
+ if (typeof handler !== "function") {
88
+ throw new TypeError("Invalid handler. It should be a function:", handler);
89
+ }
90
+ return defineEventHandler((event) => {
91
+ return callHandler(handler, event.req, event.res);
92
+ });
93
+ }
94
+ function createEvent(req, res) {
95
+ const event = {
96
+ __is_event__: true,
97
+ req,
98
+ res
50
99
  };
100
+ event.event = event;
101
+ req.event = event;
102
+ req.req = req;
103
+ req.res = res;
104
+ res.event = event;
105
+ res.res = res;
106
+ res.req.res = res;
107
+ res.req.req = req;
108
+ return event;
109
+ }
110
+ function isEvent(input) {
111
+ return "__is_event__" in input;
51
112
  }
52
113
 
53
- function useQuery(req) {
54
- return getQuery(req.url || "");
114
+ function useQuery(event) {
115
+ return getQuery(event.req.url || "");
55
116
  }
56
- function useMethod(req, defaultMethod = "GET") {
57
- return (req.method || defaultMethod).toUpperCase();
117
+ function useMethod(event, defaultMethod = "GET") {
118
+ return (event.req.method || defaultMethod).toUpperCase();
58
119
  }
59
- function isMethod(req, expected, allowHead) {
60
- const method = useMethod(req);
120
+ function isMethod(event, expected, allowHead) {
121
+ const method = useMethod(event);
61
122
  if (allowHead && method === "HEAD") {
62
123
  return true;
63
124
  }
@@ -70,8 +131,8 @@ function isMethod(req, expected, allowHead) {
70
131
  }
71
132
  return false;
72
133
  }
73
- function assertMethod(req, expected, allowHead) {
74
- if (!isMethod(req, expected, allowHead)) {
134
+ function assertMethod(event, expected, allowHead) {
135
+ if (!isMethod(event, expected, allowHead)) {
75
136
  throw createError({
76
137
  statusCode: 405,
77
138
  statusMessage: "HTTP method is not allowed."
@@ -82,18 +143,18 @@ function assertMethod(req, expected, allowHead) {
82
143
  const RawBodySymbol = Symbol("h3RawBody");
83
144
  const ParsedBodySymbol = Symbol("h3RawBody");
84
145
  const PayloadMethods = ["PATCH", "POST", "PUT", "DELETE"];
85
- function useRawBody(req, encoding = "utf-8") {
86
- assertMethod(req, PayloadMethods);
87
- if (RawBodySymbol in req) {
88
- const promise2 = Promise.resolve(req[RawBodySymbol]);
146
+ function useRawBody(event, encoding = "utf-8") {
147
+ assertMethod(event, PayloadMethods);
148
+ if (RawBodySymbol in event.req) {
149
+ const promise2 = Promise.resolve(event.req[RawBodySymbol]);
89
150
  return encoding ? promise2.then((buff) => buff.toString(encoding)) : promise2;
90
151
  }
91
- if ("body" in req) {
92
- return Promise.resolve(req.body);
152
+ if ("body" in event.req) {
153
+ return Promise.resolve(event.req.body);
93
154
  }
94
- const promise = req[RawBodySymbol] = new Promise((resolve, reject) => {
155
+ const promise = event.req[RawBodySymbol] = new Promise((resolve, reject) => {
95
156
  const bodyData = [];
96
- req.on("error", (err) => {
157
+ event.req.on("error", (err) => {
97
158
  reject(err);
98
159
  }).on("data", (chunk) => {
99
160
  bodyData.push(chunk);
@@ -103,13 +164,13 @@ function useRawBody(req, encoding = "utf-8") {
103
164
  });
104
165
  return encoding ? promise.then((buff) => buff.toString(encoding)) : promise;
105
166
  }
106
- async function useBody(req) {
107
- if (ParsedBodySymbol in req) {
108
- return req[ParsedBodySymbol];
167
+ async function useBody(event) {
168
+ if (ParsedBodySymbol in event.req) {
169
+ return event.req[ParsedBodySymbol];
109
170
  }
110
- const body = await useRawBody(req);
171
+ const body = await useRawBody(event);
111
172
  const json = destr(body);
112
- req[ParsedBodySymbol] = json;
173
+ event.req[ParsedBodySymbol] = json;
113
174
  return json;
114
175
  }
115
176
 
@@ -119,51 +180,61 @@ const MIMES = {
119
180
  };
120
181
 
121
182
  const defer = typeof setImmediate !== "undefined" ? setImmediate : (fn) => fn();
122
- function send(res, data, type) {
183
+ function send(event, data, type) {
123
184
  if (type) {
124
- defaultContentType(res, type);
185
+ defaultContentType(event, type);
125
186
  }
126
187
  return new Promise((resolve) => {
127
188
  defer(() => {
128
- res.end(data);
189
+ event.res.end(data);
129
190
  resolve(void 0);
130
191
  });
131
192
  });
132
193
  }
133
- function defaultContentType(res, type) {
134
- if (type && !res.getHeader("Content-Type")) {
135
- res.setHeader("Content-Type", type);
194
+ function defaultContentType(event, type) {
195
+ if (type && !event.res.getHeader("Content-Type")) {
196
+ event.res.setHeader("Content-Type", type);
136
197
  }
137
198
  }
138
- function sendRedirect(res, location, code = 302) {
139
- res.statusCode = code;
140
- res.setHeader("Location", location);
141
- return send(res, "Redirecting to " + location, MIMES.html);
199
+ function sendRedirect(event, location, code = 302) {
200
+ event.res.statusCode = code;
201
+ event.res.setHeader("Location", location);
202
+ return send(event, "Redirecting to " + location, MIMES.html);
142
203
  }
143
- function appendHeader(res, name, value) {
144
- let current = res.getHeader(name);
204
+ function appendHeader(event, name, value) {
205
+ let current = event.res.getHeader(name);
145
206
  if (!current) {
146
- res.setHeader(name, value);
207
+ event.res.setHeader(name, value);
147
208
  return;
148
209
  }
149
210
  if (!Array.isArray(current)) {
150
211
  current = [current.toString()];
151
212
  }
152
- res.setHeader(name, current.concat(value));
213
+ event.res.setHeader(name, current.concat(value));
214
+ }
215
+ function isStream(data) {
216
+ return data && typeof data === "object" && typeof data.pipe === "function" && typeof data.on === "function";
217
+ }
218
+ function sendStream(event, data) {
219
+ return new Promise((resolve, reject) => {
220
+ data.pipe(event.res);
221
+ data.on("end", () => resolve(void 0));
222
+ data.on("error", (error) => reject(createError(error)));
223
+ });
153
224
  }
154
225
 
155
- function useCookies(req) {
156
- return parse(req.headers.cookie || "");
226
+ function useCookies(event) {
227
+ return parse(event.req.headers.cookie || "");
157
228
  }
158
- function useCookie(req, name) {
159
- return useCookies(req)[name];
229
+ function useCookie(event, name) {
230
+ return useCookies(event)[name];
160
231
  }
161
- function setCookie(res, name, value, serializeOptions) {
232
+ function setCookie(event, name, value, serializeOptions) {
162
233
  const cookieStr = serialize(name, value, serializeOptions);
163
- appendHeader(res, "Set-Cookie", cookieStr);
234
+ appendHeader(event, "Set-Cookie", cookieStr);
164
235
  }
165
- function deleteCookie(res, name, serializeOptions) {
166
- setCookie(res, name, "", {
236
+ function deleteCookie(event, name, serializeOptions) {
237
+ setCookie(event, name, "", {
167
238
  ...serializeOptions,
168
239
  maxAge: 0
169
240
  });
@@ -192,7 +263,7 @@ function createError(input) {
192
263
  }
193
264
  return err;
194
265
  }
195
- function sendError(res, error, debug) {
266
+ function sendError(event, error, debug) {
196
267
  let h3Error;
197
268
  if (error instanceof H3Error) {
198
269
  h3Error = error;
@@ -200,37 +271,42 @@ function sendError(res, error, debug) {
200
271
  console.error(error);
201
272
  h3Error = createError(error);
202
273
  }
203
- if (res.writableEnded) {
274
+ if (event.res.writableEnded) {
204
275
  return;
205
276
  }
206
- res.statusCode = h3Error.statusCode;
207
- res.statusMessage = h3Error.statusMessage;
277
+ event.res.statusCode = h3Error.statusCode;
278
+ event.res.statusMessage = h3Error.statusMessage;
208
279
  const responseBody = {
209
- statusCode: res.statusCode,
210
- statusMessage: res.statusMessage,
280
+ statusCode: event.res.statusCode,
281
+ statusMessage: event.res.statusMessage,
211
282
  stack: [],
212
283
  data: h3Error.data
213
284
  };
214
285
  if (debug) {
215
286
  responseBody.stack = (h3Error.stack || "").split("\n").map((l) => l.trim());
216
287
  }
217
- res.setHeader("Content-Type", MIMES.json);
218
- res.end(JSON.stringify(responseBody, null, 2));
288
+ event.res.setHeader("Content-Type", MIMES.json);
289
+ event.res.end(JSON.stringify(responseBody, null, 2));
219
290
  }
220
291
 
221
292
  function createApp(options = {}) {
222
293
  const stack = [];
223
- const _handle = createHandle(stack, options);
224
- const app = function(req, res) {
225
- return _handle(req, res).catch((error) => {
294
+ const handler = createAppEventHandler(stack, options);
295
+ const nodeHandler = async function(req, res) {
296
+ const event = createEvent(req, res);
297
+ try {
298
+ await handler(event);
299
+ } catch (err) {
226
300
  if (options.onError) {
227
- return options.onError(error, req, res);
301
+ await options.onError(err, event);
228
302
  }
229
- return sendError(res, error, !!options.debug);
230
- });
303
+ await sendError(event, err, !!options.debug);
304
+ }
231
305
  };
306
+ const app = nodeHandler;
307
+ app.nodeHandler = nodeHandler;
232
308
  app.stack = stack;
233
- app._handle = _handle;
309
+ app.handler = handler;
234
310
  app.use = (arg1, arg2, arg3) => use(app, arg1, arg2, arg3);
235
311
  return app;
236
312
  }
@@ -240,61 +316,69 @@ function use(app, arg1, arg2, arg3) {
240
316
  } else if (Array.isArray(arg2)) {
241
317
  arg2.forEach((i) => use(app, arg1, i, arg3));
242
318
  } else if (typeof arg1 === "string") {
243
- app.stack.push(normalizeLayer({ ...arg3, route: arg1, handle: arg2 }));
319
+ app.stack.push(normalizeLayer({ ...arg3, route: arg1, handler: arg2 }));
244
320
  } else if (typeof arg1 === "function") {
245
- app.stack.push(normalizeLayer({ ...arg2, route: "/", handle: arg1 }));
321
+ app.stack.push(normalizeLayer({ ...arg2, route: "/", handler: arg1 }));
246
322
  } else {
247
323
  app.stack.push(normalizeLayer({ ...arg1 }));
248
324
  }
249
325
  return app;
250
326
  }
251
- function createHandle(stack, options) {
327
+ function createAppEventHandler(stack, options) {
252
328
  const spacing = options.debug ? 2 : void 0;
253
- return async function handle(req, res) {
254
- req.originalUrl = req.originalUrl || req.url || "/";
255
- const reqUrl = req.url || "/";
329
+ return defineEventHandler(async (event) => {
330
+ event.req.originalUrl = event.req.originalUrl || event.req.url || "/";
331
+ const reqUrl = event.req.url || "/";
256
332
  for (const layer of stack) {
257
333
  if (layer.route.length > 1) {
258
334
  if (!reqUrl.startsWith(layer.route)) {
259
335
  continue;
260
336
  }
261
- req.url = reqUrl.slice(layer.route.length) || "/";
337
+ event.req.url = reqUrl.slice(layer.route.length) || "/";
262
338
  } else {
263
- req.url = reqUrl;
339
+ event.req.url = reqUrl;
264
340
  }
265
- if (layer.match && !layer.match(req.url, req)) {
341
+ if (layer.match && !layer.match(event.req.url, event)) {
266
342
  continue;
267
343
  }
268
- const val = await layer.handle(req, res);
269
- if (res.writableEnded) {
344
+ const val = await layer.handler(event);
345
+ if (event.res.writableEnded) {
270
346
  return;
271
347
  }
272
348
  const type = typeof val;
273
349
  if (type === "string") {
274
- return send(res, val, MIMES.html);
350
+ return send(event, val, MIMES.html);
351
+ } else if (isStream(val)) {
352
+ return sendStream(event, val);
275
353
  } else if (type === "object" || type === "boolean" || type === "number") {
276
354
  if (val && val.buffer) {
277
- return send(res, val);
355
+ return send(event, val);
278
356
  } else if (val instanceof Error) {
279
357
  throw createError(val);
280
358
  } else {
281
- return send(res, JSON.stringify(val, null, spacing), MIMES.json);
359
+ return send(event, JSON.stringify(val, null, spacing), MIMES.json);
282
360
  }
283
361
  }
284
362
  }
285
- if (!res.writableEnded) {
363
+ if (!event.res.writableEnded) {
286
364
  throw createError({ statusCode: 404, statusMessage: "Not Found" });
287
365
  }
288
- };
366
+ });
289
367
  }
290
- function normalizeLayer(layer) {
291
- if (layer.promisify === void 0) {
292
- layer.promisify = layer.handle.length > 2;
368
+ function normalizeLayer(input) {
369
+ let handler = input.handler || input.handle;
370
+ if (handler.handler) {
371
+ handler = handler.handler;
372
+ }
373
+ if (input.lazy) {
374
+ handler = defineLazyEventHandler(handler);
375
+ } else if (!isEventHandler(handler)) {
376
+ handler = toEventHandler(handler);
293
377
  }
294
378
  return {
295
- route: withoutTrailingSlash(layer.route),
296
- match: layer.match,
297
- handle: layer.lazy ? lazyHandle(layer.handle, layer.promisify) : layer.promisify ? promisifyHandle(layer.handle) : layer.handle
379
+ route: withoutTrailingSlash(input.route),
380
+ match: input.match,
381
+ handler
298
382
  };
299
383
  }
300
384
 
@@ -303,28 +387,34 @@ function createRouter() {
303
387
  const _router = createRouter$1({});
304
388
  const routes = {};
305
389
  const router = {};
306
- router.add = (path, handle, method = "all") => {
390
+ const addRoute = (path, handler, method) => {
307
391
  let route = routes[path];
308
392
  if (!route) {
309
393
  routes[path] = route = { handlers: {} };
310
394
  _router.insert(path, route);
311
395
  }
312
- route.handlers[method] = handle;
396
+ route.handlers[method] = toEventHandler(handler);
313
397
  return router;
314
398
  };
399
+ router.use = router.add = (path, handler, method) => addRoute(path, handler, method || "all");
315
400
  for (const method of RouterMethods) {
316
401
  router[method] = (path, handle) => router.add(path, handle, method);
317
402
  }
318
- router.handle = (req, res) => {
319
- const matched = _router.lookup(req.url || "/");
403
+ router.handler = defineEventHandler((event) => {
404
+ let path = event.req.url || "/";
405
+ const queryUrlIndex = path.lastIndexOf("?");
406
+ if (queryUrlIndex > -1) {
407
+ path = path.substring(0, queryUrlIndex);
408
+ }
409
+ const matched = _router.lookup(path);
320
410
  if (!matched) {
321
411
  throw createError({
322
412
  statusCode: 404,
323
413
  name: "Not Found",
324
- statusMessage: `Cannot find any route matching ${req.url || "/"}.`
414
+ statusMessage: `Cannot find any route matching ${event.req.url || "/"}.`
325
415
  });
326
416
  }
327
- const method = (req.method || "get").toLowerCase();
417
+ const method = (event.req.method || "get").toLowerCase();
328
418
  const handler = matched.handlers[method] || matched.handlers.all;
329
419
  if (!handler) {
330
420
  throw createError({
@@ -333,10 +423,11 @@ function createRouter() {
333
423
  statusMessage: `Method ${method} is not allowed on this route.`
334
424
  });
335
425
  }
336
- req.params = matched.params || {};
337
- return handler(req, res);
338
- };
426
+ event.event.params = matched.params || {};
427
+ event.req.params = event.event.params;
428
+ return handler(event);
429
+ });
339
430
  return router;
340
431
  }
341
432
 
342
- export { H3Error, MIMES, appendHeader, assertMethod, callHandle, createApp, createError, createHandle, createRouter, defaultContentType, defineHandle, defineMiddleware, deleteCookie, isMethod, lazyHandle, promisifyHandle, send, sendError, sendRedirect, setCookie, use, useBase, useBody, useCookie, useCookies, useMethod, useQuery, useRawBody };
433
+ export { H3Error, MIMES, appendHeader, assertMethod, callHandler, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineHandle, defineHandler, defineLazyEventHandler, defineLazyHandler, defineMiddleware, deleteCookie, isEvent, isEventHandler, isMethod, isStream, lazyHandle, promisifyHandle, promisifyHandler, send, sendError, sendRedirect, sendStream, setCookie, toEventHandler, use, useBase, useBody, useCookie, useCookies, useMethod, useQuery, useRawBody };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "h3",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "Tiny JavaScript Server",
5
5
  "repository": "unjs/h3",
6
6
  "license": "MIT",
@@ -17,14 +17,6 @@
17
17
  "files": [
18
18
  "dist"
19
19
  ],
20
- "scripts": {
21
- "build": "unbuild",
22
- "dev": "jiti ./playground/index.ts",
23
- "lint": "eslint --ext ts,mjs,cjs .",
24
- "profile": "0x -o -D .profile -P 'autocannon -c 100 -p 10 -d 40 http://localhost:$PORT' ./playground/server.cjs",
25
- "release": "yarn lint && yarn test && yarn build && standard-version && npm publish && git push --follow-tags",
26
- "test": "vitest run"
27
- },
28
20
  "dependencies": {
29
21
  "cookie-es": "^0.5.0",
30
22
  "destr": "^1.1.0",
@@ -51,5 +43,16 @@
51
43
  "typescript": "latest",
52
44
  "unbuild": "latest",
53
45
  "vitest": "latest"
54
- }
55
- }
46
+ },
47
+ "packageManager": "pnpm@6.32.3",
48
+ "scripts": {
49
+ "build": "unbuild",
50
+ "dev": "vitest",
51
+ "lint": "eslint --ext ts,mjs,cjs .",
52
+ "play": "jiti ./playground/index.ts",
53
+ "profile": "0x -o -D .profile -P 'autocannon -c 100 -p 10 -d 40 http://localhost:$PORT' ./playground/server.cjs",
54
+ "release": "pnpm lint && pnpm test && pnpm build && standard-version && pnpm publish && git push --follow-tags",
55
+ "test": "vitest run"
56
+ },
57
+ "readme": "[![npm downloads](https://img.shields.io/npm/dm/h3.svg?style=flat-square)](https://npmjs.com/package/h3)\n[![version](https://img.shields.io/npm/v/h3/latest.svg?style=flat-square)](https://npmjs.com/package/h3)\n[![bundlephobia](https://img.shields.io/bundlephobia/min/h3/latest.svg?style=flat-square)](https://bundlephobia.com/result?p=h3)\n[![build status](https://img.shields.io/github/workflow/status/unjs/h3/ci/main?style=flat-square)](https://github.com/unjs/h3/actions)\n[![coverage](https://img.shields.io/codecov/c/gh/unjs/h3/main?style=flat-square)](https://codecov.io/gh/unjs/h3)\n[![jsDocs.io](https://img.shields.io/badge/jsDocs.io-reference-blue?style=flat-square)](https://www.jsdocs.io/package/h3)\n\n> H3 is a minimal h(ttp) framework built for high performance and portability\n\n<!-- ![h3 - Tiny JavaScript Server](.github/banner.svg) -->\n\n## Features\n\n✔️ &nbsp;**Portable:** Works perfectly in Serverless, Workers, and Node.js\n\n✔️ &nbsp;**Compatible:** Support connect/express middleware\n\n✔️ &nbsp;**Minimal:** Small, tree-shakable and zero-dependency\n\n✔️ &nbsp;**Modern:** Native promise support\n\n✔️ &nbsp;**Extendable:** Ships with a set of composable utilities but can be extended\n\n✔️ &nbsp;**Router:** Super fast route matching using [unjs/radix3](https://github.com/unjs/radix3)\n\n## Install\n\n```bash\n# Using npm\nnpm install h3\n\n# Using yarn\nyarn add h3\n\n# Using pnpm\npnpm add h3\n```\n\n## Usage\n\n```ts\nimport { createServer } from 'http'\nimport { createApp } from 'h3'\n\nconst app = createApp()\napp.use('/', () => 'Hello world!')\n\ncreateServer(app).listen(process.env.PORT || 3000)\n```\n\n<details>\n <summary>Example using <a href=\"https://github.com/unjs/listhen\">listhen</a> for an elegant listener.</summary>\n\n```ts\nimport { createApp } from 'h3'\nimport { listen } from 'listhen'\n\nconst app = createApp()\napp.use('/', () => 'Hello world!')\n\nlisten(app)\n```\n</details>\n\n## Router\n\nThe `app` instance created by `h3` uses a middleware stack (see [how it works](#how-it-works)) with the ability to match route prefix and apply matched middleware.\n\nTo opt-in using a more advanced and convenient routing system, we can create a router instance and register it to app instance.\n\n```ts\nimport { createApp, createRouter } from 'h3'\n\nconst app = createApp()\n\nconst router = createRouter()\n .get('/', () => 'Hello World!')\n .get('/hello/:name', req => `Hello ${req.params.name}!`)\n\napp.use(router)\n```\n\n**Tip:** We can register same route more than once with different methods.\n\nRoutes are internally stored in a [Radix Tree](https://en.wikipedia.org/wiki/Radix_tree) and matched using [unjs/radix3](https://github.com/unjs/radix3).\n\n## More usage examples\n\n```js\n// Handle can directly return object or Promise<object> for JSON response\napp.use('/api', (req) => ({ url: req.url }))\n\n// We can have better matching other than quick prefix match\napp.use('/odd', () => 'Is odd!', { match: url => url.substr(1) % 2 })\n\n// Handle can directly return string for HTML response\napp.use(() => '<h1>Hello world!</h1>')\n\n// We can chain calls to .use()\napp.use('/1', () => '<h1>Hello world!</h1>')\n .use('/2', () => '<h1>Goodbye!</h1>')\n\n// Legacy middleware with 3rd argument are automatically promisified\napp.use((req, res, next) => { req.setHeader('X-Foo', 'bar'); next() })\n\n// Force promisify a legacy middleware\n// app.use(someMiddleware, { promisify: true })\n\n// Lazy loaded routes using { lazy: true }\n// app.use('/big', () => import('./big'), { lazy: true })\n```\n\n## Utilities\n\nInstead of adding helpers to `req` and `res`, h3 exposes them as composable utilities.\n\n- `useRawBody(req, encoding?)`\n- `useBody(req)`\n- `useCookies(req)`\n- `useCookie(req, name)`\n- `setCookie(res, name, value, opts?)`\n- `deleteCookie(res, name, opts?)`\n- `useQuery(req)`\n- `send(res, data, type?)`\n- `sendRedirect(res, location, code=302)`\n- `appendHeader(res, name, value)`\n- `createError({ statusCode, statusMessage, data? })`\n- `sendError(res, error, debug?)`\n- `defineHandle(handle)`\n- `defineMiddleware(middlware)`\n- `useMethod(req, default?)`\n- `isMethod(req, expected, allowHead?)`\n- `assertMethod(req, expected, allowHead?)`\n\n👉 You can learn more about usage in [JSDocs Documentation](https://www.jsdocs.io/package/h3#package-functions).\n\n## How it works?\n\nUsing `createApp`, it returns a standard `(req, res)` handler function and internally an array called middleware stack. using`use()` method we can add an item to this internal stack.\n\nWhen a request comes, each stack item that matches the route will be called and resolved until [`res.writableEnded`](https://nodejs.org/api/http.html#http_response_writableended) flag is set, which means the response is sent. If `writableEnded` is not set after all middleware, a `404` error will be thrown. And if one of the stack items resolves to a value, it will be serialized and sent as response as a shorthand method to sending responses.\n\nFor maximum compatibility with connect/express middleware (`req, res, next?` signature), h3 converts classic middleware into a promisified version ready to use with stack runner:\n\n- If middleware has 3rd next/callback param, the promise will `resolve/reject` when called\n- If middleware returns a promise, it will be **chained** to the main promise\n- If calling middleware throws an immediate error, the promise will be rejected\n- On `close` and `error` events of res, the promise will `resolve/reject` (to ensure if middleware simply calls `res.end`)\n\n## License\n\nMIT\n"
58
+ }