h3 1.9.0 → 1.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/dist/index.cjs +40 -18
- package/dist/index.d.cts +9 -7
- package/dist/index.d.mts +9 -7
- package/dist/index.d.ts +9 -7
- package/dist/index.mjs +40 -18
- package/package.json +19 -19
package/README.md
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
H3 (pronounced as /eɪtʃθriː/, like h-3) is a minimal h(ttp) framework built for high performance and portability.
|
|
11
11
|
|
|
12
|
-
👉 [Online Playground](https://stackblitz.com/github/unjs/h3/tree/main/playground
|
|
12
|
+
👉 [Online Playground](https://stackblitz.com/github/unjs/h3/tree/main/playground)
|
|
13
|
+
|
|
14
|
+
👉 [Online Examples Playground](https://stackblitz.com/github/unjs/h3/tree/main/examples)
|
|
13
15
|
|
|
14
16
|
## Features
|
|
15
17
|
|
|
@@ -135,7 +137,7 @@ app.use(router);
|
|
|
135
137
|
|
|
136
138
|
Routes are internally stored in a [Radix Tree](https://en.wikipedia.org/wiki/Radix_tree) and matched using [unjs/radix3](https://github.com/unjs/radix3).
|
|
137
139
|
|
|
138
|
-
For using nested routers, see [this example](
|
|
140
|
+
For using nested routers, see [this example](./examples/nested-router.ts)
|
|
139
141
|
|
|
140
142
|
## More app usage examples
|
|
141
143
|
|
|
@@ -220,7 +222,7 @@ H3 has a concept of composable utilities that accept `event` (from `eventHandler
|
|
|
220
222
|
- `getMethod(event, default?)`
|
|
221
223
|
- `isMethod(event, expected, allowHead?)`
|
|
222
224
|
- `assertMethod(event, expected, allowHead?)`
|
|
223
|
-
- `getRequestHeaders(event
|
|
225
|
+
- `getRequestHeaders(event)` (alias: `getHeaders`)
|
|
224
226
|
- `getRequestHeader(event, name)` (alias: `getHeader`)
|
|
225
227
|
- `getRequestURL(event)`
|
|
226
228
|
- `getRequestHost(event)`
|
|
@@ -314,6 +316,14 @@ PRs are welcome to add your packages.
|
|
|
314
316
|
- [h3-valibot](https://github.com/intevel/h3-valibot)
|
|
315
317
|
- `useValidateBody(event, schema)`
|
|
316
318
|
- `useValidateParams(event, schema)`
|
|
319
|
+
- [h3-compression](https://github.com/CodeDredd/h3-compression)
|
|
320
|
+
- `useGZipCompression(event, response)`
|
|
321
|
+
- `useDeflateCompression(event, response)`
|
|
322
|
+
- `useBrotliCompression(event, response)`
|
|
323
|
+
- `useCompression(event, response)`
|
|
324
|
+
- `useGZipCompressionStream(event, response)`
|
|
325
|
+
- `useDeflateCompressionStream(event, response)`
|
|
326
|
+
- `useCompressionStream(event, response)`
|
|
317
327
|
- [@intlify/h3](https://github.com/intlify/h3)
|
|
318
328
|
- `defineI18nMiddleware(options)`
|
|
319
329
|
- `useTranslation(event)`
|
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const ufo = require('ufo');
|
|
4
4
|
const cookieEs = require('cookie-es');
|
|
5
|
+
const ohash = require('ohash');
|
|
5
6
|
const radix3 = require('radix3');
|
|
6
7
|
const destr = require('destr');
|
|
7
8
|
const defu = require('defu');
|
|
@@ -250,8 +251,9 @@ async function validateData(data, fn) {
|
|
|
250
251
|
function createValidationError(validateError) {
|
|
251
252
|
throw createError({
|
|
252
253
|
status: 400,
|
|
253
|
-
|
|
254
|
-
|
|
254
|
+
statusMessage: "Validation Error",
|
|
255
|
+
message: validateError?.message || "Validation Error",
|
|
256
|
+
data: validateError
|
|
255
257
|
});
|
|
256
258
|
}
|
|
257
259
|
|
|
@@ -362,7 +364,7 @@ function getRequestIP(event, opts = {}) {
|
|
|
362
364
|
return event.context.clientAddress;
|
|
363
365
|
}
|
|
364
366
|
if (opts.xForwardedFor) {
|
|
365
|
-
const xForwardedFor = getRequestHeader(event, "x-forwarded-for")?.split(",")?.
|
|
367
|
+
const xForwardedFor = getRequestHeader(event, "x-forwarded-for")?.split(",").shift()?.trim();
|
|
366
368
|
if (xForwardedFor) {
|
|
367
369
|
return xForwardedFor;
|
|
368
370
|
}
|
|
@@ -377,7 +379,7 @@ const ParsedBodySymbol = Symbol.for("h3ParsedBody");
|
|
|
377
379
|
const PayloadMethods$1 = ["PATCH", "POST", "PUT", "DELETE"];
|
|
378
380
|
function readRawBody(event, encoding = "utf8") {
|
|
379
381
|
assertMethod(event, PayloadMethods$1);
|
|
380
|
-
const _rawBody = event._requestBody || event.web?.request?.body || event.node.req[RawBodySymbol] || event.node.req.body;
|
|
382
|
+
const _rawBody = event._requestBody || event.web?.request?.body || event.node.req[RawBodySymbol] || event.node.req.rawBody || event.node.req.body;
|
|
381
383
|
if (_rawBody) {
|
|
382
384
|
const promise2 = Promise.resolve(_rawBody).then((_resolved) => {
|
|
383
385
|
if (Buffer.isBuffer(_resolved)) {
|
|
@@ -481,7 +483,23 @@ function getRequestWebStream(event) {
|
|
|
481
483
|
if (!PayloadMethods$1.includes(event.method)) {
|
|
482
484
|
return;
|
|
483
485
|
}
|
|
484
|
-
|
|
486
|
+
const bodyStream = event.web?.request?.body || event._requestBody;
|
|
487
|
+
if (bodyStream) {
|
|
488
|
+
return bodyStream;
|
|
489
|
+
}
|
|
490
|
+
const _hasRawBody = RawBodySymbol in event.node.req || "rawBody" in event.node.req || "body" in event.node.req || "__unenv__" in event.node.req;
|
|
491
|
+
if (_hasRawBody) {
|
|
492
|
+
return new ReadableStream({
|
|
493
|
+
async start(controller) {
|
|
494
|
+
const _rawBody = await readRawBody(event, false);
|
|
495
|
+
if (_rawBody) {
|
|
496
|
+
controller.enqueue(_rawBody);
|
|
497
|
+
}
|
|
498
|
+
controller.close();
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
return new ReadableStream({
|
|
485
503
|
start: (controller) => {
|
|
486
504
|
event.node.req.on("data", (chunk) => {
|
|
487
505
|
controller.enqueue(chunk);
|
|
@@ -586,16 +604,15 @@ function getCookie(event, name) {
|
|
|
586
604
|
return parseCookies(event)[name];
|
|
587
605
|
}
|
|
588
606
|
function setCookie(event, name, value, serializeOptions) {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
...serializeOptions
|
|
592
|
-
});
|
|
607
|
+
serializeOptions = { path: "/", ...serializeOptions };
|
|
608
|
+
const cookieStr = cookieEs.serialize(name, value, serializeOptions);
|
|
593
609
|
let setCookies = event.node.res.getHeader("set-cookie");
|
|
594
610
|
if (!Array.isArray(setCookies)) {
|
|
595
611
|
setCookies = [setCookies];
|
|
596
612
|
}
|
|
613
|
+
const _optionsHash = ohash.objectHash(serializeOptions);
|
|
597
614
|
setCookies = setCookies.filter((cookieValue) => {
|
|
598
|
-
return cookieValue &&
|
|
615
|
+
return cookieValue && _optionsHash !== ohash.objectHash(cookieEs.parse(cookieValue));
|
|
599
616
|
});
|
|
600
617
|
event.node.res.setHeader("set-cookie", [...setCookies, cookieStr]);
|
|
601
618
|
}
|
|
@@ -707,7 +724,7 @@ function getResponseStatusText(event) {
|
|
|
707
724
|
return event.node.res.statusMessage;
|
|
708
725
|
}
|
|
709
726
|
function defaultContentType(event, type) {
|
|
710
|
-
if (type && !event.node.res.getHeader("content-type")) {
|
|
727
|
+
if (type && event.node.res.statusCode !== 304 && !event.node.res.getHeader("content-type")) {
|
|
711
728
|
event.node.res.setHeader("content-type", type);
|
|
712
729
|
}
|
|
713
730
|
}
|
|
@@ -1214,6 +1231,7 @@ function mergeHeaders(defaults, ...inputs) {
|
|
|
1214
1231
|
return merged;
|
|
1215
1232
|
}
|
|
1216
1233
|
|
|
1234
|
+
const getSessionPromise = Symbol("getSession");
|
|
1217
1235
|
const DEFAULT_NAME = "h3";
|
|
1218
1236
|
const DEFAULT_COOKIE = {
|
|
1219
1237
|
path: "/",
|
|
@@ -1246,8 +1264,9 @@ async function getSession(event, config) {
|
|
|
1246
1264
|
if (!event.context.sessions) {
|
|
1247
1265
|
event.context.sessions = /* @__PURE__ */ Object.create(null);
|
|
1248
1266
|
}
|
|
1249
|
-
|
|
1250
|
-
|
|
1267
|
+
const existingSession = event.context.sessions[sessionName];
|
|
1268
|
+
if (existingSession) {
|
|
1269
|
+
return existingSession[getSessionPromise] || existingSession;
|
|
1251
1270
|
}
|
|
1252
1271
|
const session = {
|
|
1253
1272
|
id: "",
|
|
@@ -1267,11 +1286,14 @@ async function getSession(event, config) {
|
|
|
1267
1286
|
sealedSession = getCookie(event, sessionName);
|
|
1268
1287
|
}
|
|
1269
1288
|
if (sealedSession) {
|
|
1270
|
-
const
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1289
|
+
const promise = unsealSession(event, config, sealedSession).catch(() => {
|
|
1290
|
+
}).then((unsealed) => {
|
|
1291
|
+
Object.assign(session, unsealed);
|
|
1292
|
+
delete event.context.sessions[sessionName][getSessionPromise];
|
|
1293
|
+
return session;
|
|
1294
|
+
});
|
|
1295
|
+
event.context.sessions[sessionName][getSessionPromise] = promise;
|
|
1296
|
+
await promise;
|
|
1275
1297
|
}
|
|
1276
1298
|
if (!session.id) {
|
|
1277
1299
|
session.id = config.generateId?.() ?? (config.crypto || crypto__default).randomUUID();
|
package/dist/index.d.cts
CHANGED
|
@@ -107,10 +107,12 @@ declare const H3Response: {
|
|
|
107
107
|
|
|
108
108
|
type SessionDataT = Record<string, any>;
|
|
109
109
|
type SessionData<T extends SessionDataT = SessionDataT> = T;
|
|
110
|
+
declare const getSessionPromise: unique symbol;
|
|
110
111
|
interface Session<T extends SessionDataT = SessionDataT> {
|
|
111
112
|
id: string;
|
|
112
113
|
createdAt: number;
|
|
113
114
|
data: SessionData<T>;
|
|
115
|
+
[getSessionPromise]?: Promise<Session<T>>;
|
|
114
116
|
}
|
|
115
117
|
interface SessionConfig {
|
|
116
118
|
/** Private key used to encrypt session tokens */
|
|
@@ -213,7 +215,7 @@ type HTTPHeaderName = _HTTPHeaderName | Lowercase<_HTTPHeaderName> | (string & {
|
|
|
213
215
|
* This can be used to pass additional information about the error.
|
|
214
216
|
* @property {boolean} internal - Setting this property to `true` will mark the error as an internal error.
|
|
215
217
|
*/
|
|
216
|
-
declare class H3Error<DataT =
|
|
218
|
+
declare class H3Error<DataT = unknown> extends Error {
|
|
217
219
|
static __h3_error__: boolean;
|
|
218
220
|
statusCode: number;
|
|
219
221
|
fatal: boolean;
|
|
@@ -232,10 +234,10 @@ declare class H3Error<DataT = any> extends Error {
|
|
|
232
234
|
* @param input {string | (Partial<H3Error> & { status?: number; statusText?: string })} - The error message or an object containing error properties.
|
|
233
235
|
* @return {H3Error} - An instance of H3Error.
|
|
234
236
|
*/
|
|
235
|
-
declare function createError<DataT =
|
|
237
|
+
declare function createError<DataT = unknown>(input: string | (Partial<H3Error<DataT>> & {
|
|
236
238
|
status?: number;
|
|
237
239
|
statusText?: string;
|
|
238
|
-
})): H3Error
|
|
240
|
+
})): H3Error<DataT>;
|
|
239
241
|
/**
|
|
240
242
|
* Receives an error and returns the corresponding response.
|
|
241
243
|
* H3 internally uses this function to handle unhandled errors.
|
|
@@ -253,7 +255,7 @@ declare function sendError(event: H3Event, error: Error | H3Error, debug?: boole
|
|
|
253
255
|
* @param input {*} - The input to check.
|
|
254
256
|
* @return {boolean} - Returns true if the input is an instance of H3Error, false otherwise.
|
|
255
257
|
*/
|
|
256
|
-
declare function isError(input: any): input is H3Error
|
|
258
|
+
declare function isError<DataT = unknown>(input: any): input is H3Error<DataT>;
|
|
257
259
|
|
|
258
260
|
interface Layer {
|
|
259
261
|
route: string;
|
|
@@ -580,9 +582,9 @@ declare function getResponseStatus(event: H3Event): number;
|
|
|
580
582
|
declare function getResponseStatusText(event: H3Event): string;
|
|
581
583
|
declare function defaultContentType(event: H3Event, type?: string): void;
|
|
582
584
|
declare function sendRedirect(event: H3Event, location: string, code?: number): Promise<void>;
|
|
583
|
-
declare function getResponseHeaders(event: H3Event): ReturnType<H3Event["res"]["getHeaders"]>;
|
|
584
|
-
declare function getResponseHeader(event: H3Event, name: HTTPHeaderName): ReturnType<H3Event["res"]["getHeader"]>;
|
|
585
|
-
declare function setResponseHeaders(event: H3Event, headers: Record<
|
|
585
|
+
declare function getResponseHeaders(event: H3Event): ReturnType<H3Event["node"]["res"]["getHeaders"]>;
|
|
586
|
+
declare function getResponseHeader(event: H3Event, name: HTTPHeaderName): ReturnType<H3Event["node"]["res"]["getHeader"]>;
|
|
587
|
+
declare function setResponseHeaders(event: H3Event, headers: Partial<Record<HTTPHeaderName, Parameters<OutgoingMessage["setHeader"]>[1]>>): void;
|
|
586
588
|
declare const setHeaders: typeof setResponseHeaders;
|
|
587
589
|
declare function setResponseHeader(event: H3Event, name: HTTPHeaderName, value: Parameters<OutgoingMessage["setHeader"]>[1]): void;
|
|
588
590
|
declare const setHeader: typeof setResponseHeader;
|
package/dist/index.d.mts
CHANGED
|
@@ -107,10 +107,12 @@ declare const H3Response: {
|
|
|
107
107
|
|
|
108
108
|
type SessionDataT = Record<string, any>;
|
|
109
109
|
type SessionData<T extends SessionDataT = SessionDataT> = T;
|
|
110
|
+
declare const getSessionPromise: unique symbol;
|
|
110
111
|
interface Session<T extends SessionDataT = SessionDataT> {
|
|
111
112
|
id: string;
|
|
112
113
|
createdAt: number;
|
|
113
114
|
data: SessionData<T>;
|
|
115
|
+
[getSessionPromise]?: Promise<Session<T>>;
|
|
114
116
|
}
|
|
115
117
|
interface SessionConfig {
|
|
116
118
|
/** Private key used to encrypt session tokens */
|
|
@@ -213,7 +215,7 @@ type HTTPHeaderName = _HTTPHeaderName | Lowercase<_HTTPHeaderName> | (string & {
|
|
|
213
215
|
* This can be used to pass additional information about the error.
|
|
214
216
|
* @property {boolean} internal - Setting this property to `true` will mark the error as an internal error.
|
|
215
217
|
*/
|
|
216
|
-
declare class H3Error<DataT =
|
|
218
|
+
declare class H3Error<DataT = unknown> extends Error {
|
|
217
219
|
static __h3_error__: boolean;
|
|
218
220
|
statusCode: number;
|
|
219
221
|
fatal: boolean;
|
|
@@ -232,10 +234,10 @@ declare class H3Error<DataT = any> extends Error {
|
|
|
232
234
|
* @param input {string | (Partial<H3Error> & { status?: number; statusText?: string })} - The error message or an object containing error properties.
|
|
233
235
|
* @return {H3Error} - An instance of H3Error.
|
|
234
236
|
*/
|
|
235
|
-
declare function createError<DataT =
|
|
237
|
+
declare function createError<DataT = unknown>(input: string | (Partial<H3Error<DataT>> & {
|
|
236
238
|
status?: number;
|
|
237
239
|
statusText?: string;
|
|
238
|
-
})): H3Error
|
|
240
|
+
})): H3Error<DataT>;
|
|
239
241
|
/**
|
|
240
242
|
* Receives an error and returns the corresponding response.
|
|
241
243
|
* H3 internally uses this function to handle unhandled errors.
|
|
@@ -253,7 +255,7 @@ declare function sendError(event: H3Event, error: Error | H3Error, debug?: boole
|
|
|
253
255
|
* @param input {*} - The input to check.
|
|
254
256
|
* @return {boolean} - Returns true if the input is an instance of H3Error, false otherwise.
|
|
255
257
|
*/
|
|
256
|
-
declare function isError(input: any): input is H3Error
|
|
258
|
+
declare function isError<DataT = unknown>(input: any): input is H3Error<DataT>;
|
|
257
259
|
|
|
258
260
|
interface Layer {
|
|
259
261
|
route: string;
|
|
@@ -580,9 +582,9 @@ declare function getResponseStatus(event: H3Event): number;
|
|
|
580
582
|
declare function getResponseStatusText(event: H3Event): string;
|
|
581
583
|
declare function defaultContentType(event: H3Event, type?: string): void;
|
|
582
584
|
declare function sendRedirect(event: H3Event, location: string, code?: number): Promise<void>;
|
|
583
|
-
declare function getResponseHeaders(event: H3Event): ReturnType<H3Event["res"]["getHeaders"]>;
|
|
584
|
-
declare function getResponseHeader(event: H3Event, name: HTTPHeaderName): ReturnType<H3Event["res"]["getHeader"]>;
|
|
585
|
-
declare function setResponseHeaders(event: H3Event, headers: Record<
|
|
585
|
+
declare function getResponseHeaders(event: H3Event): ReturnType<H3Event["node"]["res"]["getHeaders"]>;
|
|
586
|
+
declare function getResponseHeader(event: H3Event, name: HTTPHeaderName): ReturnType<H3Event["node"]["res"]["getHeader"]>;
|
|
587
|
+
declare function setResponseHeaders(event: H3Event, headers: Partial<Record<HTTPHeaderName, Parameters<OutgoingMessage["setHeader"]>[1]>>): void;
|
|
586
588
|
declare const setHeaders: typeof setResponseHeaders;
|
|
587
589
|
declare function setResponseHeader(event: H3Event, name: HTTPHeaderName, value: Parameters<OutgoingMessage["setHeader"]>[1]): void;
|
|
588
590
|
declare const setHeader: typeof setResponseHeader;
|
package/dist/index.d.ts
CHANGED
|
@@ -107,10 +107,12 @@ declare const H3Response: {
|
|
|
107
107
|
|
|
108
108
|
type SessionDataT = Record<string, any>;
|
|
109
109
|
type SessionData<T extends SessionDataT = SessionDataT> = T;
|
|
110
|
+
declare const getSessionPromise: unique symbol;
|
|
110
111
|
interface Session<T extends SessionDataT = SessionDataT> {
|
|
111
112
|
id: string;
|
|
112
113
|
createdAt: number;
|
|
113
114
|
data: SessionData<T>;
|
|
115
|
+
[getSessionPromise]?: Promise<Session<T>>;
|
|
114
116
|
}
|
|
115
117
|
interface SessionConfig {
|
|
116
118
|
/** Private key used to encrypt session tokens */
|
|
@@ -213,7 +215,7 @@ type HTTPHeaderName = _HTTPHeaderName | Lowercase<_HTTPHeaderName> | (string & {
|
|
|
213
215
|
* This can be used to pass additional information about the error.
|
|
214
216
|
* @property {boolean} internal - Setting this property to `true` will mark the error as an internal error.
|
|
215
217
|
*/
|
|
216
|
-
declare class H3Error<DataT =
|
|
218
|
+
declare class H3Error<DataT = unknown> extends Error {
|
|
217
219
|
static __h3_error__: boolean;
|
|
218
220
|
statusCode: number;
|
|
219
221
|
fatal: boolean;
|
|
@@ -232,10 +234,10 @@ declare class H3Error<DataT = any> extends Error {
|
|
|
232
234
|
* @param input {string | (Partial<H3Error> & { status?: number; statusText?: string })} - The error message or an object containing error properties.
|
|
233
235
|
* @return {H3Error} - An instance of H3Error.
|
|
234
236
|
*/
|
|
235
|
-
declare function createError<DataT =
|
|
237
|
+
declare function createError<DataT = unknown>(input: string | (Partial<H3Error<DataT>> & {
|
|
236
238
|
status?: number;
|
|
237
239
|
statusText?: string;
|
|
238
|
-
})): H3Error
|
|
240
|
+
})): H3Error<DataT>;
|
|
239
241
|
/**
|
|
240
242
|
* Receives an error and returns the corresponding response.
|
|
241
243
|
* H3 internally uses this function to handle unhandled errors.
|
|
@@ -253,7 +255,7 @@ declare function sendError(event: H3Event, error: Error | H3Error, debug?: boole
|
|
|
253
255
|
* @param input {*} - The input to check.
|
|
254
256
|
* @return {boolean} - Returns true if the input is an instance of H3Error, false otherwise.
|
|
255
257
|
*/
|
|
256
|
-
declare function isError(input: any): input is H3Error
|
|
258
|
+
declare function isError<DataT = unknown>(input: any): input is H3Error<DataT>;
|
|
257
259
|
|
|
258
260
|
interface Layer {
|
|
259
261
|
route: string;
|
|
@@ -580,9 +582,9 @@ declare function getResponseStatus(event: H3Event): number;
|
|
|
580
582
|
declare function getResponseStatusText(event: H3Event): string;
|
|
581
583
|
declare function defaultContentType(event: H3Event, type?: string): void;
|
|
582
584
|
declare function sendRedirect(event: H3Event, location: string, code?: number): Promise<void>;
|
|
583
|
-
declare function getResponseHeaders(event: H3Event): ReturnType<H3Event["res"]["getHeaders"]>;
|
|
584
|
-
declare function getResponseHeader(event: H3Event, name: HTTPHeaderName): ReturnType<H3Event["res"]["getHeader"]>;
|
|
585
|
-
declare function setResponseHeaders(event: H3Event, headers: Record<
|
|
585
|
+
declare function getResponseHeaders(event: H3Event): ReturnType<H3Event["node"]["res"]["getHeaders"]>;
|
|
586
|
+
declare function getResponseHeader(event: H3Event, name: HTTPHeaderName): ReturnType<H3Event["node"]["res"]["getHeader"]>;
|
|
587
|
+
declare function setResponseHeaders(event: H3Event, headers: Partial<Record<HTTPHeaderName, Parameters<OutgoingMessage["setHeader"]>[1]>>): void;
|
|
586
588
|
declare const setHeaders: typeof setResponseHeaders;
|
|
587
589
|
declare function setResponseHeader(event: H3Event, name: HTTPHeaderName, value: Parameters<OutgoingMessage["setHeader"]>[1]): void;
|
|
588
590
|
declare const setHeader: typeof setResponseHeader;
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { withoutTrailingSlash, withoutBase, getQuery as getQuery$1, decode, decodePath, withLeadingSlash, parseURL } from 'ufo';
|
|
2
2
|
import { parse as parse$1, serialize } from 'cookie-es';
|
|
3
|
+
import { objectHash } from 'ohash';
|
|
3
4
|
import { createRouter as createRouter$1, toRouteMatcher } from 'radix3';
|
|
4
5
|
import destr from 'destr';
|
|
5
6
|
import { defu } from 'defu';
|
|
@@ -243,8 +244,9 @@ async function validateData(data, fn) {
|
|
|
243
244
|
function createValidationError(validateError) {
|
|
244
245
|
throw createError({
|
|
245
246
|
status: 400,
|
|
246
|
-
|
|
247
|
-
|
|
247
|
+
statusMessage: "Validation Error",
|
|
248
|
+
message: validateError?.message || "Validation Error",
|
|
249
|
+
data: validateError
|
|
248
250
|
});
|
|
249
251
|
}
|
|
250
252
|
|
|
@@ -355,7 +357,7 @@ function getRequestIP(event, opts = {}) {
|
|
|
355
357
|
return event.context.clientAddress;
|
|
356
358
|
}
|
|
357
359
|
if (opts.xForwardedFor) {
|
|
358
|
-
const xForwardedFor = getRequestHeader(event, "x-forwarded-for")?.split(",")?.
|
|
360
|
+
const xForwardedFor = getRequestHeader(event, "x-forwarded-for")?.split(",").shift()?.trim();
|
|
359
361
|
if (xForwardedFor) {
|
|
360
362
|
return xForwardedFor;
|
|
361
363
|
}
|
|
@@ -370,7 +372,7 @@ const ParsedBodySymbol = Symbol.for("h3ParsedBody");
|
|
|
370
372
|
const PayloadMethods$1 = ["PATCH", "POST", "PUT", "DELETE"];
|
|
371
373
|
function readRawBody(event, encoding = "utf8") {
|
|
372
374
|
assertMethod(event, PayloadMethods$1);
|
|
373
|
-
const _rawBody = event._requestBody || event.web?.request?.body || event.node.req[RawBodySymbol] || event.node.req.body;
|
|
375
|
+
const _rawBody = event._requestBody || event.web?.request?.body || event.node.req[RawBodySymbol] || event.node.req.rawBody || event.node.req.body;
|
|
374
376
|
if (_rawBody) {
|
|
375
377
|
const promise2 = Promise.resolve(_rawBody).then((_resolved) => {
|
|
376
378
|
if (Buffer.isBuffer(_resolved)) {
|
|
@@ -474,7 +476,23 @@ function getRequestWebStream(event) {
|
|
|
474
476
|
if (!PayloadMethods$1.includes(event.method)) {
|
|
475
477
|
return;
|
|
476
478
|
}
|
|
477
|
-
|
|
479
|
+
const bodyStream = event.web?.request?.body || event._requestBody;
|
|
480
|
+
if (bodyStream) {
|
|
481
|
+
return bodyStream;
|
|
482
|
+
}
|
|
483
|
+
const _hasRawBody = RawBodySymbol in event.node.req || "rawBody" in event.node.req || "body" in event.node.req || "__unenv__" in event.node.req;
|
|
484
|
+
if (_hasRawBody) {
|
|
485
|
+
return new ReadableStream({
|
|
486
|
+
async start(controller) {
|
|
487
|
+
const _rawBody = await readRawBody(event, false);
|
|
488
|
+
if (_rawBody) {
|
|
489
|
+
controller.enqueue(_rawBody);
|
|
490
|
+
}
|
|
491
|
+
controller.close();
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
return new ReadableStream({
|
|
478
496
|
start: (controller) => {
|
|
479
497
|
event.node.req.on("data", (chunk) => {
|
|
480
498
|
controller.enqueue(chunk);
|
|
@@ -579,16 +597,15 @@ function getCookie(event, name) {
|
|
|
579
597
|
return parseCookies(event)[name];
|
|
580
598
|
}
|
|
581
599
|
function setCookie(event, name, value, serializeOptions) {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
...serializeOptions
|
|
585
|
-
});
|
|
600
|
+
serializeOptions = { path: "/", ...serializeOptions };
|
|
601
|
+
const cookieStr = serialize(name, value, serializeOptions);
|
|
586
602
|
let setCookies = event.node.res.getHeader("set-cookie");
|
|
587
603
|
if (!Array.isArray(setCookies)) {
|
|
588
604
|
setCookies = [setCookies];
|
|
589
605
|
}
|
|
606
|
+
const _optionsHash = objectHash(serializeOptions);
|
|
590
607
|
setCookies = setCookies.filter((cookieValue) => {
|
|
591
|
-
return cookieValue &&
|
|
608
|
+
return cookieValue && _optionsHash !== objectHash(parse$1(cookieValue));
|
|
592
609
|
});
|
|
593
610
|
event.node.res.setHeader("set-cookie", [...setCookies, cookieStr]);
|
|
594
611
|
}
|
|
@@ -700,7 +717,7 @@ function getResponseStatusText(event) {
|
|
|
700
717
|
return event.node.res.statusMessage;
|
|
701
718
|
}
|
|
702
719
|
function defaultContentType(event, type) {
|
|
703
|
-
if (type && !event.node.res.getHeader("content-type")) {
|
|
720
|
+
if (type && event.node.res.statusCode !== 304 && !event.node.res.getHeader("content-type")) {
|
|
704
721
|
event.node.res.setHeader("content-type", type);
|
|
705
722
|
}
|
|
706
723
|
}
|
|
@@ -1207,6 +1224,7 @@ function mergeHeaders(defaults, ...inputs) {
|
|
|
1207
1224
|
return merged;
|
|
1208
1225
|
}
|
|
1209
1226
|
|
|
1227
|
+
const getSessionPromise = Symbol("getSession");
|
|
1210
1228
|
const DEFAULT_NAME = "h3";
|
|
1211
1229
|
const DEFAULT_COOKIE = {
|
|
1212
1230
|
path: "/",
|
|
@@ -1239,8 +1257,9 @@ async function getSession(event, config) {
|
|
|
1239
1257
|
if (!event.context.sessions) {
|
|
1240
1258
|
event.context.sessions = /* @__PURE__ */ Object.create(null);
|
|
1241
1259
|
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1260
|
+
const existingSession = event.context.sessions[sessionName];
|
|
1261
|
+
if (existingSession) {
|
|
1262
|
+
return existingSession[getSessionPromise] || existingSession;
|
|
1244
1263
|
}
|
|
1245
1264
|
const session = {
|
|
1246
1265
|
id: "",
|
|
@@ -1260,11 +1279,14 @@ async function getSession(event, config) {
|
|
|
1260
1279
|
sealedSession = getCookie(event, sessionName);
|
|
1261
1280
|
}
|
|
1262
1281
|
if (sealedSession) {
|
|
1263
|
-
const
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1282
|
+
const promise = unsealSession(event, config, sealedSession).catch(() => {
|
|
1283
|
+
}).then((unsealed) => {
|
|
1284
|
+
Object.assign(session, unsealed);
|
|
1285
|
+
delete event.context.sessions[sessionName][getSessionPromise];
|
|
1286
|
+
return session;
|
|
1287
|
+
});
|
|
1288
|
+
event.context.sessions[sessionName][getSessionPromise] = promise;
|
|
1289
|
+
await promise;
|
|
1268
1290
|
}
|
|
1269
1291
|
if (!session.id) {
|
|
1270
1292
|
session.id = config.generateId?.() ?? (config.crypto || crypto).randomUUID();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "h3",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.1",
|
|
4
4
|
"description": "Minimal H(TTP) framework built for high performance and portability.",
|
|
5
5
|
"repository": "unjs/h3",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,50 +21,50 @@
|
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"cookie-es": "^1.0.0",
|
|
24
|
-
"defu": "^6.1.
|
|
24
|
+
"defu": "^6.1.4",
|
|
25
25
|
"destr": "^2.0.2",
|
|
26
26
|
"iron-webcrypto": "^1.0.0",
|
|
27
|
+
"ohash": "^1.1.3",
|
|
27
28
|
"radix3": "^1.1.0",
|
|
28
29
|
"ufo": "^1.3.2",
|
|
29
30
|
"uncrypto": "^0.1.3",
|
|
30
|
-
"unenv": "^1.
|
|
31
|
+
"unenv": "^1.9.0"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"0x": "^5.7.0",
|
|
34
35
|
"@types/express": "^4.17.21",
|
|
35
|
-
"@types/node": "^20.
|
|
36
|
-
"@types/supertest": "^
|
|
37
|
-
"@vitest/coverage-v8": "^
|
|
38
|
-
"autocannon": "^7.
|
|
36
|
+
"@types/node": "^20.11.6",
|
|
37
|
+
"@types/supertest": "^6.0.2",
|
|
38
|
+
"@vitest/coverage-v8": "^1.2.1",
|
|
39
|
+
"autocannon": "^7.14.0",
|
|
39
40
|
"changelogen": "^0.5.5",
|
|
40
41
|
"connect": "^3.7.0",
|
|
41
|
-
"eslint": "^8.
|
|
42
|
+
"eslint": "^8.56.0",
|
|
42
43
|
"eslint-config-unjs": "^0.2.1",
|
|
43
44
|
"express": "^4.18.2",
|
|
44
45
|
"get-port": "^7.0.0",
|
|
45
46
|
"jiti": "^1.21.0",
|
|
46
|
-
"listhen": "^1.5.
|
|
47
|
-
"node-fetch-native": "^1.
|
|
48
|
-
"prettier": "^3.
|
|
47
|
+
"listhen": "^1.5.6",
|
|
48
|
+
"node-fetch-native": "^1.6.1",
|
|
49
|
+
"prettier": "^3.2.4",
|
|
49
50
|
"react": "^18.2.0",
|
|
50
51
|
"react-dom": "^18.2.0",
|
|
51
|
-
"supertest": "^6.3.
|
|
52
|
-
"typescript": "^5.
|
|
52
|
+
"supertest": "^6.3.4",
|
|
53
|
+
"typescript": "^5.3.3",
|
|
53
54
|
"unbuild": "^2.0.0",
|
|
54
|
-
"vitest": "^
|
|
55
|
+
"vitest": "^1.2.1",
|
|
55
56
|
"zod": "^3.22.4"
|
|
56
57
|
},
|
|
57
|
-
"packageManager": "pnpm@8.
|
|
58
|
+
"packageManager": "pnpm@8.14.3",
|
|
58
59
|
"scripts": {
|
|
59
60
|
"build": "unbuild",
|
|
60
61
|
"dev": "vitest",
|
|
61
|
-
"lint": "eslint --cache --ext .ts,.js,.mjs,.cjs . && prettier -c src test playground",
|
|
62
|
-
"lint:fix": "eslint --cache --ext .ts,.js,.mjs,.cjs . --fix && prettier -c src test playground -w",
|
|
62
|
+
"lint": "eslint --cache --ext .ts,.js,.mjs,.cjs . && prettier -c src test playground examples",
|
|
63
|
+
"lint:fix": "eslint --cache --ext .ts,.js,.mjs,.cjs . --fix && prettier -c src test playground examples -w",
|
|
63
64
|
"play": "listhen -w ./playground/app.ts",
|
|
64
65
|
"profile": "0x -o -D .profile -P 'autocannon -c 100 -p 10 -d 40 http://localhost:$PORT' ./playground/server.cjs",
|
|
65
66
|
"release": "pnpm test && pnpm build && changelogen --release && pnpm publish && git push --follow-tags",
|
|
66
67
|
"release-rc": "pnpm test && pnpm build && changelogen --release --prerelease rc --push --publish --publishTag rc",
|
|
67
|
-
"test": "pnpm lint && vitest --run
|
|
68
|
-
"test:types": "vitest typecheck"
|
|
68
|
+
"test": "pnpm lint && vitest --run --coverage"
|
|
69
69
|
}
|
|
70
70
|
}
|