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 +55 -31
- package/dist/index.cjs +216 -31
- package/dist/index.d.ts +42 -21
- package/dist/index.mjs +214 -34
- package/package.json +27 -26
package/README.md
CHANGED
|
@@ -39,28 +39,32 @@ pnpm add h3
|
|
|
39
39
|
## Usage
|
|
40
40
|
|
|
41
41
|
```ts
|
|
42
|
-
import { createServer } from
|
|
43
|
-
import { createApp, eventHandler, toNodeListener } from
|
|
42
|
+
import { createServer } from "node:http";
|
|
43
|
+
import { createApp, eventHandler, toNodeListener } from "h3";
|
|
44
44
|
|
|
45
|
-
const app = createApp()
|
|
46
|
-
app.use(
|
|
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
|
-
<
|
|
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
|
|
56
|
-
import { listen } from
|
|
57
|
+
import { createApp, eventHandler, toNodeListener } from "h3";
|
|
58
|
+
import { listen } from "listhen";
|
|
57
59
|
|
|
58
|
-
const app = createApp()
|
|
59
|
-
app.use(
|
|
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
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
- `
|
|
117
|
-
- `
|
|
118
|
-
- `
|
|
119
|
-
- `
|
|
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
|
-
- `
|
|
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
|
-
- `
|
|
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
|
-
|
|
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
|
-
|
|
165
|
+
PRs are welcome to add your packages.
|
|
147
166
|
|
|
148
|
-
-
|
|
149
|
-
- `
|
|
150
|
-
- `
|
|
151
|
-
-
|
|
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(
|
|
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", {
|
|
54
|
-
|
|
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(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
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(
|
|
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
|
|
473
|
+
if (event.node.res.socket) {
|
|
474
|
+
event.node.res.socket.write(
|
|
475
|
+
`${hint}\r
|
|
341
476
|
\r
|
|
342
|
-
`,
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
813
|
+
app.stack.push(
|
|
814
|
+
normalizeLayer({ ...arg3, route: arg1, handler: arg2 })
|
|
815
|
+
);
|
|
659
816
|
} else if (typeof arg1 === "function") {
|
|
660
|
-
app.stack.push(
|
|
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(
|
|
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(
|
|
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(
|
|
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 = [
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
10
|
+
type EventHandlerResponse<T = any> = T | Promise<T>;
|
|
11
11
|
interface EventHandler<T = any> {
|
|
12
|
-
|
|
12
|
+
__is_handler__?: true;
|
|
13
13
|
(event: H3Event): EventHandlerResponse<T>;
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
type LazyEventHandler = () => EventHandler | Promise<EventHandler>;
|
|
16
|
+
type RequestHeaders = {
|
|
17
17
|
[name: string]: string | undefined;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
108
|
-
|
|
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, "
|
|
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?:
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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(
|
|
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", {
|
|
52
|
-
|
|
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(
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
|
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(
|
|
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
|
|
471
|
+
if (event.node.res.socket) {
|
|
472
|
+
event.node.res.socket.write(
|
|
473
|
+
`${hint}\r
|
|
339
474
|
\r
|
|
340
|
-
`,
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
811
|
+
app.stack.push(
|
|
812
|
+
normalizeLayer({ ...arg3, route: arg1, handler: arg2 })
|
|
813
|
+
);
|
|
657
814
|
} else if (typeof arg1 === "function") {
|
|
658
|
-
app.stack.push(
|
|
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(
|
|
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(
|
|
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(
|
|
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 = [
|
|
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
|
|
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.
|
|
24
|
+
"destr": "^1.2.2",
|
|
34
25
|
"radix3": "^1.0.0",
|
|
35
|
-
"ufo": "^1.0.
|
|
26
|
+
"ufo": "^1.0.1"
|
|
36
27
|
},
|
|
37
28
|
"devDependencies": {
|
|
38
29
|
"0x": "^5.4.1",
|
|
39
|
-
"@types/express": "^4.17.
|
|
40
|
-
"@types/node": "^18.11.
|
|
30
|
+
"@types/express": "^4.17.16",
|
|
31
|
+
"@types/node": "^18.11.18",
|
|
41
32
|
"@types/supertest": "^2.0.12",
|
|
42
|
-
"@vitest/coverage-c8": "^0.
|
|
33
|
+
"@vitest/coverage-c8": "^0.28.2",
|
|
43
34
|
"autocannon": "^7.10.0",
|
|
44
|
-
"changelogen": "^0.4.
|
|
35
|
+
"changelogen": "^0.4.1",
|
|
45
36
|
"connect": "^3.7.0",
|
|
46
|
-
"eslint": "^8.
|
|
47
|
-
"eslint-config-unjs": "^0.0
|
|
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.
|
|
51
|
-
"listhen": "^1.0.
|
|
41
|
+
"jiti": "^1.16.2",
|
|
42
|
+
"listhen": "^1.0.2",
|
|
52
43
|
"node-fetch-native": "^1.0.1",
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
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.
|
|
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
|
+
}
|