h3 1.0.1 → 1.1.0

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/README.md CHANGED
@@ -39,28 +39,32 @@ pnpm add h3
39
39
  ## Usage
40
40
 
41
41
  ```ts
42
- import { createServer } from 'http'
43
- import { createApp, eventHandler, toNodeListener } from 'h3'
42
+ import { createServer } from "node:http";
43
+ import { createApp, eventHandler, toNodeListener } from "h3";
44
44
 
45
- const app = createApp()
46
- app.use('/', eventHandler(() => 'Hello world!'))
45
+ const app = createApp();
46
+ app.use(
47
+ "/",
48
+ eventHandler(() => "Hello world!")
49
+ );
47
50
 
48
- createServer(toNodeListener(app)).listen(process.env.PORT || 3000)
51
+ createServer(toNodeListener(app)).listen(process.env.PORT || 3000);
49
52
  ```
50
53
 
51
- <details>
52
- <summary>Example using <a href="https://github.com/unjs/listhen">listhen</a> for an elegant listener.</summary>
54
+ Example using <a href="https://github.com/unjs/listhen">listhen</a> for an elegant listener:
53
55
 
54
56
  ```ts
55
- import { createApp, toNodeListener } from 'h3'
56
- import { listen } from 'listhen'
57
+ import { createApp, eventHandler, toNodeListener } from "h3";
58
+ import { listen } from "listhen";
57
59
 
58
- const app = createApp()
59
- app.use('/', eventHandler(() => 'Hello world!'))
60
+ const app = createApp();
61
+ app.use(
62
+ "/",
63
+ eventHandler(() => "Hello world!")
64
+ );
60
65
 
61
- listen(toNodeListener(app))
66
+ listen(toNodeListener(app));
62
67
  ```
63
- </details>
64
68
 
65
69
  ## Router
66
70
 
