h3 1.0.0 → 1.0.2
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/LICENSE +21 -0
- package/README.md +151 -25
- package/dist/index.cjs +977 -0
- package/dist/index.d.ts +306 -0
- package/dist/index.mjs +913 -0
- package/package.json +56 -9
- package/LICENSE.txt +0 -13
- package/index.js +0 -53
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,977 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ufo = require('ufo');
|
|
4
|
+
const radix3 = require('radix3');
|
|
5
|
+
const destr = require('destr');
|
|
6
|
+
const cookieEs = require('cookie-es');
|
|
7
|
+
|
|
8
|
+
function useBase(base, handler) {
|
|
9
|
+
base = ufo.withoutTrailingSlash(base);
|
|
10
|
+
if (!base) {
|
|
11
|
+
return handler;
|
|
12
|
+
}
|
|
13
|
+
return eventHandler((event) => {
|
|
14
|
+
event.node.req.originalUrl = event.node.req.originalUrl || event.node.req.url || "/";
|
|
15
|
+
event.node.req.url = ufo.withoutBase(event.node.req.url || "/", base);
|
|
16
|
+
return handler(event);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class H3Error extends Error {
|
|
21
|
+
constructor() {
|
|
22
|
+
super(...arguments);
|
|
23
|
+
this.statusCode = 500;
|
|
24
|
+
this.fatal = false;
|
|
25
|
+
this.unhandled = false;
|
|
26
|
+
this.statusMessage = void 0;
|
|
27
|
+
}
|
|
28
|
+
toJSON() {
|
|
29
|
+
const obj = {
|
|
30
|
+
message: this.message,
|
|
31
|
+
statusCode: this.statusCode
|
|
32
|
+
};
|
|
33
|
+
if (this.statusMessage) {
|
|
34
|
+
obj.statusMessage = this.statusMessage;
|
|
35
|
+
}
|
|
36
|
+
if (this.data !== void 0) {
|
|
37
|
+
obj.data = this.data;
|
|
38
|
+
}
|
|
39
|
+
return obj;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
H3Error.__h3_error__ = true;
|
|
43
|
+
function createError(input) {
|
|
44
|
+
if (typeof input === "string") {
|
|
45
|
+
return new H3Error(input);
|
|
46
|
+
}
|
|
47
|
+
if (isError(input)) {
|
|
48
|
+
return input;
|
|
49
|
+
}
|
|
50
|
+
const err = new H3Error(
|
|
51
|
+
input.message ?? input.statusMessage,
|
|
52
|
+
input.cause ? { cause: input.cause } : void 0
|
|
53
|
+
);
|
|
54
|
+
if ("stack" in input) {
|
|
55
|
+
try {
|
|
56
|
+
Object.defineProperty(err, "stack", {
|
|
57
|
+
get() {
|
|
58
|
+
return input.stack;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
} catch {
|
|
62
|
+
try {
|
|
63
|
+
err.stack = input.stack;
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (input.data) {
|
|
69
|
+
err.data = input.data;
|
|
70
|
+
}
|
|
71
|
+
if (input.statusCode) {
|
|
72
|
+
err.statusCode = input.statusCode;
|
|
73
|
+
} else if (input.status) {
|
|
74
|
+
err.statusCode = input.status;
|
|
75
|
+
}
|
|
76
|
+
if (input.statusMessage) {
|
|
77
|
+
err.statusMessage = input.statusMessage;
|
|
78
|
+
} else if (input.statusText) {
|
|
79
|
+
err.statusMessage = input.statusText;
|
|
80
|
+
}
|
|
81
|
+
if (input.fatal !== void 0) {
|
|
82
|
+
err.fatal = input.fatal;
|
|
83
|
+
}
|
|
84
|
+
if (input.unhandled !== void 0) {
|
|
85
|
+
err.unhandled = input.unhandled;
|
|
86
|
+
}
|
|
87
|
+
return err;
|
|
88
|
+
}
|
|
89
|
+
function sendError(event, error, debug) {
|
|
90
|
+
if (event.node.res.writableEnded) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const h3Error = isError(error) ? error : createError(error);
|
|
94
|
+
const responseBody = {
|
|
95
|
+
statusCode: h3Error.statusCode,
|
|
96
|
+
statusMessage: h3Error.statusMessage,
|
|
97
|
+
stack: [],
|
|
98
|
+
data: h3Error.data
|
|
99
|
+
};
|
|
100
|
+
if (debug) {
|
|
101
|
+
responseBody.stack = (h3Error.stack || "").split("\n").map((l) => l.trim());
|
|
102
|
+
}
|
|
103
|
+
if (event.node.res.writableEnded) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const _code = Number.parseInt(h3Error.statusCode);
|
|
107
|
+
if (_code) {
|
|
108
|
+
event.node.res.statusCode = _code;
|
|
109
|
+
}
|
|
110
|
+
if (h3Error.statusMessage) {
|
|
111
|
+
event.node.res.statusMessage = h3Error.statusMessage;
|
|
112
|
+
}
|
|
113
|
+
event.node.res.setHeader("content-type", MIMES.json);
|
|
114
|
+
event.node.res.end(JSON.stringify(responseBody, void 0, 2));
|
|
115
|
+
}
|
|
116
|
+
function isError(input) {
|
|
117
|
+
return input?.constructor?.__h3_error__ === true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getQuery(event) {
|
|
121
|
+
return ufo.getQuery(event.node.req.url || "");
|
|
122
|
+
}
|
|
123
|
+
function getRouterParams(event) {
|
|
124
|
+
return event.context.params || {};
|
|
125
|
+
}
|
|
126
|
+
function getRouterParam(event, name) {
|
|
127
|
+
const params = getRouterParams(event);
|
|
128
|
+
return params[name];
|
|
129
|
+
}
|
|
130
|
+
function getMethod(event, defaultMethod = "GET") {
|
|
131
|
+
return (event.node.req.method || defaultMethod).toUpperCase();
|
|
132
|
+
}
|
|
133
|
+
function isMethod(event, expected, allowHead) {
|
|
134
|
+
const method = getMethod(event);
|
|
135
|
+
if (allowHead && method === "HEAD") {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
if (typeof expected === "string") {
|
|
139
|
+
if (method === expected) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
} else if (expected.includes(method)) {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
function assertMethod(event, expected, allowHead) {
|
|
148
|
+
if (!isMethod(event, expected, allowHead)) {
|
|
149
|
+
throw createError({
|
|
150
|
+
statusCode: 405,
|
|
151
|
+
statusMessage: "HTTP method is not allowed."
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function getRequestHeaders(event) {
|
|
156
|
+
const _headers = {};
|
|
157
|
+
for (const key in event.node.req.headers) {
|
|
158
|
+
const val = event.node.req.headers[key];
|
|
159
|
+
_headers[key] = Array.isArray(val) ? val.filter(Boolean).join(", ") : val;
|
|
160
|
+
}
|
|
161
|
+
return _headers;
|
|
162
|
+
}
|
|
163
|
+
const getHeaders = getRequestHeaders;
|
|
164
|
+
function getRequestHeader(event, name) {
|
|
165
|
+
const headers = getRequestHeaders(event);
|
|
166
|
+
const value = headers[name.toLowerCase()];
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
const getHeader = getRequestHeader;
|
|
170
|
+
|
|
171
|
+
const RawBodySymbol = Symbol.for("h3RawBody");
|
|
172
|
+
const ParsedBodySymbol = Symbol.for("h3ParsedBody");
|
|
173
|
+
const PayloadMethods$1 = ["PATCH", "POST", "PUT", "DELETE"];
|
|
174
|
+
function readRawBody(event, encoding = "utf8") {
|
|
175
|
+
assertMethod(event, PayloadMethods$1);
|
|
176
|
+
if (RawBodySymbol in event.node.req) {
|
|
177
|
+
const promise2 = Promise.resolve(event.node.req[RawBodySymbol]);
|
|
178
|
+
return encoding ? promise2.then((buff) => buff.toString(encoding)) : promise2;
|
|
179
|
+
}
|
|
180
|
+
if ("body" in event.node.req) {
|
|
181
|
+
return Promise.resolve(event.node.req.body);
|
|
182
|
+
}
|
|
183
|
+
if (!Number.parseInt(event.node.req.headers["content-length"] || "")) {
|
|
184
|
+
return Promise.resolve(void 0);
|
|
185
|
+
}
|
|
186
|
+
const promise = event.node.req[RawBodySymbol] = new Promise(
|
|
187
|
+
(resolve, reject) => {
|
|
188
|
+
const bodyData = [];
|
|
189
|
+
event.node.req.on("error", (err) => {
|
|
190
|
+
reject(err);
|
|
191
|
+
}).on("data", (chunk) => {
|
|
192
|
+
bodyData.push(chunk);
|
|
193
|
+
}).on("end", () => {
|
|
194
|
+
resolve(Buffer.concat(bodyData));
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
const result = encoding ? promise.then((buff) => buff.toString(encoding)) : promise;
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
async function readBody(event) {
|
|
202
|
+
if (ParsedBodySymbol in event.node.req) {
|
|
203
|
+
return event.node.req[ParsedBodySymbol];
|
|
204
|
+
}
|
|
205
|
+
const body = await readRawBody(event);
|
|
206
|
+
if (event.node.req.headers["content-type"] === "application/x-www-form-urlencoded") {
|
|
207
|
+
const form = new URLSearchParams(body);
|
|
208
|
+
const parsedForm = /* @__PURE__ */ Object.create(null);
|
|
209
|
+
for (const [key, value] of form.entries()) {
|
|
210
|
+
if (key in parsedForm) {
|
|
211
|
+
if (!Array.isArray(parsedForm[key])) {
|
|
212
|
+
parsedForm[key] = [parsedForm[key]];
|
|
213
|
+
}
|
|
214
|
+
parsedForm[key].push(value);
|
|
215
|
+
} else {
|
|
216
|
+
parsedForm[key] = value;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return parsedForm;
|
|
220
|
+
}
|
|
221
|
+
const json = destr(body);
|
|
222
|
+
event.node.req[ParsedBodySymbol] = json;
|
|
223
|
+
return json;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function handleCacheHeaders(event, opts) {
|
|
227
|
+
const cacheControls = ["public", ...opts.cacheControls || []];
|
|
228
|
+
let cacheMatched = false;
|
|
229
|
+
if (opts.maxAge !== void 0) {
|
|
230
|
+
cacheControls.push(`max-age=${+opts.maxAge}`, `s-maxage=${+opts.maxAge}`);
|
|
231
|
+
}
|
|
232
|
+
if (opts.modifiedTime) {
|
|
233
|
+
const modifiedTime = new Date(opts.modifiedTime);
|
|
234
|
+
const ifModifiedSince = event.node.req.headers["if-modified-since"];
|
|
235
|
+
event.node.res.setHeader("last-modified", modifiedTime.toUTCString());
|
|
236
|
+
if (ifModifiedSince && new Date(ifModifiedSince) >= opts.modifiedTime) {
|
|
237
|
+
cacheMatched = true;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (opts.etag) {
|
|
241
|
+
event.node.res.setHeader("etag", opts.etag);
|
|
242
|
+
const ifNonMatch = event.node.req.headers["if-none-match"];
|
|
243
|
+
if (ifNonMatch === opts.etag) {
|
|
244
|
+
cacheMatched = true;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
event.node.res.setHeader("cache-control", cacheControls.join(", "));
|
|
248
|
+
if (cacheMatched) {
|
|
249
|
+
event.node.res.statusCode = 304;
|
|
250
|
+
event.node.res.end();
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const MIMES = {
|
|
257
|
+
html: "text/html",
|
|
258
|
+
json: "application/json"
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const defer = typeof setImmediate !== "undefined" ? setImmediate : (fn) => fn();
|
|
262
|
+
function send(event, data, type) {
|
|
263
|
+
if (type) {
|
|
264
|
+
defaultContentType(event, type);
|
|
265
|
+
}
|
|
266
|
+
return new Promise((resolve) => {
|
|
267
|
+
defer(() => {
|
|
268
|
+
event.node.res.end(data);
|
|
269
|
+
resolve();
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
function defaultContentType(event, type) {
|
|
274
|
+
if (type && !event.node.res.getHeader("content-type")) {
|
|
275
|
+
event.node.res.setHeader("content-type", type);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function sendRedirect(event, location, code = 302) {
|
|
279
|
+
event.node.res.statusCode = code;
|
|
280
|
+
event.node.res.setHeader("location", location);
|
|
281
|
+
const encodedLoc = location.replace(/"/g, "%22");
|
|
282
|
+
const html = `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${encodedLoc}"></head></html>`;
|
|
283
|
+
return send(event, html, MIMES.html);
|
|
284
|
+
}
|
|
285
|
+
function getResponseHeaders(event) {
|
|
286
|
+
return event.node.res.getHeaders();
|
|
287
|
+
}
|
|
288
|
+
function getResponseHeader(event, name) {
|
|
289
|
+
return event.node.res.getHeader(name);
|
|
290
|
+
}
|
|
291
|
+
function setResponseHeaders(event, headers) {
|
|
292
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
293
|
+
event.node.res.setHeader(name, value);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const setHeaders = setResponseHeaders;
|
|
297
|
+
function setResponseHeader(event, name, value) {
|
|
298
|
+
event.node.res.setHeader(name, value);
|
|
299
|
+
}
|
|
300
|
+
const setHeader = setResponseHeader;
|
|
301
|
+
function appendResponseHeaders(event, headers) {
|
|
302
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
303
|
+
appendResponseHeader(event, name, value);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const appendHeaders = appendResponseHeaders;
|
|
307
|
+
function appendResponseHeader(event, name, value) {
|
|
308
|
+
let current = event.node.res.getHeader(name);
|
|
309
|
+
if (!current) {
|
|
310
|
+
event.node.res.setHeader(name, value);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (!Array.isArray(current)) {
|
|
314
|
+
current = [current.toString()];
|
|
315
|
+
}
|
|
316
|
+
event.node.res.setHeader(name, [...current, value]);
|
|
317
|
+
}
|
|
318
|
+
const appendHeader = appendResponseHeader;
|
|
319
|
+
function isStream(data) {
|
|
320
|
+
return data && typeof data === "object" && typeof data.pipe === "function" && typeof data.on === "function";
|
|
321
|
+
}
|
|
322
|
+
function sendStream(event, data) {
|
|
323
|
+
return new Promise((resolve, reject) => {
|
|
324
|
+
data.pipe(event.node.res);
|
|
325
|
+
data.on("end", () => resolve());
|
|
326
|
+
data.on("error", (error) => reject(createError(error)));
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
const noop = () => {
|
|
330
|
+
};
|
|
331
|
+
function writeEarlyHints(event, hints, cb = noop) {
|
|
332
|
+
if (!event.node.res.socket) {
|
|
333
|
+
cb();
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (typeof hints === "string" || Array.isArray(hints)) {
|
|
337
|
+
hints = { link: hints };
|
|
338
|
+
}
|
|
339
|
+
if (hints.link) {
|
|
340
|
+
hints.link = Array.isArray(hints.link) ? hints.link : hints.link.split(",");
|
|
341
|
+
}
|
|
342
|
+
const headers = Object.entries(hints).map(
|
|
343
|
+
(e) => [e[0].toLowerCase(), e[1]]
|
|
344
|
+
);
|
|
345
|
+
if (headers.length === 0) {
|
|
346
|
+
cb();
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
let hint = "HTTP/1.1 103 Early Hints";
|
|
350
|
+
if (hints.link) {
|
|
351
|
+
hint += `\r
|
|
352
|
+
Link: ${hints.link.join(", ")}`;
|
|
353
|
+
}
|
|
354
|
+
for (const [header, value] of headers) {
|
|
355
|
+
if (header === "link") {
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
hint += `\r
|
|
359
|
+
${header}: ${value}`;
|
|
360
|
+
}
|
|
361
|
+
if (event.node.res.socket) {
|
|
362
|
+
event.node.res.socket.write(
|
|
363
|
+
`${hint}\r
|
|
364
|
+
\r
|
|
365
|
+
`,
|
|
366
|
+
"utf8",
|
|
367
|
+
cb
|
|
368
|
+
);
|
|
369
|
+
} else {
|
|
370
|
+
cb();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function parseCookies(event) {
|
|
375
|
+
return cookieEs.parse(event.node.req.headers.cookie || "");
|
|
376
|
+
}
|
|
377
|
+
function getCookie(event, name) {
|
|
378
|
+
return parseCookies(event)[name];
|
|
379
|
+
}
|
|
380
|
+
function setCookie(event, name, value, serializeOptions) {
|
|
381
|
+
const cookieStr = cookieEs.serialize(name, value, {
|
|
382
|
+
path: "/",
|
|
383
|
+
...serializeOptions
|
|
384
|
+
});
|
|
385
|
+
appendHeader(event, "Set-Cookie", cookieStr);
|
|
386
|
+
}
|
|
387
|
+
function deleteCookie(event, name, serializeOptions) {
|
|
388
|
+
setCookie(event, name, "", {
|
|
389
|
+
...serializeOptions,
|
|
390
|
+
maxAge: 0
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const PayloadMethods = /* @__PURE__ */ new Set(["PATCH", "POST", "PUT", "DELETE"]);
|
|
395
|
+
const ignoredHeaders = /* @__PURE__ */ new Set([
|
|
396
|
+
"transfer-encoding",
|
|
397
|
+
"connection",
|
|
398
|
+
"keep-alive",
|
|
399
|
+
"upgrade",
|
|
400
|
+
"expect"
|
|
401
|
+
]);
|
|
402
|
+
async function proxyRequest(event, target, opts = {}) {
|
|
403
|
+
const method = getMethod(event);
|
|
404
|
+
let body;
|
|
405
|
+
if (PayloadMethods.has(method)) {
|
|
406
|
+
body = await readRawBody(event).catch(() => void 0);
|
|
407
|
+
}
|
|
408
|
+
const headers = /* @__PURE__ */ Object.create(null);
|
|
409
|
+
const reqHeaders = getRequestHeaders(event);
|
|
410
|
+
for (const name in reqHeaders) {
|
|
411
|
+
if (!ignoredHeaders.has(name)) {
|
|
412
|
+
headers[name] = reqHeaders[name];
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (opts.fetchOptions?.headers) {
|
|
416
|
+
Object.assign(headers, opts.fetchOptions.headers);
|
|
417
|
+
}
|
|
418
|
+
if (opts.headers) {
|
|
419
|
+
Object.assign(headers, opts.headers);
|
|
420
|
+
}
|
|
421
|
+
return sendProxy(event, target, {
|
|
422
|
+
...opts,
|
|
423
|
+
fetchOptions: {
|
|
424
|
+
headers,
|
|
425
|
+
method,
|
|
426
|
+
body,
|
|
427
|
+
...opts.fetchOptions
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
async function sendProxy(event, target, opts = {}) {
|
|
432
|
+
const _fetch = opts.fetch || globalThis.fetch;
|
|
433
|
+
if (!_fetch) {
|
|
434
|
+
throw new Error(
|
|
435
|
+
"fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js."
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
const response = await _fetch(target, {
|
|
439
|
+
headers: opts.headers,
|
|
440
|
+
...opts.fetchOptions
|
|
441
|
+
});
|
|
442
|
+
event.node.res.statusCode = response.status;
|
|
443
|
+
event.node.res.statusMessage = response.statusText;
|
|
444
|
+
for (const [key, value] of response.headers.entries()) {
|
|
445
|
+
if (key === "content-encoding") {
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
if (key === "content-length") {
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
event.node.res.setHeader(key, value);
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
if (response.body) {
|
|
455
|
+
if (opts.sendStream === false) {
|
|
456
|
+
const data = new Uint8Array(await response.arrayBuffer());
|
|
457
|
+
event.node.res.end(data);
|
|
458
|
+
} else {
|
|
459
|
+
for await (const chunk of response.body) {
|
|
460
|
+
event.node.res.write(chunk);
|
|
461
|
+
}
|
|
462
|
+
event.node.res.end();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
} catch (error) {
|
|
466
|
+
event.node.res.end();
|
|
467
|
+
throw error;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
class H3Headers {
|
|
472
|
+
constructor(init) {
|
|
473
|
+
if (!init) {
|
|
474
|
+
this._headers = {};
|
|
475
|
+
} else if (Array.isArray(init)) {
|
|
476
|
+
this._headers = Object.fromEntries(
|
|
477
|
+
init.map(([key, value]) => [key.toLowerCase(), value])
|
|
478
|
+
);
|
|
479
|
+
} else if (init && "append" in init) {
|
|
480
|
+
this._headers = Object.fromEntries(init.entries());
|
|
481
|
+
} else {
|
|
482
|
+
this._headers = Object.fromEntries(
|
|
483
|
+
Object.entries(init).map(([key, value]) => [key.toLowerCase(), value])
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
[Symbol.iterator]() {
|
|
488
|
+
return this.entries();
|
|
489
|
+
}
|
|
490
|
+
entries() {
|
|
491
|
+
throw Object.entries(this._headers)[Symbol.iterator]();
|
|
492
|
+
}
|
|
493
|
+
keys() {
|
|
494
|
+
return Object.keys(this._headers)[Symbol.iterator]();
|
|
495
|
+
}
|
|
496
|
+
values() {
|
|
497
|
+
throw Object.values(this._headers)[Symbol.iterator]();
|
|
498
|
+
}
|
|
499
|
+
append(name, value) {
|
|
500
|
+
const _name = name.toLowerCase();
|
|
501
|
+
this.set(_name, [this.get(_name), value].filter(Boolean).join(", "));
|
|
502
|
+
}
|
|
503
|
+
delete(name) {
|
|
504
|
+
delete this._headers[name.toLowerCase()];
|
|
505
|
+
}
|
|
506
|
+
get(name) {
|
|
507
|
+
return this._headers[name.toLowerCase()];
|
|
508
|
+
}
|
|
509
|
+
has(name) {
|
|
510
|
+
return name.toLowerCase() in this._headers;
|
|
511
|
+
}
|
|
512
|
+
set(name, value) {
|
|
513
|
+
this._headers[name.toLowerCase()] = String(value);
|
|
514
|
+
}
|
|
515
|
+
forEach(callbackfn) {
|
|
516
|
+
for (const [key, value] of Object.entries(this._headers)) {
|
|
517
|
+
callbackfn(value, key, this);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
class H3Response {
|
|
523
|
+
constructor(body = null, init = {}) {
|
|
524
|
+
this.body = null;
|
|
525
|
+
this.type = "default";
|
|
526
|
+
this.bodyUsed = false;
|
|
527
|
+
this.headers = new H3Headers(init.headers);
|
|
528
|
+
this.status = init.status ?? 200;
|
|
529
|
+
this.statusText = init.statusText || "";
|
|
530
|
+
this.redirected = !!init.status && [301, 302, 307, 308].includes(init.status);
|
|
531
|
+
this._body = body;
|
|
532
|
+
this.url = "";
|
|
533
|
+
this.ok = this.status < 300 && this.status > 199;
|
|
534
|
+
}
|
|
535
|
+
clone() {
|
|
536
|
+
return new H3Response(this.body, {
|
|
537
|
+
headers: this.headers,
|
|
538
|
+
status: this.status,
|
|
539
|
+
statusText: this.statusText
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
arrayBuffer() {
|
|
543
|
+
return Promise.resolve(this._body);
|
|
544
|
+
}
|
|
545
|
+
blob() {
|
|
546
|
+
return Promise.resolve(this._body);
|
|
547
|
+
}
|
|
548
|
+
formData() {
|
|
549
|
+
return Promise.resolve(this._body);
|
|
550
|
+
}
|
|
551
|
+
json() {
|
|
552
|
+
return Promise.resolve(this._body);
|
|
553
|
+
}
|
|
554
|
+
text() {
|
|
555
|
+
return Promise.resolve(this._body);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
class H3Event {
|
|
560
|
+
constructor(req, res) {
|
|
561
|
+
this["__is_event__"] = true;
|
|
562
|
+
this.context = {};
|
|
563
|
+
this.node = { req, res };
|
|
564
|
+
}
|
|
565
|
+
get path() {
|
|
566
|
+
return this.req.url;
|
|
567
|
+
}
|
|
568
|
+
get req() {
|
|
569
|
+
return this.node.req;
|
|
570
|
+
}
|
|
571
|
+
get res() {
|
|
572
|
+
return this.node.res;
|
|
573
|
+
}
|
|
574
|
+
respondWith(r) {
|
|
575
|
+
Promise.resolve(r).then((_response) => {
|
|
576
|
+
if (this.res.writableEnded) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
const response = _response instanceof H3Response ? _response : new H3Response(_response);
|
|
580
|
+
for (const [key, value] of response.headers.entries()) {
|
|
581
|
+
this.res.setHeader(key, value);
|
|
582
|
+
}
|
|
583
|
+
if (response.status) {
|
|
584
|
+
this.res.statusCode = response.status;
|
|
585
|
+
}
|
|
586
|
+
if (response.statusText) {
|
|
587
|
+
this.res.statusMessage = response.statusText;
|
|
588
|
+
}
|
|
589
|
+
if (response.redirected) {
|
|
590
|
+
this.res.setHeader("location", response.url);
|
|
591
|
+
}
|
|
592
|
+
if (!response._body) {
|
|
593
|
+
return this.res.end();
|
|
594
|
+
}
|
|
595
|
+
if (typeof response._body === "string" || "buffer" in response._body || "byteLength" in response._body) {
|
|
596
|
+
return this.res.end(response._body);
|
|
597
|
+
}
|
|
598
|
+
if (!response.headers.has("content-type")) {
|
|
599
|
+
response.headers.set("content-type", MIMES.json);
|
|
600
|
+
}
|
|
601
|
+
this.res.end(JSON.stringify(response._body));
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
function isEvent(input) {
|
|
606
|
+
return "__is_event__" in input;
|
|
607
|
+
}
|
|
608
|
+
function createEvent(req, res) {
|
|
609
|
+
return new H3Event(req, res);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function defineEventHandler(handler) {
|
|
613
|
+
handler.__is_handler__ = true;
|
|
614
|
+
return handler;
|
|
615
|
+
}
|
|
616
|
+
const eventHandler = defineEventHandler;
|
|
617
|
+
function isEventHandler(input) {
|
|
618
|
+
return "__is_handler__" in input;
|
|
619
|
+
}
|
|
620
|
+
function toEventHandler(input, _, _route) {
|
|
621
|
+
if (!isEventHandler(input)) {
|
|
622
|
+
console.warn(
|
|
623
|
+
"[h3] Implicit event handler conversion is deprecated. Use `eventHandler()` or `fromNodeMiddleware()` to define event handlers.",
|
|
624
|
+
_route && _route !== "/" ? `
|
|
625
|
+
Route: ${_route}` : "",
|
|
626
|
+
`
|
|
627
|
+
Handler: ${input}`
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
return input;
|
|
631
|
+
}
|
|
632
|
+
function dynamicEventHandler(initial) {
|
|
633
|
+
let current = initial;
|
|
634
|
+
const wrapper = eventHandler((event) => {
|
|
635
|
+
if (current) {
|
|
636
|
+
return current(event);
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
wrapper.set = (handler) => {
|
|
640
|
+
current = handler;
|
|
641
|
+
};
|
|
642
|
+
return wrapper;
|
|
643
|
+
}
|
|
644
|
+
function defineLazyEventHandler(factory) {
|
|
645
|
+
let _promise;
|
|
646
|
+
let _resolved;
|
|
647
|
+
const resolveHandler = () => {
|
|
648
|
+
if (_resolved) {
|
|
649
|
+
return Promise.resolve(_resolved);
|
|
650
|
+
}
|
|
651
|
+
if (!_promise) {
|
|
652
|
+
_promise = Promise.resolve(factory()).then((r) => {
|
|
653
|
+
const handler = r.default || r;
|
|
654
|
+
if (typeof handler !== "function") {
|
|
655
|
+
throw new TypeError(
|
|
656
|
+
"Invalid lazy handler result. It should be a function:",
|
|
657
|
+
handler
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
_resolved = toEventHandler(r.default || r);
|
|
661
|
+
return _resolved;
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
return _promise;
|
|
665
|
+
};
|
|
666
|
+
return eventHandler((event) => {
|
|
667
|
+
if (_resolved) {
|
|
668
|
+
return _resolved(event);
|
|
669
|
+
}
|
|
670
|
+
return resolveHandler().then((handler) => handler(event));
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
const lazyEventHandler = defineLazyEventHandler;
|
|
674
|
+
|
|
675
|
+
function createApp(options = {}) {
|
|
676
|
+
const stack = [];
|
|
677
|
+
const handler = createAppEventHandler(stack, options);
|
|
678
|
+
const app = {
|
|
679
|
+
use: (arg1, arg2, arg3) => use(app, arg1, arg2, arg3),
|
|
680
|
+
handler,
|
|
681
|
+
stack,
|
|
682
|
+
options
|
|
683
|
+
};
|
|
684
|
+
return app;
|
|
685
|
+
}
|
|
686
|
+
function use(app, arg1, arg2, arg3) {
|
|
687
|
+
if (Array.isArray(arg1)) {
|
|
688
|
+
for (const i of arg1) {
|
|
689
|
+
use(app, i, arg2, arg3);
|
|
690
|
+
}
|
|
691
|
+
} else if (Array.isArray(arg2)) {
|
|
692
|
+
for (const i of arg2) {
|
|
693
|
+
use(app, arg1, i, arg3);
|
|
694
|
+
}
|
|
695
|
+
} else if (typeof arg1 === "string") {
|
|
696
|
+
app.stack.push(
|
|
697
|
+
normalizeLayer({ ...arg3, route: arg1, handler: arg2 })
|
|
698
|
+
);
|
|
699
|
+
} else if (typeof arg1 === "function") {
|
|
700
|
+
app.stack.push(
|
|
701
|
+
normalizeLayer({ ...arg2, route: "/", handler: arg1 })
|
|
702
|
+
);
|
|
703
|
+
} else {
|
|
704
|
+
app.stack.push(normalizeLayer({ ...arg1 }));
|
|
705
|
+
}
|
|
706
|
+
return app;
|
|
707
|
+
}
|
|
708
|
+
function createAppEventHandler(stack, options) {
|
|
709
|
+
const spacing = options.debug ? 2 : void 0;
|
|
710
|
+
return eventHandler(async (event) => {
|
|
711
|
+
event.node.req.originalUrl = event.node.req.originalUrl || event.node.req.url || "/";
|
|
712
|
+
const reqUrl = event.node.req.url || "/";
|
|
713
|
+
for (const layer of stack) {
|
|
714
|
+
if (layer.route.length > 1) {
|
|
715
|
+
if (!reqUrl.startsWith(layer.route)) {
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
event.node.req.url = reqUrl.slice(layer.route.length) || "/";
|
|
719
|
+
} else {
|
|
720
|
+
event.node.req.url = reqUrl;
|
|
721
|
+
}
|
|
722
|
+
if (layer.match && !layer.match(event.node.req.url, event)) {
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
const val = await layer.handler(event);
|
|
726
|
+
if (event.node.res.writableEnded) {
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
const type = typeof val;
|
|
730
|
+
if (type === "string") {
|
|
731
|
+
return send(event, val, MIMES.html);
|
|
732
|
+
} else if (isStream(val)) {
|
|
733
|
+
return sendStream(event, val);
|
|
734
|
+
} else if (val === null) {
|
|
735
|
+
event.node.res.statusCode = 204;
|
|
736
|
+
return send(event);
|
|
737
|
+
} else if (type === "object" || type === "boolean" || type === "number") {
|
|
738
|
+
if (val.buffer) {
|
|
739
|
+
return send(event, val);
|
|
740
|
+
} else if (val instanceof Error) {
|
|
741
|
+
throw createError(val);
|
|
742
|
+
} else {
|
|
743
|
+
return send(
|
|
744
|
+
event,
|
|
745
|
+
JSON.stringify(val, void 0, spacing),
|
|
746
|
+
MIMES.json
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (!event.node.res.writableEnded) {
|
|
752
|
+
throw createError({
|
|
753
|
+
statusCode: 404,
|
|
754
|
+
statusMessage: `Cannot find any route matching ${event.node.req.url || "/"}.`
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
function normalizeLayer(input) {
|
|
760
|
+
let handler = input.handler;
|
|
761
|
+
if (handler.handler) {
|
|
762
|
+
handler = handler.handler;
|
|
763
|
+
}
|
|
764
|
+
if (input.lazy) {
|
|
765
|
+
handler = lazyEventHandler(handler);
|
|
766
|
+
} else if (!isEventHandler(handler)) {
|
|
767
|
+
handler = toEventHandler(handler, void 0, input.route);
|
|
768
|
+
}
|
|
769
|
+
return {
|
|
770
|
+
route: ufo.withoutTrailingSlash(input.route),
|
|
771
|
+
match: input.match,
|
|
772
|
+
handler
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const defineNodeListener = (handler) => handler;
|
|
777
|
+
const defineNodeMiddleware = (middleware) => middleware;
|
|
778
|
+
function fromNodeMiddleware(handler) {
|
|
779
|
+
if (isEventHandler(handler)) {
|
|
780
|
+
return handler;
|
|
781
|
+
}
|
|
782
|
+
if (typeof handler !== "function") {
|
|
783
|
+
throw new TypeError(
|
|
784
|
+
"Invalid handler. It should be a function:",
|
|
785
|
+
handler
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
return eventHandler((event) => {
|
|
789
|
+
return callNodeListener(
|
|
790
|
+
handler,
|
|
791
|
+
event.node.req,
|
|
792
|
+
event.node.res
|
|
793
|
+
);
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
function toNodeListener(app) {
|
|
797
|
+
const toNodeHandle = async function(req, res) {
|
|
798
|
+
const event = createEvent(req, res);
|
|
799
|
+
try {
|
|
800
|
+
await app.handler(event);
|
|
801
|
+
} catch (_error) {
|
|
802
|
+
const error = createError(_error);
|
|
803
|
+
if (!isError(_error)) {
|
|
804
|
+
error.unhandled = true;
|
|
805
|
+
}
|
|
806
|
+
if (app.options.onError) {
|
|
807
|
+
await app.options.onError(error, event);
|
|
808
|
+
} else {
|
|
809
|
+
if (error.unhandled || error.fatal) {
|
|
810
|
+
console.error("[h3]", error.fatal ? "[fatal]" : "[unhandled]", error);
|
|
811
|
+
}
|
|
812
|
+
await sendError(event, error, !!app.options.debug);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
return toNodeHandle;
|
|
817
|
+
}
|
|
818
|
+
function promisifyNodeListener(handler) {
|
|
819
|
+
return function(req, res) {
|
|
820
|
+
return callNodeListener(handler, req, res);
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
function callNodeListener(handler, req, res) {
|
|
824
|
+
const isMiddleware = handler.length > 2;
|
|
825
|
+
return new Promise((resolve, reject) => {
|
|
826
|
+
const next = (err) => {
|
|
827
|
+
if (isMiddleware) {
|
|
828
|
+
res.off("close", next);
|
|
829
|
+
res.off("error", next);
|
|
830
|
+
}
|
|
831
|
+
return err ? reject(createError(err)) : resolve(void 0);
|
|
832
|
+
};
|
|
833
|
+
try {
|
|
834
|
+
const returned = handler(req, res, next);
|
|
835
|
+
if (isMiddleware && returned === void 0) {
|
|
836
|
+
res.once("close", next);
|
|
837
|
+
res.once("error", next);
|
|
838
|
+
} else {
|
|
839
|
+
resolve(returned);
|
|
840
|
+
}
|
|
841
|
+
} catch (error) {
|
|
842
|
+
next(error);
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const RouterMethods = [
|
|
848
|
+
"connect",
|
|
849
|
+
"delete",
|
|
850
|
+
"get",
|
|
851
|
+
"head",
|
|
852
|
+
"options",
|
|
853
|
+
"post",
|
|
854
|
+
"put",
|
|
855
|
+
"trace",
|
|
856
|
+
"patch"
|
|
857
|
+
];
|
|
858
|
+
function createRouter(opts = {}) {
|
|
859
|
+
const _router = radix3.createRouter({});
|
|
860
|
+
const routes = {};
|
|
861
|
+
const router = {};
|
|
862
|
+
const addRoute = (path, handler, method) => {
|
|
863
|
+
let route = routes[path];
|
|
864
|
+
if (!route) {
|
|
865
|
+
routes[path] = route = { handlers: {} };
|
|
866
|
+
_router.insert(path, route);
|
|
867
|
+
}
|
|
868
|
+
if (Array.isArray(method)) {
|
|
869
|
+
for (const m of method) {
|
|
870
|
+
addRoute(path, handler, m);
|
|
871
|
+
}
|
|
872
|
+
} else {
|
|
873
|
+
route.handlers[method] = toEventHandler(handler, void 0, path);
|
|
874
|
+
}
|
|
875
|
+
return router;
|
|
876
|
+
};
|
|
877
|
+
router.use = router.add = (path, handler, method) => addRoute(path, handler, method || "all");
|
|
878
|
+
for (const method of RouterMethods) {
|
|
879
|
+
router[method] = (path, handle) => router.add(path, handle, method);
|
|
880
|
+
}
|
|
881
|
+
router.handler = eventHandler((event) => {
|
|
882
|
+
let path = event.node.req.url || "/";
|
|
883
|
+
const qIndex = path.indexOf("?");
|
|
884
|
+
if (qIndex !== -1) {
|
|
885
|
+
path = path.slice(0, Math.max(0, qIndex));
|
|
886
|
+
}
|
|
887
|
+
const matched = _router.lookup(path);
|
|
888
|
+
if (!matched || !matched.handlers) {
|
|
889
|
+
if (opts.preemptive || opts.preemtive) {
|
|
890
|
+
throw createError({
|
|
891
|
+
statusCode: 404,
|
|
892
|
+
name: "Not Found",
|
|
893
|
+
statusMessage: `Cannot find any route matching ${event.node.req.url || "/"}.`
|
|
894
|
+
});
|
|
895
|
+
} else {
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
const method = (event.node.req.method || "get").toLowerCase();
|
|
900
|
+
const handler = matched.handlers[method] || matched.handlers.all;
|
|
901
|
+
if (!handler) {
|
|
902
|
+
throw createError({
|
|
903
|
+
statusCode: 405,
|
|
904
|
+
name: "Method Not Allowed",
|
|
905
|
+
statusMessage: `Method ${method} is not allowed on this route.`
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
const params = matched.params || {};
|
|
909
|
+
event.context.params = params;
|
|
910
|
+
return handler(event);
|
|
911
|
+
});
|
|
912
|
+
return router;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
exports.H3Error = H3Error;
|
|
916
|
+
exports.H3Event = H3Event;
|
|
917
|
+
exports.H3Headers = H3Headers;
|
|
918
|
+
exports.H3Response = H3Response;
|
|
919
|
+
exports.MIMES = MIMES;
|
|
920
|
+
exports.appendHeader = appendHeader;
|
|
921
|
+
exports.appendHeaders = appendHeaders;
|
|
922
|
+
exports.appendResponseHeader = appendResponseHeader;
|
|
923
|
+
exports.appendResponseHeaders = appendResponseHeaders;
|
|
924
|
+
exports.assertMethod = assertMethod;
|
|
925
|
+
exports.callNodeListener = callNodeListener;
|
|
926
|
+
exports.createApp = createApp;
|
|
927
|
+
exports.createAppEventHandler = createAppEventHandler;
|
|
928
|
+
exports.createError = createError;
|
|
929
|
+
exports.createEvent = createEvent;
|
|
930
|
+
exports.createRouter = createRouter;
|
|
931
|
+
exports.defaultContentType = defaultContentType;
|
|
932
|
+
exports.defineEventHandler = defineEventHandler;
|
|
933
|
+
exports.defineLazyEventHandler = defineLazyEventHandler;
|
|
934
|
+
exports.defineNodeListener = defineNodeListener;
|
|
935
|
+
exports.defineNodeMiddleware = defineNodeMiddleware;
|
|
936
|
+
exports.deleteCookie = deleteCookie;
|
|
937
|
+
exports.dynamicEventHandler = dynamicEventHandler;
|
|
938
|
+
exports.eventHandler = eventHandler;
|
|
939
|
+
exports.fromNodeMiddleware = fromNodeMiddleware;
|
|
940
|
+
exports.getCookie = getCookie;
|
|
941
|
+
exports.getHeader = getHeader;
|
|
942
|
+
exports.getHeaders = getHeaders;
|
|
943
|
+
exports.getMethod = getMethod;
|
|
944
|
+
exports.getQuery = getQuery;
|
|
945
|
+
exports.getRequestHeader = getRequestHeader;
|
|
946
|
+
exports.getRequestHeaders = getRequestHeaders;
|
|
947
|
+
exports.getResponseHeader = getResponseHeader;
|
|
948
|
+
exports.getResponseHeaders = getResponseHeaders;
|
|
949
|
+
exports.getRouterParam = getRouterParam;
|
|
950
|
+
exports.getRouterParams = getRouterParams;
|
|
951
|
+
exports.handleCacheHeaders = handleCacheHeaders;
|
|
952
|
+
exports.isError = isError;
|
|
953
|
+
exports.isEvent = isEvent;
|
|
954
|
+
exports.isEventHandler = isEventHandler;
|
|
955
|
+
exports.isMethod = isMethod;
|
|
956
|
+
exports.isStream = isStream;
|
|
957
|
+
exports.lazyEventHandler = lazyEventHandler;
|
|
958
|
+
exports.parseCookies = parseCookies;
|
|
959
|
+
exports.promisifyNodeListener = promisifyNodeListener;
|
|
960
|
+
exports.proxyRequest = proxyRequest;
|
|
961
|
+
exports.readBody = readBody;
|
|
962
|
+
exports.readRawBody = readRawBody;
|
|
963
|
+
exports.send = send;
|
|
964
|
+
exports.sendError = sendError;
|
|
965
|
+
exports.sendProxy = sendProxy;
|
|
966
|
+
exports.sendRedirect = sendRedirect;
|
|
967
|
+
exports.sendStream = sendStream;
|
|
968
|
+
exports.setCookie = setCookie;
|
|
969
|
+
exports.setHeader = setHeader;
|
|
970
|
+
exports.setHeaders = setHeaders;
|
|
971
|
+
exports.setResponseHeader = setResponseHeader;
|
|
972
|
+
exports.setResponseHeaders = setResponseHeaders;
|
|
973
|
+
exports.toEventHandler = toEventHandler;
|
|
974
|
+
exports.toNodeListener = toNodeListener;
|
|
975
|
+
exports.use = use;
|
|
976
|
+
exports.useBase = useBase;
|
|
977
|
+
exports.writeEarlyHints = writeEarlyHints;
|