h3 1.10.0 → 1.10.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/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
  }
@@ -1053,7 +1069,8 @@ const ignoredHeaders = /* @__PURE__ */ new Set([
1053
1069
  "keep-alive",
1054
1070
  "upgrade",
1055
1071
  "expect",
1056
- "host"
1072
+ "host",
1073
+ "accept"
1057
1074
  ]);
1058
1075
  async function proxyRequest(event, target, opts = {}) {
1059
1076
  let body;
@@ -1215,6 +1232,7 @@ function mergeHeaders(defaults, ...inputs) {
1215
1232
  return merged;
1216
1233
  }
1217
1234
 
1235
+ const getSessionPromise = Symbol("getSession");
1218
1236
  const DEFAULT_NAME = "h3";
1219
1237
  const DEFAULT_COOKIE = {
1220
1238
  path: "/",
@@ -1247,8 +1265,9 @@ async function getSession(event, config) {
1247
1265
  if (!event.context.sessions) {
1248
1266
  event.context.sessions = /* @__PURE__ */ Object.create(null);
1249
1267
  }
1250
- if (event.context.sessions[sessionName]) {
1251
- return event.context.sessions[sessionName];
1268
+ const existingSession = event.context.sessions[sessionName];
1269
+ if (existingSession) {
1270
+ return existingSession[getSessionPromise] || existingSession;
1252
1271
  }
1253
1272
  const session = {
1254
1273
  id: "",
@@ -1268,11 +1287,14 @@ async function getSession(event, config) {
1268
1287
  sealedSession = getCookie(event, sessionName);
1269
1288
  }
1270
1289
  if (sealedSession) {
1271
- const unsealed = await unsealSession(event, config, sealedSession).catch(
1272
- () => {
1273
- }
1274
- );
1275
- Object.assign(session, unsealed);
1290
+ const promise = unsealSession(event, config, sealedSession).catch(() => {
1291
+ }).then((unsealed) => {
1292
+ Object.assign(session, unsealed);
1293
+ delete event.context.sessions[sessionName][getSessionPromise];
1294
+ return session;
1295
+ });
1296
+ event.context.sessions[sessionName][getSessionPromise] = promise;
1297
+ await promise;
1276
1298
  }
1277
1299
  if (!session.id) {
1278
1300
  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
  }
@@ -1046,7 +1062,8 @@ const ignoredHeaders = /* @__PURE__ */ new Set([
1046
1062
  "keep-alive",
1047
1063
  "upgrade",
1048
1064
  "expect",
1049
- "host"
1065
+ "host",
1066
+ "accept"
1050
1067
  ]);
1051
1068
  async function proxyRequest(event, target, opts = {}) {
1052
1069
  let body;
@@ -1208,6 +1225,7 @@ function mergeHeaders(defaults, ...inputs) {
1208
1225
  return merged;
1209
1226
  }
1210
1227
 
1228
+ const getSessionPromise = Symbol("getSession");
1211
1229
  const DEFAULT_NAME = "h3";
1212
1230
  const DEFAULT_COOKIE = {
1213
1231
  path: "/",
@@ -1240,8 +1258,9 @@ async function getSession(event, config) {
1240
1258
  if (!event.context.sessions) {
1241
1259
  event.context.sessions = /* @__PURE__ */ Object.create(null);
1242
1260
  }
1243
- if (event.context.sessions[sessionName]) {
1244
- return event.context.sessions[sessionName];
1261
+ const existingSession = event.context.sessions[sessionName];
1262
+ if (existingSession) {
1263
+ return existingSession[getSessionPromise] || existingSession;
1245
1264
  }
1246
1265
  const session = {
1247
1266
  id: "",
@@ -1261,11 +1280,14 @@ async function getSession(event, config) {
1261
1280
  sealedSession = getCookie(event, sessionName);
1262
1281
  }
1263
1282
  if (sealedSession) {
1264
- const unsealed = await unsealSession(event, config, sealedSession).catch(
1265
- () => {
1266
- }
1267
- );
1268
- Object.assign(session, unsealed);
1283
+ const promise = unsealSession(event, config, sealedSession).catch(() => {
1284
+ }).then((unsealed) => {
1285
+ Object.assign(session, unsealed);
1286
+ delete event.context.sessions[sessionName][getSessionPromise];
1287
+ return session;
1288
+ });
1289
+ event.context.sessions[sessionName][getSessionPromise] = promise;
1290
+ await promise;
1269
1291
  }
1270
1292
  if (!session.id) {
1271
1293
  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.2",
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",