@@ -69,15 +73,21 @@ The `app` instance created by `h3` uses a middleware stack (see [how it works](#
69
73
  To opt-in using a more advanced and convenient routing system, we can create a router instance and register it to app instance.
70
74
 
71
75
  ```ts
72
- import { createApp, eventHandler, createRouter } from 'h3'
76
+ import { createApp, eventHandler, createRouter } from "h3";
73
77
 
74
- const app = createApp()
78
+ const app = createApp();
75
79
 
76
80
  const router = createRouter()
77
- .get('/', eventHandler(() => 'Hello World!'))
78
- .get('/hello/:name', eventHandler(event => `Hello ${event.context.params.name}!`))
79
-
80
- app.use(router)
81
+ .get(
82
+ "/",
83
+ eventHandler(() => "Hello World!")
84
+ )
85
+ .get(
86
+ "/hello/:name",
87
+ eventHandler((event) => `Hello ${event.context.params.name}!`)
88
+ );
89
+
90
+ app.use(router);
81
91
  ```
82
92
 
83
93
  **Tip:** We can register same route more than once with different methods.
@@ -113,13 +123,13 @@ H3 has concept of compasable utilities that accept `event` (from `eventHandler((
113
123
 
114
124
  ### Built-in
115
125
 
116
- - `useRawBody(event, encoding?)`
117
- - `useBody(event)`
118
- - `useCookies(event)`
119
- - `useCookie(event, name)`
126
+ - `readRawBody(event, encoding?)`
127
+ - `readBody(event)`
128
+ - `parseCookies(event)`
129
+ - `getCookie(event, name)`
120
130
  - `setCookie(event, name, value, opts?)`
121
131
  - `deleteCookie(event, name, opts?)`
122
- - `useQuery(event)`
132
+ - `getQuery(event)`
123
133
  - `getRouterParams(event)`
124
134
  - `send(event, data, type?)`
125
135
  - `sendRedirect(event, location, code=302)`
@@ -132,23 +142,37 @@ H3 has concept of compasable utilities that accept `event` (from `eventHandler((
132
142
  - `writeEarlyHints(event, links, callback)`
133
143
  - `sendStream(event, data)`
134
144
  - `sendError(event, error, debug?)`
135
- - `useMethod(event, default?)`
145
+ - `getMethod(event, default?)`
136
146
  - `isMethod(event, expected, allowHead?)`
137
147
  - `assertMethod(event, expected, allowHead?)`
138
148
  - `createError({ statusCode, statusMessage, data? })`
139
149
  - `sendProxy(event, { target, headers?, fetchOptions?, fetch?, sendStream? })`
140
150
  - `proxyRequest(event, { target, headers?, fetchOptions?, fetch?, sendStream? })`
151
+ - `sendNoContent(event, code = 204)`
152
+ - `setResponseStatus(event, status)`
153
+ - `getResponseStatus(event)`
154
+ - `getResponseStatusText(event)`
155
+ - `readMultipartFormData(event)`
141
156
 
142
157
  👉 You can learn more about usage in [JSDocs Documentation](https://www.jsdocs.io/package/h3#package-functions).
143
158
 
144
- ### Add-ons
159
+ ## Community Packages
160
+
161
+ You can use more h3 event utilities made by the community.
162
+
163
+ Please check their READMEs for more details.
145
164
 
146
- More composable utilities can be found in community packages.
165
+ PRs are welcome to add your packages.
147
166
 
148
- - `validateBody(event, schema)` from [h3-typebox](https://github.com/kevinmarrec/h3-typebox)
149
- - `validateQuery(event, schema)` from [h3-typebox](https://github.com/kevinmarrec/h3-typebox)
150
- - `useValidatedBody(event, schema)` from [h3-zod](https://github.com/wobsoriano/h3-zod)
151
- - `useValidatedQuery(event, schema)` from [h3-zod](https://github.com/wobsoriano/h3-zod)
167
+ - [h3-cors](https://github.com/NozomuIkuta/h3-cors)
168
+ - `defineCorsEventHandler(options)`
169
+ - `isPreflight(event)`
170
+ - [h3-typebox](https://github.com/kevinmarrec/h3-typebox)
171
+ - `validateBody(event, schema)`
172
+ - `validateQuery(event, schema)`
173
+ - [h3-zod](https://github.com/wobsoriano/h3-zod)
174
+ - `useValidatedBody(event, schema)`
175
+ - `useValidatedQuery(event, schema)`
152
176
 
153
177
  ## License
154
178
 
package/dist/index.cjs CHANGED
@@ -17,6 +17,83 @@ function useBase(base, handler) {
17
17
  });
18
18
  }
19
19
 
20
+ function parse(multipartBodyBuffer, boundary) {
21
+ let lastline = "";
22
+ let state = 0 /* INIT */;
23
+ let buffer = [];
24
+ const allParts = [];
25
+ let currentPartHeaders = [];
26
+ for (let i = 0; i < multipartBodyBuffer.length; i++) {
27
+ const prevByte = i > 0 ? multipartBodyBuffer[i - 1] : null;
28
+ const currByte = multipartBodyBuffer[i];
29
+ const newLineChar = currByte === 10 || currByte === 13;
30
+ if (!newLineChar) {
31
+ lastline += String.fromCodePoint(currByte);
32
+ }
33
+ const newLineDetected = currByte === 10 && prevByte === 13;
34
+ if (0 /* INIT */ === state && newLineDetected) {
35
+ if ("--" + boundary === lastline) {
36
+ state = 1 /* READING_HEADERS */;
37
+ }
38
+ lastline = "";
39
+ } else if (1 /* READING_HEADERS */ === state && newLineDetected) {
40
+ if (lastline.length > 0) {
41
+ const i2 = lastline.indexOf(":");
42
+ if (i2 > 0) {
43
+ const name = lastline.slice(0, i2).toLowerCase();
44
+ const value = lastline.slice(i2 + 1).trim();
45
+ currentPartHeaders.push([name, value]);
46
+ }
47
+ } else {
48
+ state = 2 /* READING_DATA */;
49
+ buffer = [];
50
+ }
51
+ lastline = "";
52
+ } else if (2 /* READING_DATA */ === state) {
53
+ if (lastline.length > boundary.length + 4) {
54
+ lastline = "";
55
+ }
56
+ if ("--" + boundary === lastline) {
57
+ const j = buffer.length - lastline.length;
58
+ const part = buffer.slice(0, j - 1);
59
+ allParts.push(process(part, currentPartHeaders));
60
+ buffer = [];
61
+ currentPartHeaders = [];
62
+ lastline = "";
63
+ state = 3 /* READING_PART_SEPARATOR */;
64
+ } else {
65
+ buffer.push(currByte);
66
+ }
67
+ if (newLineDetected) {
68
+ lastline = "";
69
+ }
70
+ } else if (3 /* READING_PART_SEPARATOR */ === state && newLineDetected) {
71
+ state = 1 /* READING_HEADERS */;
72
+ }
73
+ }
74
+ return allParts;
75
+ }
76
+ function process(data, headers) {
77
+ const dataObj = {};
78
+ const contentDispositionHeader = headers.find((h) => h[0] === "content-disposition")?.[1] || "";
79
+ for (const i of contentDispositionHeader.split(";")) {
80
+ const s = i.split("=");
81
+ if (s.length !== 2) {
82
+ continue;
83
+ }
84
+ const key = (s[0] || "").trim();
85
+ if (key === "name" || key === "filename") {
86
+ dataObj[key] = (s[1] || "").trim().replace(/"/g, "");
87
+ }
88
+ }
89
+ const contentType = headers.find((h) => h[0] === "content-type")?.[1] || "";
90
+ if (contentType) {
91
+ dataObj.type = contentType;
92
+ }
93
+ dataObj.data = Buffer.from(data);
94
+ return dataObj;
95
+ }
96
+
20
97
  class H3Error extends Error {
21
98
  constructor() {
22
99
  super(...arguments);
@@ -47,12 +124,18 @@ function createError(input) {
47
124
  if (isError(input)) {
48
125
  return input;
49
126
  }
50
- const err = new H3Error(input.message ?? input.statusMessage, input.cause ? { cause: input.cause } : void 0);
127
+ const err = new H3Error(
128
+ input.message ?? input.statusMessage,
129
+ // @ts-ignore
130
+ input.cause ? { cause: input.cause } : void 0
131
+ );
51
132
  if ("stack" in input) {
52
133
  try {
53
- Object.defineProperty(err, "stack", { get() {
54
- return input.stack;
55
- } });
134
+ Object.defineProperty(err, "stack", {
135
+ get() {
136
+ return input.stack;
137
+ }
138
+ });
56
139
  } catch {
57
140
  try {
58
141
  err.stack = input.stack;
@@ -178,17 +261,20 @@ function readRawBody(event, encoding = "utf8") {
178
261
  if (!Number.parseInt(event.node.req.headers["content-length"] || "")) {
179
262
  return Promise.resolve(void 0);
180
263
  }
181
- const promise = event.node.req[RawBodySymbol] = new Promise((resolve, reject) => {
182
- const bodyData = [];
183
- event.node.req.on("error", (err) => {
184
- reject(err);
185
- }).on("data", (chunk) => {
186
- bodyData.push(chunk);
187
- }).on("end", () => {
188
- resolve(Buffer.concat(bodyData));
189
- });
190
- });
191
- return encoding ? promise.then((buff) => buff.toString(encoding)) : promise;
264
+ const promise = event.node.req[RawBodySymbol] = new Promise(
265
+ (resolve, reject) => {
266
+ const bodyData = [];
267
+ event.node.req.on("error", (err) => {
268
+ reject(err);
269
+ }).on("data", (chunk) => {
270
+ bodyData.push(chunk);
271
+ }).on("end", () => {
272
+ resolve(Buffer.concat(bodyData));
273
+ });
274
+ }
275
+ );
276
+ const result = encoding ? promise.then((buff) => buff.toString(encoding)) : promise;
277
+ return result;
192
278
  }
193
279
  async function readBody(event) {
194
280
  if (ParsedBodySymbol in event.node.req) {
@@ -196,13 +282,39 @@ async function readBody(event) {
196
282
  }
197
283
  const body = await readRawBody(event);
198
284
  if (event.node.req.headers["content-type"] === "application/x-www-form-urlencoded") {
199
- const parsedForm = Object.fromEntries(new URLSearchParams(body));
285
+ const form = new URLSearchParams(body);
286
+ const parsedForm = /* @__PURE__ */ Object.create(null);
287
+ for (const [key, value] of form.entries()) {
288
+ if (key in parsedForm) {
289
+ if (!Array.isArray(parsedForm[key])) {
290
+ parsedForm[key] = [parsedForm[key]];
291
+ }
292
+ parsedForm[key].push(value);
293
+ } else {
294
+ parsedForm[key] = value;
295
+ }
296
+ }
200
297
  return parsedForm;
201
298
  }
202
299
  const json = destr(body);
203
300
  event.node.req[ParsedBodySymbol] = json;
204
301
  return json;
205
302
  }
303
+ async function readMultipartFormData(event) {
304
+ const contentType = getRequestHeader(event, "content-type");
305
+ if (!contentType || !contentType.startsWith("multipart/form-data")) {
306
+ return;
307
+ }
308
+ const boundary = contentType.match(/boundary=([^;]*)(;|$)/i)?.[1];
309
+ if (!boundary) {
310
+ return;
311
+ }
312
+ const body = await readRawBody(event, false);
313
+ if (!body) {
314
+ return;
315
+ }
316
+ return parse(body, boundary);
317
+ }
206
318
 
207
319
  function handleCacheHeaders(event, opts) {
208
320
  const cacheControls = ["public", ...opts.cacheControls || []];
@@ -251,6 +363,25 @@ function send(event, data, type) {
251
363
  });
252
364
  });
253
365
  }
366
+ function sendNoContent(event, code = 204) {
367
+ event.node.res.statusCode = code;
368
+ if (event.node.res.statusCode === 204) {
369
+ event.node.res.removeHeader("content-length");
370
+ }
371
+ event.node.res.end();
372
+ }
373
+ function setResponseStatus(event, code, text) {
374
+ event.node.res.statusCode = code;
375
+ if (text) {
376
+ event.node.res.statusMessage = text;
377
+ }
378
+ }
379
+ function getResponseStatus(event) {
380
+ return event.node.res.statusCode;
381
+ }
382
+ function getResponseStatusText(event) {
383
+ return event.node.res.statusMessage;
384
+ }
254
385
  function defaultContentType(event, type) {
255
386
  if (type && !event.node.res.getHeader("content-type")) {
256
387
  event.node.res.setHeader("content-type", type);
@@ -320,7 +451,9 @@ function writeEarlyHints(event, hints, cb = noop) {
320
451
  if (hints.link) {
321
452
  hints.link = Array.isArray(hints.link) ? hints.link : hints.link.split(",");
322
453
  }
323
- const headers = Object.entries(hints).map((e) => [e[0].toLowerCase(), e[1]]);
454
+ const headers = Object.entries(hints).map(
455
+ (e) => [e[0].toLowerCase(), e[1]]
456
+ );
324
457
  if (headers.length === 0) {
325
458
  cb();
326
459
  return;
@@ -337,9 +470,17 @@ Link: ${hints.link.join(", ")}`;
337
470
  hint += `\r
338
471
  ${header}: ${value}`;
339
472
  }
340
- event.node.res.socket.write(`${hint}\r
473
+ if (event.node.res.socket) {
474
+ event.node.res.socket.write(
475
+ `${hint}\r
341
476
  \r
342
- `, "utf8", cb);
477
+ `,
478
+ "utf8",
479
+ cb
480
+ );
481
+ } else {
482
+ cb();
483
+ }
343
484
  }
344
485
 
345
486
  function parseCookies(event) {
@@ -402,7 +543,9 @@ async function proxyRequest(event, target, opts = {}) {
402
543
  async function sendProxy(event, target, opts = {}) {
403
544
  const _fetch = opts.fetch || globalThis.fetch;
404
545
  if (!_fetch) {
405
- throw new Error("fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js.");
546
+ throw new Error(
547
+ "fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js."
548
+ );
406
549
  }
407
550
  const response = await _fetch(target, {
408
551
  headers: opts.headers,
@@ -442,11 +585,15 @@ class H3Headers {
442
585
  if (!init) {
443
586
  this._headers = {};
444
587
  } else if (Array.isArray(init)) {
445
- this._headers = Object.fromEntries(init.map(([key, value]) => [key.toLowerCase(), value]));
588
+ this._headers = Object.fromEntries(
589
+ init.map(([key, value]) => [key.toLowerCase(), value])
590
+ );
446
591
  } else if (init && "append" in init) {
447
592
  this._headers = Object.fromEntries(init.entries());
448
593
  } else {
449
- this._headers = Object.fromEntries(Object.entries(init).map(([key, value]) => [key.toLowerCase(), value]));
594
+ this._headers = Object.fromEntries(
595
+ Object.entries(init).map(([key, value]) => [key.toLowerCase(), value])
596
+ );
450
597
  }
451
598
  }
452
599
  [Symbol.iterator]() {
@@ -486,6 +633,7 @@ class H3Headers {
486
633
 
487
634
  class H3Response {
488
635
  constructor(body = null, init = {}) {
636
+ // TODO: yet to implement
489
637
  this.body = null;
490
638
  this.type = "default";
491
639
  this.bodyUsed = false;
@@ -530,12 +678,15 @@ class H3Event {
530
678
  get path() {
531
679
  return this.req.url;
532
680
  }
681
+ /** @deprecated Please use `event.node.req` instead. **/
533
682
  get req() {
534
683
  return this.node.req;
535
684
  }
685
+ /** @deprecated Please use `event.node.res` instead. **/
536
686
  get res() {
537
687
  return this.node.res;
538
688
  }
689
+ // Implementation of FetchEvent
539
690
  respondWith(r) {
540
691
  Promise.resolve(r).then((_response) => {
541
692
  if (this.res.writableEnded) {
@@ -617,7 +768,10 @@ function defineLazyEventHandler(factory) {
617
768
  _promise = Promise.resolve(factory()).then((r) => {
618
769
  const handler = r.default || r;
619
770
  if (typeof handler !== "function") {
620
- throw new TypeError("Invalid lazy handler result. It should be a function:", handler);
771
+ throw new TypeError(
772
+ "Invalid lazy handler result. It should be a function:",
773
+ handler
774
+ );
621
775
  }
622
776
  _resolved = toEventHandler(r.default || r);
623
777
  return _resolved;
@@ -638,6 +792,7 @@ function createApp(options = {}) {
638
792
  const stack = [];
639
793
  const handler = createAppEventHandler(stack, options);
640
794
  const app = {
795
+ // @ts-ignore
641
796
  use: (arg1, arg2, arg3) => use(app, arg1, arg2, arg3),
642
797
  handler,
643
798
  stack,
@@ -655,9 +810,13 @@ function use(app, arg1, arg2, arg3) {
655
810
  use(app, arg1, i, arg3);
656
811
  }
657
812
  } else if (typeof arg1 === "string") {
658
- app.stack.push(normalizeLayer({ ...arg3, route: arg1, handler: arg2 }));
813
+ app.stack.push(
814
+ normalizeLayer({ ...arg3, route: arg1, handler: arg2 })
815
+ );
659
816
  } else if (typeof arg1 === "function") {
660
- app.stack.push(normalizeLayer({ ...arg2, route: "/", handler: arg1 }));
817
+ app.stack.push(
818
+ normalizeLayer({ ...arg2, route: "/", handler: arg1 })
819
+ );
661
820
  } else {
662
821
  app.stack.push(normalizeLayer({ ...arg1 }));
663
822
  }
@@ -698,7 +857,11 @@ function createAppEventHandler(stack, options) {
698
857
  } else if (val instanceof Error) {
699
858
  throw createError(val);
700
859
  } else {
701
- return send(event, JSON.stringify(val, void 0, spacing), MIMES.json);
860
+ return send(
861
+ event,
862
+ JSON.stringify(val, void 0, spacing),
863
+ MIMES.json
864
+ );
702
865
  }
703
866
  }
704
867
  }
@@ -734,10 +897,17 @@ function fromNodeMiddleware(handler) {
734
897
  return handler;
735
898
  }
736
899
  if (typeof handler !== "function") {
737
- throw new TypeError("Invalid handler. It should be a function:", handler);
900
+ throw new TypeError(
901
+ "Invalid handler. It should be a function:",
902
+ handler
903
+ );
738
904
  }
739
905
  return eventHandler((event) => {
740
- return callNodeListener(handler, event.node.req, event.node.res);
906
+ return callNodeListener(
907
+ handler,
908
+ event.node.req,
909
+ event.node.res
910
+ );
741
911
  });
742
912
  }
743
913
  function toNodeListener(app) {
@@ -791,7 +961,17 @@ function callNodeListener(handler, req, res) {
791
961
  });
792
962
  }
793
963
 
794
- const RouterMethods = ["connect", "delete", "get", "head", "options", "post", "put", "trace", "patch"];
964
+ const RouterMethods = [
965
+ "connect",
966
+ "delete",
967
+ "get",
968
+ "head",
969
+ "options",
970
+ "post",
971
+ "put",
972
+ "trace",
973
+ "patch"
974
+ ];
795
975
  function createRouter(opts = {}) {
796
976
  const _router = radix3.createRouter({});
797
977
  const routes = {};
@@ -822,8 +1002,8 @@ function createRouter(opts = {}) {
822
1002
  path = path.slice(0, Math.max(0, qIndex));
823
1003
  }
824
1004
  const matched = _router.lookup(path);
825
- if (!matched) {
826
- if (opts.preemtive) {
1005
+ if (!matched || !matched.handlers) {
1006
+ if (opts.preemptive || opts.preemtive) {
827
1007
  throw createError({
828
1008
  statusCode: 404,
829
1009
  name: "Not Found",
@@ -883,6 +1063,8 @@ exports.getRequestHeader = getRequestHeader;
883
1063
  exports.getRequestHeaders = getRequestHeaders;
884
1064
  exports.getResponseHeader = getResponseHeader;
885
1065
  exports.getResponseHeaders = getResponseHeaders;
1066
+ exports.getResponseStatus = getResponseStatus;
1067
+ exports.getResponseStatusText = getResponseStatusText;
886
1068
  exports.getRouterParam = getRouterParam;
887
1069
  exports.getRouterParams = getRouterParams;
888
1070
  exports.handleCacheHeaders = handleCacheHeaders;
@@ -896,9 +1078,11 @@ exports.parseCookies = parseCookies;
896
1078
  exports.promisifyNodeListener = promisifyNodeListener;
897
1079
  exports.proxyRequest = proxyRequest;
898
1080
  exports.readBody = readBody;
1081
+ exports.readMultipartFormData = readMultipartFormData;
899
1082
  exports.readRawBody = readRawBody;
900
1083
  exports.send = send;
901
1084
  exports.sendError = sendError;
1085
+ exports.sendNoContent = sendNoContent;
902
1086
  exports.sendProxy = sendProxy;
903
1087
  exports.sendRedirect = sendRedirect;
904
1088
  exports.sendStream = sendStream;
@@ -907,6 +1091,7 @@ exports.setHeader = setHeader;
907
1091
  exports.setHeaders = setHeaders;
908
1092
  exports.setResponseHeader = setResponseHeader;
909
1093
  exports.setResponseHeaders = setResponseHeaders;
1094
+ exports.setResponseStatus = setResponseStatus;
910
1095
  exports.toEventHandler = toEventHandler;
911
1096
  exports.toNodeListener = toNodeListener;
912
1097
  exports.use = use;
package/dist/index.d.ts CHANGED
@@ -3,23 +3,23 @@ export { IncomingMessage as NodeIncomingMessage, ServerResponse as NodeServerRes
3
3
  import { CookieSerializeOptions } from 'cookie-es';
4
4
  import * as ufo from 'ufo';
5
5
 
6
- declare type HTTPMethod = "GET" | "HEAD" | "PATCH" | "POST" | "PUT" | "DELETE" | "CONNECT" | "OPTIONS" | "TRACE";
7
- declare type Encoding = false | "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex";
6
+ type HTTPMethod = "GET" | "HEAD" | "PATCH" | "POST" | "PUT" | "DELETE" | "CONNECT" | "OPTIONS" | "TRACE";
7
+ type Encoding = false | "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex";
8
8
  interface H3EventContext extends Record<string, any> {
9
9
  }
10
- declare type EventHandlerResponse<T = any> = T | Promise<T>;
10
+ type EventHandlerResponse<T = any> = T | Promise<T>;
11
11
  interface EventHandler<T = any> {
12
- "__is_handler__"?: true;
12
+ __is_handler__?: true;
13
13
  (event: H3Event): EventHandlerResponse<T>;
14
14
  }
15
- declare type LazyEventHandler = () => EventHandler | Promise<EventHandler>;
16
- declare type RequestHeaders = {
15
+ type LazyEventHandler = () => EventHandler | Promise<EventHandler>;
16
+ type RequestHeaders = {
17
17
  [name: string]: string | undefined;
18
18
  };
19
19
 
20
- declare type NodeListener = (req: IncomingMessage, res: ServerResponse) => void;
21
- declare type NodePromisifiedHandler = (req: IncomingMessage, res: ServerResponse) => Promise<any>;
22
- declare type NodeMiddleware = (req: IncomingMessage, res: ServerResponse, next: (err?: Error) => any) => any;
20
+ type NodeListener = (req: IncomingMessage, res: ServerResponse) => void;
21
+ type NodePromisifiedHandler = (req: IncomingMessage, res: ServerResponse) => Promise<any>;
22
+ type NodeMiddleware = (req: IncomingMessage, res: ServerResponse, next: (err?: Error) => any) => any;
23
23
  declare const defineNodeListener: (handler: NodeListener) => NodeListener;
24
24
  declare const defineNodeMiddleware: (middleware: NodeMiddleware) => NodeMiddleware;
25
25
  declare function fromNodeMiddleware(handler: NodeListener | NodeMiddleware): EventHandler;
@@ -72,7 +72,7 @@ declare class H3Event implements Pick<FetchEvent, "respondWith"> {
72
72
  context: H3EventContext;
73
73
  constructor(req: IncomingMessage, res: ServerResponse);
74
74
  get path(): string | undefined;
75
- /** @deprecated Please use `event.node.res` instead. **/
75
+ /** @deprecated Please use `event.node.req` instead. **/
76
76
  get req(): IncomingMessage;
77
77
  /** @deprecated Please use `event.node.res` instead. **/
78
78
  get res(): ServerResponse<IncomingMessage>;
@@ -97,15 +97,15 @@ interface Layer {
97
97
  match?: Matcher;
98
98
  handler: EventHandler;
99
99
  }
100
- declare type Stack = Layer[];
100
+ type Stack = Layer[];
101
101
  interface InputLayer {
102
102
  route?: string;
103
103
  match?: Matcher;
104
104
  handler: EventHandler;
105
105
  lazy?: boolean;
106
106
  }
107
- declare type InputStack = InputLayer[];
108
- declare type Matcher = (url: string, event?: H3Event) => boolean;
107
+ type InputStack = InputLayer[];
108
+ type Matcher = (url: string, event?: H3Event) => boolean;
109
109
  interface AppUse {
110
110
  (route: string | string[], handler: EventHandler | EventHandler[], options?: Partial<InputLayer>): App;
111
111
  (handler: EventHandler | EventHandler[], options?: Partial<InputLayer>): App;
@@ -139,7 +139,7 @@ declare function createAppEventHandler(stack: Stack, options: AppOptions): Event
139
139
  */
140
140
  declare class H3Error extends Error {
141
141
  static __h3_error__: boolean;
142
- toJSON(): Pick<H3Error, "statusCode" | "statusMessage" | "data" | "message">;
142
+ toJSON(): Pick<H3Error, "data" | "statusCode" | "statusMessage" | "message">;
143
143
  statusCode: number;
144
144
  fatal: boolean;
145
145
  unhandled: boolean;
@@ -152,10 +152,10 @@ declare class H3Error extends Error {
152
152
  * @param input {Partial<H3Error>}
153
153
  * @return {H3Error} An instance of the H3Error
154
154
  */
155
- declare function createError(input: string | Partial<H3Error> & {
155
+ declare function createError(input: string | (Partial<H3Error> & {
156
156
  status?: number;
157
157
  statusText?: string;
158
- }): H3Error;
158
+ })): H3Error;
159
159
  /**
160
160
  * Receive an error and return the corresponding response.<br>
161
161
  * H3 internally uses this function to handle unhandled errors.<br>
@@ -171,6 +171,13 @@ declare function isError(input: any): input is H3Error;
171
171
 
172
172
  declare function useBase(base: string, handler: EventHandler): EventHandler;
173
173
 
174
+ interface MultiPartData {
175
+ data: Buffer;
176
+ name?: string;
177
+ filename?: string;
178
+ type?: string;
179
+ }
180
+
174
181
  /**
175
182
  * Reads body of the request and returns encoded raw string (default) or `Buffer` if encoding if falsy.
176
183
  * @param event {H3Event} H3 event or req passed by h3 handler
@@ -178,7 +185,7 @@ declare function useBase(base: string, handler: EventHandler): EventHandler;
178
185
  *
179
186
  * @return {String|Buffer} Encoded raw string or raw Buffer of the body
180
187
  */
181
- declare function readRawBody(event: H3Event, encoding?: Encoding): Encoding extends false ? Buffer : Promise<string | Buffer | undefined>;
188
+ declare function readRawBody<E extends Encoding = "utf8">(event: H3Event, encoding?: E): E extends false ? Promise<Buffer | undefined> : Promise<string | undefined>;
182
189
  /**
183
190
  * Reads request body and try to safely parse using [destr](https://github.com/unjs/destr)
184
191
  * @param event {H3Event} H3 event or req passed by h3 handler
@@ -191,6 +198,7 @@ declare function readRawBody(event: H3Event, encoding?: Encoding): Encoding exte
191
198
  * ```
192
199
  */
193
200
  declare function readBody<T = any>(event: H3Event): Promise<T>;
201
+ declare function readMultipartFormData(event: H3Event): Promise<MultiPartData[] | undefined>;
194
202
 
195
203
  interface CacheConditions {
196
204
  modifiedTime?: string | Date;
@@ -272,6 +280,17 @@ declare function getRequestHeader(event: H3Event, name: string): RequestHeaders[
272
280
  declare const getHeader: typeof getRequestHeader;
273
281
 
274
282
  declare function send(event: H3Event, data?: any, type?: string): Promise<void>;
283
+ /**
284
+ * Respond with an empty payload.<br>
285
+ * Note that calling this function will close the connection and no other data can be sent to the client afterwards.
286
+ *
287
+ * @param event H3 event
288
+ * @param code status code to be send. By default, it is `204 No Content`.
289
+ */
290
+ declare function sendNoContent(event: H3Event, code?: number): void;
291
+ declare function setResponseStatus(event: H3Event, code: number, text?: string): void;
292
+ declare function getResponseStatus(event: H3Event): number;
293
+ declare function getResponseStatusText(event: H3Event): string;
275
294
  declare function defaultContentType(event: H3Event, type?: string): void;
276
295
  declare function sendRedirect(event: H3Event, location: string, code?: number): Promise<void>;
277
296
  declare function getResponseHeaders(event: H3Event): ReturnType<H3Event["res"]["getHeaders"]>;
@@ -288,17 +307,19 @@ declare function isStream(data: any): any;
288
307
  declare function sendStream(event: H3Event, data: any): Promise<void>;
289
308
  declare function writeEarlyHints(event: H3Event, hints: string | string[] | Record<string, string | string[]>, cb?: () => void): void;
290
309
 
291
- declare type RouterMethod = Lowercase<HTTPMethod>;
292
- declare type RouterUse = (path: string, handler: EventHandler, method?: RouterMethod | RouterMethod[]) => Router;
293
- declare type AddRouteShortcuts = Record<RouterMethod, RouterUse>;
310
+ type RouterMethod = Lowercase<HTTPMethod>;
311
+ type RouterUse = (path: string, handler: EventHandler, method?: RouterMethod | RouterMethod[]) => Router;
312
+ type AddRouteShortcuts = Record<RouterMethod, RouterUse>;
294
313
  interface Router extends AddRouteShortcuts {
295
314
  add: RouterUse;
296
315
  use: RouterUse;
297
316
  handler: EventHandler;
298
317
  }
299
318
  interface CreateRouterOptions {
319
+ /** @deprecated Please use `preemptive` instead. **/
300
320
  preemtive?: boolean;
321
+ preemptive?: boolean;
301
322
  }
302
323
  declare function createRouter(opts?: CreateRouterOptions): Router;
303
324
 
304
- export { AddRouteShortcuts, App, AppOptions, AppUse, CacheConditions, CreateRouterOptions, DynamicEventHandler, Encoding, EventHandler, EventHandlerResponse, H3Error, H3Event, H3EventContext, H3Headers, H3Response, HTTPMethod, InputLayer, InputStack, Layer, LazyEventHandler, MIMES, Matcher, NodeEventContext, NodeListener, NodeMiddleware, NodePromisifiedHandler, ProxyOptions, RequestHeaders, Router, RouterMethod, RouterUse, Stack, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getRouterParam, getRouterParams, handleCacheHeaders, isError, isEvent, isEventHandler, isMethod, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readRawBody, send, sendError, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, toEventHandler, toNodeListener, use, useBase, writeEarlyHints };
325
+ export { AddRouteShortcuts, App, AppOptions, AppUse, CacheConditions, CreateRouterOptions, DynamicEventHandler, Encoding, EventHandler, EventHandlerResponse, H3Error, H3Event, H3EventContext, H3Headers, H3Response, HTTPMethod, InputLayer, InputStack, Layer, LazyEventHandler, MIMES, Matcher, NodeEventContext, NodeListener, NodeMiddleware, NodePromisifiedHandler, ProxyOptions, RequestHeaders, Router, RouterMethod, RouterUse, Stack, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, handleCacheHeaders, isError, isEvent, isEventHandler, isMethod, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, toEventHandler, toNodeListener, use, useBase, writeEarlyHints };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { withoutTrailingSlash, withoutBase, getQuery as getQuery$1 } from 'ufo';
2
2
  import { createRouter as createRouter$1 } from 'radix3';
3
3
  import destr from 'destr';
4
- import { parse, serialize } from 'cookie-es';
4
+ import { parse as parse$1, serialize } from 'cookie-es';
5
5
 
6
6
  function useBase(base, handler) {
7
7
  base = withoutTrailingSlash(base);
@@ -15,6 +15,83 @@ function useBase(base, handler) {
15
15
  });
16
16
  }
17
17
 
18
+ function parse(multipartBodyBuffer, boundary) {
19
+ let lastline = "";
20
+ let state = 0 /* INIT */;
21
+ let buffer = [];
22
+ const allParts = [];
23
+ let currentPartHeaders = [];
24
+ for (let i = 0; i < multipartBodyBuffer.length; i++) {
25
+ const prevByte = i > 0 ? multipartBodyBuffer[i - 1] : null;
26
+ const currByte = multipartBodyBuffer[i];
27
+ const newLineChar = currByte === 10 || currByte === 13;
28
+ if (!newLineChar) {
29
+ lastline += String.fromCodePoint(currByte);
30
+ }
31
+ const newLineDetected = currByte === 10 && prevByte === 13;
32
+ if (0 /* INIT */ === state && newLineDetected) {
33
+ if ("--" + boundary === lastline) {
34
+ state = 1 /* READING_HEADERS */;
35
+ }
36
+ lastline = "";
37
+ } else if (1 /* READING_HEADERS */ === state && newLineDetected) {
38
+ if (lastline.length > 0) {
39
+ const i2 = lastline.indexOf(":");
40
+ if (i2 > 0) {
41
+ const name = lastline.slice(0, i2).toLowerCase();
42
+ const value = lastline.slice(i2 + 1).trim();
43
+ currentPartHeaders.push([name, value]);
44
+ }
45
+ } else {
46
+ state = 2 /* READING_DATA */;
47
+ buffer = [];
48
+ }
49
+ lastline = "";
50
+ } else if (2 /* READING_DATA */ === state) {
51
+ if (lastline.length > boundary.length + 4) {
52
+ lastline = "";
53
+ }
54
+ if ("--" + boundary === lastline) {
55
+ const j = buffer.length - lastline.length;
56
+ const part = buffer.slice(0, j - 1);
57
+ allParts.push(process(part, currentPartHeaders));
58
+ buffer = [];
59
+ currentPartHeaders = [];
60
+ lastline = "";
61
+ state = 3 /* READING_PART_SEPARATOR */;
62
+ } else {
63
+ buffer.push(currByte);
64
+ }
65
+ if (newLineDetected) {
66
+ lastline = "";
67
+ }
68
+ } else if (3 /* READING_PART_SEPARATOR */ === state && newLineDetected) {
69
+ state = 1 /* READING_HEADERS */;
70
+ }
71
+ }
72
+ return allParts;
73
+ }
74
+ function process(data, headers) {
75
+ const dataObj = {};
76
+ const contentDispositionHeader = headers.find((h) => h[0] === "content-disposition")?.[1] || "";
77
+ for (const i of contentDispositionHeader.split(";")) {
78
+ const s = i.split("=");
79
+ if (s.length !== 2) {
80
+ continue;
81
+ }
82
+ const key = (s[0] || "").trim();
83
+ if (key === "name" || key === "filename") {
84
+ dataObj[key] = (s[1] || "").trim().replace(/"/g, "");
85
+ }
86
+ }
87
+ const contentType = headers.find((h) => h[0] === "content-type")?.[1] || "";
88
+ if (contentType) {
89
+ dataObj.type = contentType;
90
+ }
91
+ dataObj.data = Buffer.from(data);
92
+ return dataObj;
93
+ }
94
+
18
95
  class H3Error extends Error {
19
96
  constructor() {
20
97
  super(...arguments);
@@ -45,12 +122,18 @@ function createError(input) {
45
122
  if (isError(input)) {
46
123
  return input;
47
124
  }
48
- const err = new H3Error(input.message ?? input.statusMessage, input.cause ? { cause: input.cause } : void 0);
125
+ const err = new H3Error(
126
+ input.message ?? input.statusMessage,
127
+ // @ts-ignore
128
+ input.cause ? { cause: input.cause } : void 0
129
+ );
49
130
  if ("stack" in input) {
50
131
  try {
51
- Object.defineProperty(err, "stack", { get() {
52
- return input.stack;
53
- } });
132
+ Object.defineProperty(err, "stack", {
133
+ get() {
134
+ return input.stack;
135
+ }
136
+ });
54
137
  } catch {
55
138
  try {
56
139
  err.stack = input.stack;
@@ -176,17 +259,20 @@ function readRawBody(event, encoding = "utf8") {
176
259
  if (!Number.parseInt(event.node.req.headers["content-length"] || "")) {
177
260
  return Promise.resolve(void 0);
178
261
  }
179
- const promise = event.node.req[RawBodySymbol] = new Promise((resolve, reject) => {
180
- const bodyData = [];
181
- event.node.req.on("error", (err) => {
182
- reject(err);
183
- }).on("data", (chunk) => {
184
- bodyData.push(chunk);
185
- }).on("end", () => {
186
- resolve(Buffer.concat(bodyData));
187
- });
188
- });
189
- return encoding ? promise.then((buff) => buff.toString(encoding)) : promise;
262
+ const promise = event.node.req[RawBodySymbol] = new Promise(
263
+ (resolve, reject) => {
264
+ const bodyData = [];
265
+ event.node.req.on("error", (err) => {
266
+ reject(err);
267
+ }).on("data", (chunk) => {
268
+ bodyData.push(chunk);
269
+ }).on("end", () => {
270
+ resolve(Buffer.concat(bodyData));
271
+ });
272
+ }
273
+ );
274
+ const result = encoding ? promise.then((buff) => buff.toString(encoding)) : promise;
275
+ return result;
190
276
  }
191
277
  async function readBody(event) {
192
278
  if (ParsedBodySymbol in event.node.req) {
@@ -194,13 +280,39 @@ async function readBody(event) {
194
280
  }
195
281
  const body = await readRawBody(event);
196
282
  if (event.node.req.headers["content-type"] === "application/x-www-form-urlencoded") {
197
- const parsedForm = Object.fromEntries(new URLSearchParams(body));
283
+ const form = new URLSearchParams(body);
284
+ const parsedForm = /* @__PURE__ */ Object.create(null);
285
+ for (const [key, value] of form.entries()) {
286
+ if (key in parsedForm) {
287
+ if (!Array.isArray(parsedForm[key])) {
288
+ parsedForm[key] = [parsedForm[key]];
289
+ }
290
+ parsedForm[key].push(value);
291
+ } else {
292
+ parsedForm[key] = value;
293
+ }
294
+ }
198
295
  return parsedForm;
199
296
  }
200
297
  const json = destr(body);
201
298
  event.node.req[ParsedBodySymbol] = json;
202
299
  return json;
203
300
  }
301
+ async function readMultipartFormData(event) {
302
+ const contentType = getRequestHeader(event, "content-type");
303
+ if (!contentType || !contentType.startsWith("multipart/form-data")) {
304
+ return;
305
+ }
306
+ const boundary = contentType.match(/boundary=([^;]*)(;|$)/i)?.[1];
307
+ if (!boundary) {
308
+ return;
309
+ }
310
+ const body = await readRawBody(event, false);
311
+ if (!body) {
312
+ return;
313
+ }
314
+ return parse(body, boundary);
315
+ }
204
316
 
205
317
  function handleCacheHeaders(event, opts) {
206
318
  const cacheControls = ["public", ...opts.cacheControls || []];
@@ -249,6 +361,25 @@ function send(event, data, type) {
249
361
  });
250
362
  });
251
363
  }
364
+ function sendNoContent(event, code = 204) {
365
+ event.node.res.statusCode = code;
366
+ if (event.node.res.statusCode === 204) {
367
+ event.node.res.removeHeader("content-length");
368
+ }
369
+ event.node.res.end();
370
+ }
371
+ function setResponseStatus(event, code, text) {
372
+ event.node.res.statusCode = code;
373
+ if (text) {
374
+ event.node.res.statusMessage = text;
375
+ }
376
+ }
377
+ function getResponseStatus(event) {
378
+ return event.node.res.statusCode;
379
+ }
380
+ function getResponseStatusText(event) {
381
+ return event.node.res.statusMessage;
382
+ }
252
383
  function defaultContentType(event, type) {
253
384
  if (type && !event.node.res.getHeader("content-type")) {
254
385
  event.node.res.setHeader("content-type", type);
@@ -318,7 +449,9 @@ function writeEarlyHints(event, hints, cb = noop) {
318
449
  if (hints.link) {
319
450
  hints.link = Array.isArray(hints.link) ? hints.link : hints.link.split(",");
320
451
  }
321
- const headers = Object.entries(hints).map((e) => [e[0].toLowerCase(), e[1]]);
452
+ const headers = Object.entries(hints).map(
453
+ (e) => [e[0].toLowerCase(), e[1]]
454
+ );
322
455
  if (headers.length === 0) {
323
456
  cb();
324
457
  return;
@@ -335,13 +468,21 @@ Link: ${hints.link.join(", ")}`;
335
468
  hint += `\r
336
469
  ${header}: ${value}`;
337
470
  }
338
- event.node.res.socket.write(`${hint}\r
471
+ if (event.node.res.socket) {
472
+ event.node.res.socket.write(
473
+ `${hint}\r
339
474
  \r
340
- `, "utf8", cb);
475
+ `,
476
+ "utf8",
477
+ cb
478
+ );
479
+ } else {
480
+ cb();
481
+ }
341
482
  }
342
483
 
343
484
  function parseCookies(event) {
344
- return parse(event.node.req.headers.cookie || "");
485
+ return parse$1(event.node.req.headers.cookie || "");
345
486
  }
346
487
  function getCookie(event, name) {
347
488
  return parseCookies(event)[name];
@@ -400,7 +541,9 @@ async function proxyRequest(event, target, opts = {}) {
400
541
  async function sendProxy(event, target, opts = {}) {
401
542
  const _fetch = opts.fetch || globalThis.fetch;
402
543
  if (!_fetch) {
403
- throw new Error("fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js.");
544
+ throw new Error(
545
+ "fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js."
546
+ );
404
547
  }
405
548
  const response = await _fetch(target, {
406
549
  headers: opts.headers,
@@ -440,11 +583,15 @@ class H3Headers {
440
583
  if (!init) {
441
584
  this._headers = {};
442
585
  } else if (Array.isArray(init)) {
443
- this._headers = Object.fromEntries(init.map(([key, value]) => [key.toLowerCase(), value]));
586
+ this._headers = Object.fromEntries(
587
+ init.map(([key, value]) => [key.toLowerCase(), value])
588
+ );
444
589
  } else if (init && "append" in init) {
445
590
  this._headers = Object.fromEntries(init.entries());
446
591
  } else {
447
- this._headers = Object.fromEntries(Object.entries(init).map(([key, value]) => [key.toLowerCase(), value]));
592
+ this._headers = Object.fromEntries(
593
+ Object.entries(init).map(([key, value]) => [key.toLowerCase(), value])
594
+ );
448
595
  }
449
596
  }
450
597
  [Symbol.iterator]() {
@@ -484,6 +631,7 @@ class H3Headers {
484
631
 
485
632
  class H3Response {
486
633
  constructor(body = null, init = {}) {
634
+ // TODO: yet to implement
487
635
  this.body = null;
488
636
  this.type = "default";
489
637
  this.bodyUsed = false;
@@ -528,12 +676,15 @@ class H3Event {
528
676
  get path() {
529
677
  return this.req.url;
530
678
  }
679
+ /** @deprecated Please use `event.node.req` instead. **/
531
680
  get req() {
532
681
  return this.node.req;
533
682
  }
683
+ /** @deprecated Please use `event.node.res` instead. **/
534
684
  get res() {
535
685
  return this.node.res;
536
686
  }
687
+ // Implementation of FetchEvent
537
688
  respondWith(r) {
538
689
  Promise.resolve(r).then((_response) => {
539
690
  if (this.res.writableEnded) {
@@ -615,7 +766,10 @@ function defineLazyEventHandler(factory) {
615
766
  _promise = Promise.resolve(factory()).then((r) => {
616
767
  const handler = r.default || r;
617
768
  if (typeof handler !== "function") {
618
- throw new TypeError("Invalid lazy handler result. It should be a function:", handler);
769
+ throw new TypeError(
770
+ "Invalid lazy handler result. It should be a function:",
771
+ handler
772
+ );
619
773
  }
620
774
  _resolved = toEventHandler(r.default || r);
621
775
  return _resolved;
@@ -636,6 +790,7 @@ function createApp(options = {}) {
636
790
  const stack = [];
637
791
  const handler = createAppEventHandler(stack, options);
638
792
  const app = {
793
+ // @ts-ignore
639
794
  use: (arg1, arg2, arg3) => use(app, arg1, arg2, arg3),
640
795
  handler,
641
796
  stack,
@@ -653,9 +808,13 @@ function use(app, arg1, arg2, arg3) {
653
808
  use(app, arg1, i, arg3);
654
809
  }
655
810
  } else if (typeof arg1 === "string") {
656
- app.stack.push(normalizeLayer({ ...arg3, route: arg1, handler: arg2 }));
811
+ app.stack.push(
812
+ normalizeLayer({ ...arg3, route: arg1, handler: arg2 })
813
+ );
657
814
  } else if (typeof arg1 === "function") {
658
- app.stack.push(normalizeLayer({ ...arg2, route: "/", handler: arg1 }));
815
+ app.stack.push(
816
+ normalizeLayer({ ...arg2, route: "/", handler: arg1 })
817
+ );
659
818
  } else {
660
819
  app.stack.push(normalizeLayer({ ...arg1 }));
661
820
  }
@@ -696,7 +855,11 @@ function createAppEventHandler(stack, options) {
696
855
  } else if (val instanceof Error) {
697
856
  throw createError(val);
698
857
  } else {
699
- return send(event, JSON.stringify(val, void 0, spacing), MIMES.json);
858
+ return send(
859
+ event,
860
+ JSON.stringify(val, void 0, spacing),
861
+ MIMES.json
862
+ );
700
863
  }
701
864
  }
702
865
  }
@@ -732,10 +895,17 @@ function fromNodeMiddleware(handler) {
732
895
  return handler;
733
896
  }
734
897
  if (typeof handler !== "function") {
735
- throw new TypeError("Invalid handler. It should be a function:", handler);
898
+ throw new TypeError(
899
+ "Invalid handler. It should be a function:",
900
+ handler
901
+ );
736
902
  }
737
903
  return eventHandler((event) => {
738
- return callNodeListener(handler, event.node.req, event.node.res);
904
+ return callNodeListener(
905
+ handler,
906
+ event.node.req,
907
+ event.node.res
908
+ );
739
909
  });
740
910
  }
741
911
  function toNodeListener(app) {
@@ -789,7 +959,17 @@ function callNodeListener(handler, req, res) {
789
959
  });
790
960
  }
791
961
 
792
- const RouterMethods = ["connect", "delete", "get", "head", "options", "post", "put", "trace", "patch"];
962
+ const RouterMethods = [
963
+ "connect",
964
+ "delete",
965
+ "get",
966
+ "head",
967
+ "options",
968
+ "post",
969
+ "put",
970
+ "trace",
971
+ "patch"
972
+ ];
793
973
  function createRouter(opts = {}) {
794
974
  const _router = createRouter$1({});
795
975
  const routes = {};
@@ -820,8 +1000,8 @@ function createRouter(opts = {}) {
820
1000
  path = path.slice(0, Math.max(0, qIndex));
821
1001
  }
822
1002
  const matched = _router.lookup(path);
823
- if (!matched) {
824
- if (opts.preemtive) {
1003
+ if (!matched || !matched.handlers) {
1004
+ if (opts.preemptive || opts.preemtive) {
825
1005
  throw createError({
826
1006
  statusCode: 404,
827
1007
  name: "Not Found",
@@ -847,4 +1027,4 @@ function createRouter(opts = {}) {
847
1027
  return router;
848
1028
  }
849
1029
 
850
- export { H3Error, H3Event, H3Headers, H3Response, MIMES, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getRouterParam, getRouterParams, handleCacheHeaders, isError, isEvent, isEventHandler, isMethod, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readRawBody, send, sendError, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, toEventHandler, toNodeListener, use, useBase, writeEarlyHints };
1030
+ export { H3Error, H3Event, H3Headers, H3Response, MIMES, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, handleCacheHeaders, isError, isEvent, isEventHandler, isMethod, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, toEventHandler, toNodeListener, use, useBase, writeEarlyHints };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "h3",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Tiny JavaScript Server",
5
5
  "repository": "unjs/h3",
6
6
  "license": "MIT",
@@ -19,41 +19,42 @@
19
19
  "files": [
20
20
  "dist"
21
21
  ],
22
- "scripts": {
23
- "build": "unbuild",
24
- "dev": "vitest",
25
- "lint": "eslint --ext ts,mjs,cjs .",
26
- "play": "jiti ./playground/index.ts",
27
- "profile": "0x -o -D .profile -P 'autocannon -c 100 -p 10 -d 40 http://localhost:$PORT' ./playground/server.cjs",
28
- "release": "pnpm test && pnpm build && changelogen --release && pnpm publish && git push --follow-tags",
29
- "test": "pnpm lint && vitest run --coverage"
30
- },
31
22
  "dependencies": {
32
23
  "cookie-es": "^0.5.0",
33
- "destr": "^1.2.1",
24
+ "destr": "^1.2.2",
34
25
  "radix3": "^1.0.0",
35
- "ufo": "^1.0.0"
26
+ "ufo": "^1.0.1"
36
27
  },
37
28
  "devDependencies": {
38
29
  "0x": "^5.4.1",
39
- "@types/express": "^4.17.14",
40
- "@types/node": "^18.11.9",
30
+ "@types/express": "^4.17.16",
31
+ "@types/node": "^18.11.18",
41
32
  "@types/supertest": "^2.0.12",
42
- "@vitest/coverage-c8": "^0.25.2",
33
+ "@vitest/coverage-c8": "^0.28.2",
43
34
  "autocannon": "^7.10.0",
44
- "changelogen": "^0.4.0",
35
+ "changelogen": "^0.4.1",
45
36
  "connect": "^3.7.0",
46
- "eslint": "^8.27.0",
47
- "eslint-config-unjs": "^0.0.2",
37
+ "eslint": "^8.32.0",
38
+ "eslint-config-unjs": "^0.1.0",
48
39
  "express": "^4.18.2",
49
40
  "get-port": "^6.1.2",
50
- "jiti": "^1.16.0",
51
- "listhen": "^1.0.0",
41
+ "jiti": "^1.16.2",
42
+ "listhen": "^1.0.2",
52
43
  "node-fetch-native": "^1.0.1",
53
- "supertest": "^6.3.1",
54
- "typescript": "^4.8.4",
55
- "unbuild": "^0.9.4",
56
- "vitest": "^0.25.2"
44
+ "prettier": "^2.8.3",
45
+ "supertest": "^6.3.3",
46
+ "typescript": "^4.9.4",
47
+ "unbuild": "^1.1.1",
48
+ "vitest": "^0.28.2"
57
49
  },
58
- "packageManager": "pnpm@7.16.0"
59
- }
50
+ "packageManager": "pnpm@7.26.0",
51
+ "scripts": {
52
+ "build": "unbuild",
53
+ "dev": "vitest",
54
+ "lint": "eslint --ext ts,mjs,cjs . && prettier -c src test playground",
55
+ "play": "jiti ./playground/index.ts",
56
+ "profile": "0x -o -D .profile -P 'autocannon -c 100 -p 10 -d 40 http://localhost:$PORT' ./playground/server.cjs",
57
+ "release": "pnpm test && pnpm build && changelogen --release && pnpm publish && git push --follow-tags",
58
+ "test": "pnpm lint && vitest run --coverage"
59
+ }
60
+ }