h3 1.10.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
@@ -222,7 +222,7 @@ H3 has a concept of composable utilities that accept `event` (from `eventHandler
222
222
  - `getMethod(event, default?)`
223
223
  - `isMethod(event, expected, allowHead?)`
224
224
  - `assertMethod(event, expected, allowHead?)`
225
- - `getRequestHeaders(event, headers)` (alias: `getHeaders`)
225
+ - `getRequestHeaders(event)` (alias: `getHeaders`)
226
226
  - `getRequestHeader(event, name)` (alias: `getHeader`)
227
227
  - `getRequestURL(event)`
228
228
  - `getRequestHost(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');
@@ -363,7 +364,7 @@ function getRequestIP(event, opts = {}) {
363
364
  return event.context.clientAddress;
364
365
  }
365
366
  if (opts.xForwardedFor) {
366
- const xForwardedFor = getRequestHeader(event, "x-forwarded-for")?.split(",")?.pop();
367
+ const xForwardedFor = getRequestHeader(event, "x-forwarded-for")?.split(",").shift()?.trim();
367
368
  if (xForwardedFor) {
368
369
  return xForwardedFor;
369
370
  }
@@ -482,7 +483,23 @@ function getRequestWebStream(event) {
482
483
  if (!PayloadMethods$1.includes(event.method)) {
483
484
  return;
484
485
  }
485
- 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({
486
503
  start: (controller) => {
487
504
  event.node.req.on("data", (chunk) => {
488
505
  controller.enqueue(chunk);
@@ -587,16 +604,15 @@ function getCookie(event, name) {
587
604
  return parseCookies(event)[name];
588
605
  }
589
606
  function setCookie(event, name, value, serializeOptions) {
590
- const cookieStr = cookieEs.serialize(name, value, {
591
- path: "/",
592
- ...serializeOptions
593
- });
607
+ serializeOptions = { path: "/", ...serializeOptions };
608
+ const cookieStr = cookieEs.serialize(name, value, serializeOptions);
594
609
  let setCookies = event.node.res.getHeader("set-cookie");
595
610
  if (!Array.isArray(setCookies)) {
596
611
  setCookies = [setCookies];
597
612
  }
613
+ const _optionsHash = ohash.objectHash(serializeOptions);
598
614
  setCookies = setCookies.filter((cookieValue) => {
599
- return cookieValue && !cookieValue.startsWith(name + "=");
615
+ return cookieValue && _optionsHash !== ohash.objectHash(cookieEs.parse(cookieValue));
600
616
  });
601
617
  event.node.res.setHeader("set-cookie", [...setCookies, cookieStr]);
602
618
  }
@@ -708,7 +724,7 @@ function getResponseStatusText(event) {
708
724
  return event.node.res.statusMessage;
709
725
  }
710
726
  function defaultContentType(event, type) {
711
- if (type && !event.node.res.getHeader("content-type")) {
727
+ if (type && event.node.res.statusCode !== 304 && !event.node.res.getHeader("content-type")) {
712
728
  event.node.res.setHeader("content-type", type);
713
729
  }
714
730
  }
@@ -1215,6 +1231,7 @@ function mergeHeaders(defaults, ...inputs) {
1215
1231
  return merged;
1216
1232
  }
1217
1233
 
1234
+ const getSessionPromise = Symbol("getSession");
1218
1235
  const DEFAULT_NAME = "h3";
1219
1236
  const DEFAULT_COOKIE = {
1220
1237
  path: "/",
@@ -1247,8 +1264,9 @@ async function getSession(event, config) {
1247
1264
  if (!event.context.sessions) {
1248
1265
  event.context.sessions = /* @__PURE__ */ Object.create(null);
1249
1266
  }
1250
- if (event.context.sessions[sessionName]) {
1251
- return event.context.sessions[sessionName];
1267
+ const existingSession = event.context.sessions[sessionName];
1268
+ if (existingSession) {
1269
+ return existingSession[getSessionPromise] || existingSession;
1252
1270
  }
1253
1271
  const session = {
1254
1272
  id: "",
@@ -1268,11 +1286,14 @@ async function getSession(event, config) {
1268
1286
  sealedSession = getCookie(event, sessionName);
1269
1287
  }
1270
1288
  if (sealedSession) {
1271
- const unsealed = await unsealSession(event, config, sealedSession).catch(
1272
- () => {
1273
- }
1274
- );
1275
- 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;
1276
1297
  }
1277
1298
  if (!session.id) {
1278
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 */
@@ -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<HTTPHeaderName, 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 */
@@ -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<HTTPHeaderName, 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 */
@@ -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<HTTPHeaderName, 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';
@@ -356,7 +357,7 @@ function getRequestIP(event, opts = {}) {
356
357
  return event.context.clientAddress;
357
358
  }
358
359
  if (opts.xForwardedFor) {
359
- const xForwardedFor = getRequestHeader(event, "x-forwarded-for")?.split(",")?.pop();
360
+ const xForwardedFor = getRequestHeader(event, "x-forwarded-for")?.split(",").shift()?.trim();
360
361
  if (xForwardedFor) {
361
362
  return xForwardedFor;
362
363
  }
@@ -475,7 +476,23 @@ function getRequestWebStream(event) {
475
476
  if (!PayloadMethods$1.includes(event.method)) {
476
477
  return;
477
478
  }
478
- 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({
479
496
  start: (controller) => {
480
497
  event.node.req.on("data", (chunk) => {
481
498
  controller.enqueue(chunk);
@@ -580,16 +597,15 @@ function getCookie(event, name) {
580
597
  return parseCookies(event)[name];
581
598
  }
582
599
  function setCookie(event, name, value, serializeOptions) {
583
- const cookieStr = serialize(name, value, {
584
- path: "/",
585
- ...serializeOptions
586
- });
600
+ serializeOptions = { path: "/", ...serializeOptions };
601
+ const cookieStr = serialize(name, value, serializeOptions);
587
602
  let setCookies = event.node.res.getHeader("set-cookie");
588
603
  if (!Array.isArray(setCookies)) {
589
604
  setCookies = [setCookies];
590
605
  }
606
+ const _optionsHash = objectHash(serializeOptions);
591
607
  setCookies = setCookies.filter((cookieValue) => {
592
- return cookieValue && !cookieValue.startsWith(name + "=");
608
+ return cookieValue && _optionsHash !== objectHash(parse$1(cookieValue));
593
609
  });
594
610
  event.node.res.setHeader("set-cookie", [...setCookies, cookieStr]);
595
611
  }
@@ -701,7 +717,7 @@ function getResponseStatusText(event) {
701
717
  return event.node.res.statusMessage;
702
718
  }
703
719
  function defaultContentType(event, type) {
704
- if (type && !event.node.res.getHeader("content-type")) {
720
+ if (type && event.node.res.statusCode !== 304 && !event.node.res.getHeader("content-type")) {
705
721
  event.node.res.setHeader("content-type", type);
706
722
  }
707
723
  }
@@ -1208,6 +1224,7 @@ function mergeHeaders(defaults, ...inputs) {
1208
1224
  return merged;
1209
1225
  }
1210
1226
 
1227
+ const getSessionPromise = Symbol("getSession");
1211
1228
  const DEFAULT_NAME = "h3";
1212
1229
  const DEFAULT_COOKIE = {
1213
1230
  path: "/",
@@ -1240,8 +1257,9 @@ async function getSession(event, config) {
1240
1257
  if (!event.context.sessions) {
1241
1258
  event.context.sessions = /* @__PURE__ */ Object.create(null);
1242
1259
  }
1243
- if (event.context.sessions[sessionName]) {
1244
- return event.context.sessions[sessionName];
1260
+ const existingSession = event.context.sessions[sessionName];
1261
+ if (existingSession) {
1262
+ return existingSession[getSessionPromise] || existingSession;
1245
1263
  }
1246
1264
  const session = {
1247
1265
  id: "",
@@ -1261,11 +1279,14 @@ async function getSession(event, config) {
1261
1279
  sealedSession = getCookie(event, sessionName);
1262
1280
  }
1263
1281
  if (sealedSession) {
1264
- const unsealed = await unsealSession(event, config, sealedSession).catch(
1265
- () => {
1266
- }
1267
- );
1268
- 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;
1269
1290
  }
1270
1291
  if (!session.id) {
1271
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.10.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,40 +21,41 @@
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.8.0"
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.10.4",
36
+ "@types/node": "^20.11.6",
36
37
  "@types/supertest": "^6.0.2",
37
- "@vitest/coverage-v8": "^1.0.2",
38
+ "@vitest/coverage-v8": "^1.2.1",
38
39
  "autocannon": "^7.14.0",
39
40
  "changelogen": "^0.5.5",
40
41
  "connect": "^3.7.0",
41
- "eslint": "^8.55.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
+ "supertest": "^6.3.4",
52
53
  "typescript": "^5.3.3",
53
54
  "unbuild": "^2.0.0",
54
- "vitest": "^1.0.2",
55
+ "vitest": "^1.2.1",
55
56
  "zod": "^3.22.4"
56
57
  },
57
- "packageManager": "pnpm@8.11.0",
58
+ "packageManager": "pnpm@8.14.3",
58
59
  "scripts": {
59
60
  "build": "unbuild",
60
61
  "dev": "vitest",