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 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?startScript=dev)
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](https://stackblitz.com/edit/github-2bmusk?file=app.ts&startScript=dev)
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, headers)` (alias: `getHeaders`)
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
- message: validateError.message || "Validation Failed",
254
- ...validateError
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(",")?.pop();
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
- return event.web?.request?.body || event._requestBody || new ReadableStream({
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
- const cookieStr = cookieEs.serialize(name, value, {
590
- path: "/",
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 && !cookieValue.startsWith(name + "=");
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
- if (event.context.sessions[sessionName]) {
1250
- return event.context.sessions[sessionName];
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 unsealed = await unsealSession(event, config, sealedSession).catch(
1271
- () => {
1272
- }
1273
- );
1274
- Object.assign(session, unsealed);
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 = any> extends Error {
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 = any>(input: string | (Partial<H3Error<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<string, Parameters<OutgoingMessage["setHeader"]>[1]>): void;
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 = any> extends Error {
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 = any>(input: string | (Partial<H3Error<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<string, Parameters<OutgoingMessage["setHeader"]>[1]>): void;
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 = any> extends Error {
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 = any>(input: string | (Partial<H3Error<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<string, Parameters<OutgoingMessage["setHeader"]>[1]>): void;
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
- message: validateError.message || "Validation Failed",
247
- ...validateError
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(",")?.pop();
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
- return event.web?.request?.body || event._requestBody || new ReadableStream({
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
- const cookieStr = serialize(name, value, {
583
- path: "/",
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 && !cookieValue.startsWith(name + "=");
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
- if (event.context.sessions[sessionName]) {
1243
- return event.context.sessions[sessionName];
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 unsealed = await unsealSession(event, config, sealedSession).catch(
1264
- () => {
1265
- }
1266
- );
1267
- Object.assign(session, unsealed);
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.9.0",
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.3",
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.7.4"
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.9.2",
36
- "@types/supertest": "^2.0.16",
37
- "@vitest/coverage-v8": "^0.34.6",
38
- "autocannon": "^7.12.0",
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.54.0",
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.5",
47
- "node-fetch-native": "^1.4.1",
48
- "prettier": "^3.1.0",
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.3",
52
- "typescript": "^5.2.2",
52
+ "supertest": "^6.3.4",
53
+ "typescript": "^5.3.3",
53
54
  "unbuild": "^2.0.0",
54
- "vitest": "^0.34.6",
55
+ "vitest": "^1.2.1",
55
56
  "zod": "^3.22.4"
56
57
  },
57
- "packageManager": "pnpm@8.10.5",
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 typecheck && vitest --run --coverage",
68
- "test:types": "vitest typecheck"
68
+ "test": "pnpm lint && vitest --run --coverage"
69
69
  }
70
70
  }