h3 1.6.5 → 1.7.0

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
@@ -111,7 +111,7 @@ app.use('/1', eventHandler(() => '<h1>Hello world!</h1>'))
111
111
  .use('/2', eventHandler(() => '<h1>Goodbye!</h1>'))
112
112
 
113
113
  // We can proxy requests and rewrite cookie's domain and path
114
- app.use('/api', eventHandler((event) => proxyRequest('https://example.com', {
114
+ app.use('/api', eventHandler((event) => proxyRequest(event, 'https://example.com', {
115
115
  // f.e. keep one domain unchanged, rewrite one domain and remove other domains
116
116
  cookieDomainRewrite: {
117
117
  "example.com": "example.com",
package/dist/index.cjs CHANGED
@@ -182,7 +182,7 @@ function createError(input) {
182
182
  return err;
183
183
  }
184
184
  function sendError(event, error, debug) {
185
- if (event.node.res.writableEnded) {
185
+ if (event.handled) {
186
186
  return;
187
187
  }
188
188
  const h3Error = isError(error) ? error : createError(error);
@@ -195,7 +195,7 @@ function sendError(event, error, debug) {
195
195
  if (debug) {
196
196
  responseBody.stack = (h3Error.stack || "").split("\n").map((l) => l.trim());
197
197
  }
198
- if (event.node.res.writableEnded) {
198
+ if (event.handled) {
199
199
  return;
200
200
  }
201
201
  const _code = Number.parseInt(h3Error.statusCode);
@@ -257,23 +257,31 @@ function getRequestHeader(event, name) {
257
257
  return value;
258
258
  }
259
259
  const getHeader = getRequestHeader;
260
- function getRequestHost(event) {
261
- const xForwardedHost = event.node.req.headers["x-forwarded-host"];
262
- if (xForwardedHost) {
263
- return xForwardedHost;
260
+ function getRequestHost(event, opts = {}) {
261
+ if (opts.xForwardedHost) {
262
+ const xForwardedHost = event.node.req.headers["x-forwarded-host"];
263
+ if (xForwardedHost) {
264
+ return xForwardedHost;
265
+ }
264
266
  }
265
267
  return event.node.req.headers.host || "localhost";
266
268
  }
267
- function getRequestProtocol(event) {
268
- if (event.node.req.headers["x-forwarded-proto"] === "https") {
269
+ function getRequestProtocol(event, opts = {}) {
270
+ if (opts.xForwardedProto !== false && event.node.req.headers["x-forwarded-proto"] === "https") {
269
271
  return "https";
270
272
  }
271
273
  return event.node.req.connection.encrypted ? "https" : "http";
272
274
  }
273
- function getRequestURL(event) {
274
- const host = getRequestHost(event);
275
+ const DOUBLE_SLASH_RE = /[/\\]{2,}/g;
276
+ function getRequestPath(event) {
277
+ const path = (event.node.req.url || "/").replace(DOUBLE_SLASH_RE, "/");
278
+ return path;
279
+ }
280
+ function getRequestURL(event, opts = {}) {
281
+ const host = getRequestHost(event, opts);
275
282
  const protocol = getRequestProtocol(event);
276
- return new URL(event.path || "/", `${protocol}://${host}`);
283
+ const path = getRequestPath(event);
284
+ return new URL(path, `${protocol}://${host}`);
277
285
  }
278
286
 
279
287
  const RawBodySymbol = Symbol.for("h3RawBody");
@@ -283,9 +291,15 @@ function readRawBody(event, encoding = "utf8") {
283
291
  assertMethod(event, PayloadMethods$1);
284
292
  const _rawBody = event.node.req[RawBodySymbol] || event.node.req.body;
285
293
  if (_rawBody) {
286
- const promise2 = Promise.resolve(_rawBody).then(
287
- (_resolved) => Buffer.isBuffer(_resolved) ? _resolved : Buffer.from(_resolved)
288
- );
294
+ const promise2 = Promise.resolve(_rawBody).then((_resolved) => {
295
+ if (Buffer.isBuffer(_resolved)) {
296
+ return _resolved;
297
+ }
298
+ if (_resolved.constructor === Object) {
299
+ return Buffer.from(JSON.stringify(_resolved));
300
+ }
301
+ return Buffer.from(_resolved);
302
+ });
289
303
  return encoding ? promise2.then((buff) => buff.toString(encoding)) : promise2;
290
304
  }
291
305
  if (!Number.parseInt(event.node.req.headers["content-length"] || "")) {
@@ -370,7 +384,9 @@ function handleCacheHeaders(event, opts) {
370
384
  event.node.res.setHeader("cache-control", cacheControls.join(", "));
371
385
  if (cacheMatched) {
372
386
  event.node.res.statusCode = 304;
373
- event.node.res.end();
387
+ if (!event.handled) {
388
+ event.node.res.end();
389
+ }
374
390
  return true;
375
391
  }
376
392
  return false;
@@ -519,6 +535,7 @@ async function sendProxy(event, target, opts = {}) {
519
535
  event.node.res.statusCode
520
536
  );
521
537
  event.node.res.statusMessage = sanitizeStatusMessage(response.statusText);
538
+ const cookies = [];
522
539
  for (const [key, value] of response.headers.entries()) {
523
540
  if (key === "content-encoding") {
524
541
  continue;
@@ -527,7 +544,15 @@ async function sendProxy(event, target, opts = {}) {
527
544
  continue;
528
545
  }
529
546
  if (key === "set-cookie") {
530
- const cookies = splitCookiesString(value).map((cookie) => {
547
+ cookies.push(...splitCookiesString(value));
548
+ continue;
549
+ }
550
+ event.node.res.setHeader(key, value);
551
+ }
552
+ if (cookies.length > 0) {
553
+ event.node.res.setHeader(
554
+ "set-cookie",
555
+ cookies.map((cookie) => {
531
556
  if (opts.cookieDomainRewrite) {
532
557
  cookie = rewriteCookieProperty(
533
558
  cookie,
@@ -543,15 +568,18 @@ async function sendProxy(event, target, opts = {}) {
543
568
  );
544
569
  }
545
570
  return cookie;
546
- });
547
- event.node.res.setHeader("set-cookie", cookies);
548
- continue;
549
- }
550
- event.node.res.setHeader(key, value);
571
+ })
572
+ );
573
+ }
574
+ if (opts.onResponse) {
575
+ await opts.onResponse(event, response);
551
576
  }
552
577
  if (response._data !== void 0) {
553
578
  return response._data;
554
579
  }
580
+ if (event.handled) {
581
+ return;
582
+ }
555
583
  if (opts.sendStream === false) {
556
584
  const data = new Uint8Array(await response.arrayBuffer());
557
585
  return event.node.res.end(data);
@@ -610,14 +638,16 @@ function rewriteCookieProperty(header, map, property) {
610
638
  );
611
639
  }
612
640
 
613
- const defer = typeof setImmediate !== "undefined" ? setImmediate : (fn) => fn();
641
+ const defer = typeof setImmediate === "undefined" ? (fn) => fn() : setImmediate;
614
642
  function send(event, data, type) {
615
643
  if (type) {
616
644
  defaultContentType(event, type);
617
645
  }
618
646
  return new Promise((resolve) => {
619
647
  defer(() => {
620
- event.node.res.end(data);
648
+ if (!event.handled) {
649
+ event.node.res.end(data);
650
+ }
621
651
  resolve();
622
652
  });
623
653
  });
@@ -627,7 +657,9 @@ function sendNoContent(event, code = 204) {
627
657
  if (event.node.res.statusCode === 204) {
628
658
  event.node.res.removeHeader("content-length");
629
659
  }
630
- event.node.res.end();
660
+ if (!event.handled) {
661
+ event.node.res.end();
662
+ }
631
663
  }
632
664
  function setResponseStatus(event, code, text) {
633
665
  if (code) {
@@ -810,7 +842,7 @@ async function getSession(event, config) {
810
842
  Object.assign(session, unsealed);
811
843
  }
812
844
  if (!session.id) {
813
- session.id = (config.crypto || crypto__default).randomUUID();
845
+ session.id = config.generateId?.() ?? (config.crypto || crypto__default).randomUUID();
814
846
  session.createdAt = Date.now();
815
847
  await updateSession(event, config);
816
848
  }
@@ -1081,11 +1113,15 @@ class H3Response {
1081
1113
  class H3Event {
1082
1114
  constructor(req, res) {
1083
1115
  this["__is_event__"] = true;
1116
+ this._handled = false;
1084
1117
  this.context = {};
1085
1118
  this.node = { req, res };
1086
1119
  }
1087
1120
  get path() {
1088
- return this.req.url;
1121
+ return getRequestPath(this);
1122
+ }
1123
+ get handled() {
1124
+ return this._handled || this.node.res.writableEnded || this.node.res.headersSent;
1089
1125
  }
1090
1126
  /** @deprecated Please use `event.node.req` instead. **/
1091
1127
  get req() {
@@ -1098,35 +1134,37 @@ class H3Event {
1098
1134
  // Implementation of FetchEvent
1099
1135
  respondWith(r) {
1100
1136
  Promise.resolve(r).then((_response) => {
1101
- if (this.res.writableEnded) {
1137
+ if (this.handled) {
1102
1138
  return;
1103
1139
  }
1104
1140
  const response = _response instanceof H3Response ? _response : new H3Response(_response);
1105
1141
  for (const [key, value] of response.headers.entries()) {
1106
- this.res.setHeader(key, value);
1142
+ this.node.res.setHeader(key, value);
1107
1143
  }
1108
1144
  if (response.status) {
1109
- this.res.statusCode = sanitizeStatusCode(
1145
+ this.node.res.statusCode = sanitizeStatusCode(
1110
1146
  response.status,
1111
- this.res.statusCode
1147
+ this.node.res.statusCode
1112
1148
  );
1113
1149
  }
1114
1150
  if (response.statusText) {
1115
- this.res.statusMessage = sanitizeStatusMessage(response.statusText);
1151
+ this.node.res.statusMessage = sanitizeStatusMessage(
1152
+ response.statusText
1153
+ );
1116
1154
  }
1117
1155
  if (response.redirected) {
1118
- this.res.setHeader("location", response.url);
1156
+ this.node.res.setHeader("location", response.url);
1119
1157
  }
1120
1158
  if (!response._body) {
1121
- return this.res.end();
1159
+ return this.node.res.end();
1122
1160
  }
1123
1161
  if (typeof response._body === "string" || "buffer" in response._body || "byteLength" in response._body) {
1124
- return this.res.end(response._body);
1162
+ return this.node.res.end(response._body);
1125
1163
  }
1126
1164
  if (!response.headers.has("content-type")) {
1127
1165
  response.headers.set("content-type", MIMES.json);
1128
1166
  }
1129
- this.res.end(JSON.stringify(response._body));
1167
+ this.node.res.end(JSON.stringify(response._body));
1130
1168
  });
1131
1169
  }
1132
1170
  }
@@ -1252,7 +1290,7 @@ function createAppEventHandler(stack, options) {
1252
1290
  continue;
1253
1291
  }
1254
1292
  const val = await layer.handler(event);
1255
- if (event.node.res.writableEnded) {
1293
+ if (event.handled) {
1256
1294
  return;
1257
1295
  }
1258
1296
  const type = typeof val;
@@ -1277,10 +1315,10 @@ function createAppEventHandler(stack, options) {
1277
1315
  }
1278
1316
  }
1279
1317
  }
1280
- if (!event.node.res.writableEnded) {
1318
+ if (!event.handled) {
1281
1319
  throw createError({
1282
1320
  statusCode: 404,
1283
- statusMessage: `Cannot find any route matching ${event.node.req.url || "/"}.`
1321
+ statusMessage: `Cannot find any path matching ${event.node.req.url || "/"}.`
1284
1322
  });
1285
1323
  }
1286
1324
  });
@@ -1428,15 +1466,25 @@ function createRouter(opts = {}) {
1428
1466
  const method = (event.node.req.method || "get").toLowerCase();
1429
1467
  const handler = matched.handlers[method] || matched.handlers.all;
1430
1468
  if (!handler) {
1431
- throw createError({
1432
- statusCode: 405,
1433
- name: "Method Not Allowed",
1434
- statusMessage: `Method ${method} is not allowed on this route.`
1435
- });
1469
+ if (opts.preemptive || opts.preemtive) {
1470
+ throw createError({
1471
+ statusCode: 405,
1472
+ name: "Method Not Allowed",
1473
+ statusMessage: `Method ${method} is not allowed on this route.`
1474
+ });
1475
+ } else {
1476
+ return;
1477
+ }
1436
1478
  }
1437
1479
  const params = matched.params || {};
1438
1480
  event.context.params = params;
1439
- return handler(event);
1481
+ return Promise.resolve(handler(event)).then((res) => {
1482
+ if (res === void 0 && (opts.preemptive || opts.preemtive)) {
1483
+ setResponseStatus(event, 204);
1484
+ return "";
1485
+ }
1486
+ return res;
1487
+ });
1440
1488
  });
1441
1489
  return router;
1442
1490
  }
@@ -1479,6 +1527,7 @@ exports.getQuery = getQuery;
1479
1527
  exports.getRequestHeader = getRequestHeader;
1480
1528
  exports.getRequestHeaders = getRequestHeaders;
1481
1529
  exports.getRequestHost = getRequestHost;
1530
+ exports.getRequestPath = getRequestPath;
1482
1531
  exports.getRequestProtocol = getRequestProtocol;
1483
1532
  exports.getRequestURL = getRequestURL;
1484
1533
  exports.getResponseHeader = getResponseHeader;
package/dist/index.d.ts CHANGED
@@ -24,6 +24,8 @@ interface SessionConfig {
24
24
  sessionHeader?: false | string;
25
25
  seal?: SealOptions;
26
26
  crypto?: Crypto;
27
+ /** Default is Crypto.randomUUID */
28
+ generateId?: () => string;
27
29
  }
28
30
  declare function useSession<T extends SessionDataT = SessionDataT>(event: H3Event, config: SessionConfig): Promise<{
29
31
  readonly id: string | undefined;
@@ -105,10 +107,12 @@ interface NodeEventContext {
105
107
  }
106
108
  declare class H3Event implements Pick<FetchEvent, "respondWith"> {
107
109
  "__is_event__": boolean;
110
+ _handled: boolean;
108
111
  node: NodeEventContext;
109
112
  context: H3EventContext;
110
113
  constructor(req: IncomingMessage, res: ServerResponse);
111
- get path(): string | undefined;
114
+ get path(): string;
115
+ get handled(): boolean;
112
116
  /** @deprecated Please use `event.node.req` instead. **/
113
117
  get req(): IncomingMessage;
114
118
  /** @deprecated Please use `event.node.res` instead. **/
@@ -313,6 +317,7 @@ interface ProxyOptions {
313
317
  sendStream?: boolean;
314
318
  cookieDomainRewrite?: string | Record<string, string>;
315
319
  cookiePathRewrite?: string | Record<string, string>;
320
+ onResponse?: (event: H3Event, response: Response) => void;
316
321
  }
317
322
  declare function proxyRequest(event: H3Event, target: string, opts?: ProxyOptions): Promise<any>;
318
323
  declare function sendProxy(event: H3Event, target: string, opts?: ProxyOptions): Promise<any>;
@@ -324,8 +329,8 @@ declare function fetchWithEvent(event: H3Event, req: RequestInfo | URL, init?: R
324
329
  }): Promise<Response>;
325
330
 
326
331
  declare function getQuery(event: H3Event): ufo.QueryObject;
327
- declare function getRouterParams(event: H3Event): H3Event["context"];
328
- declare function getRouterParam(event: H3Event, name: string): H3Event["context"][string];
332
+ declare function getRouterParams(event: H3Event): NonNullable<H3Event["context"]["params"]>;
333
+ declare function getRouterParam(event: H3Event, name: string): string | undefined;
329
334
  declare function getMethod(event: H3Event, defaultMethod?: HTTPMethod): HTTPMethod;
330
335
  declare function isMethod(event: H3Event, expected: HTTPMethod | HTTPMethod[], allowHead?: boolean): boolean;
331
336
  declare function assertMethod(event: H3Event, expected: HTTPMethod | HTTPMethod[], allowHead?: boolean): void;
@@ -333,9 +338,17 @@ declare function getRequestHeaders(event: H3Event): RequestHeaders;
333
338
  declare const getHeaders: typeof getRequestHeaders;
334
339
  declare function getRequestHeader(event: H3Event, name: string): RequestHeaders[string];
335
340
  declare const getHeader: typeof getRequestHeader;
336
- declare function getRequestHost(event: H3Event): string;
337
- declare function getRequestProtocol(event: H3Event): "https" | "http";
338
- declare function getRequestURL(event: H3Event): URL;
341
+ declare function getRequestHost(event: H3Event, opts?: {
342
+ xForwardedHost?: boolean;
343
+ }): string;
344
+ declare function getRequestProtocol(event: H3Event, opts?: {
345
+ xForwardedProto?: boolean;
346
+ }): "https" | "http";
347
+ declare function getRequestPath(event: H3Event): string;
348
+ declare function getRequestURL(event: H3Event, opts?: {
349
+ xForwardedHost?: boolean;
350
+ xForwardedProto?: boolean;
351
+ }): URL;
339
352
 
340
353
  declare function send(event: H3Event, data?: any, type?: string): Promise<void>;
341
354
  /**
@@ -402,4 +415,4 @@ interface CreateRouterOptions {
402
415
  }
403
416
  declare function createRouter(opts?: CreateRouterOptions): Router;
404
417
 
405
- export { AddRouteShortcuts, App, AppOptions, AppUse, CacheConditions, CreateRouterOptions, DynamicEventHandler, Encoding, EventHandler, EventHandlerResponse, H3CorsOptions, H3Error, H3Event, H3EventContext, H3Headers, H3Response, HTTPMethod, InputLayer, InputStack, Layer, LazyEventHandler, MIMES, Matcher, MultiPartData, NodeEventContext, NodeListener, NodeMiddleware, NodePromisifiedHandler, ProxyOptions, RequestHeaders, Router, RouterMethod, RouterUse, Session, SessionConfig, SessionData, Stack, appendCorsHeaders, appendCorsPreflightHeaders, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getRequestHost, getRequestProtocol, getRequestURL, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, handleCors, isCorsOriginAllowed, isError, isEvent, isEventHandler, isMethod, isPreflightRequest, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, sanitizeStatusCode, sanitizeStatusMessage, sealSession, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, splitCookiesString, toEventHandler, toNodeListener, unsealSession, updateSession, use, useBase, useSession, writeEarlyHints };
418
+ export { AddRouteShortcuts, App, AppOptions, AppUse, CacheConditions, CreateRouterOptions, DynamicEventHandler, Encoding, EventHandler, EventHandlerResponse, H3CorsOptions, H3Error, H3Event, H3EventContext, H3Headers, H3Response, HTTPMethod, InputLayer, InputStack, Layer, LazyEventHandler, MIMES, Matcher, MultiPartData, NodeEventContext, NodeListener, NodeMiddleware, NodePromisifiedHandler, ProxyOptions, RequestHeaders, Router, RouterMethod, RouterUse, Session, SessionConfig, SessionData, Stack, appendCorsHeaders, appendCorsPreflightHeaders, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getRequestHost, getRequestPath, getRequestProtocol, getRequestURL, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, handleCors, isCorsOriginAllowed, isError, isEvent, isEventHandler, isMethod, isPreflightRequest, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, sanitizeStatusCode, sanitizeStatusMessage, sealSession, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, splitCookiesString, toEventHandler, toNodeListener, unsealSession, updateSession, use, useBase, useSession, writeEarlyHints };
package/dist/index.mjs CHANGED
@@ -175,7 +175,7 @@ function createError(input) {
175
175
  return err;
176
176
  }
177
177
  function sendError(event, error, debug) {
178
- if (event.node.res.writableEnded) {
178
+ if (event.handled) {
179
179
  return;
180
180
  }
181
181
  const h3Error = isError(error) ? error : createError(error);
@@ -188,7 +188,7 @@ function sendError(event, error, debug) {
188
188
  if (debug) {
189
189
  responseBody.stack = (h3Error.stack || "").split("\n").map((l) => l.trim());
190
190
  }
191
- if (event.node.res.writableEnded) {
191
+ if (event.handled) {
192
192
  return;
193
193
  }
194
194
  const _code = Number.parseInt(h3Error.statusCode);
@@ -250,23 +250,31 @@ function getRequestHeader(event, name) {
250
250
  return value;
251
251
  }
252
252
  const getHeader = getRequestHeader;
253
- function getRequestHost(event) {
254
- const xForwardedHost = event.node.req.headers["x-forwarded-host"];
255
- if (xForwardedHost) {
256
- return xForwardedHost;
253
+ function getRequestHost(event, opts = {}) {
254
+ if (opts.xForwardedHost) {
255
+ const xForwardedHost = event.node.req.headers["x-forwarded-host"];
256
+ if (xForwardedHost) {
257
+ return xForwardedHost;
258
+ }
257
259
  }
258
260
  return event.node.req.headers.host || "localhost";
259
261
  }
260
- function getRequestProtocol(event) {
261
- if (event.node.req.headers["x-forwarded-proto"] === "https") {
262
+ function getRequestProtocol(event, opts = {}) {
263
+ if (opts.xForwardedProto !== false && event.node.req.headers["x-forwarded-proto"] === "https") {
262
264
  return "https";
263
265
  }
264
266
  return event.node.req.connection.encrypted ? "https" : "http";
265
267
  }
266
- function getRequestURL(event) {
267
- const host = getRequestHost(event);
268
+ const DOUBLE_SLASH_RE = /[/\\]{2,}/g;
269
+ function getRequestPath(event) {
270
+ const path = (event.node.req.url || "/").replace(DOUBLE_SLASH_RE, "/");
271
+ return path;
272
+ }
273
+ function getRequestURL(event, opts = {}) {
274
+ const host = getRequestHost(event, opts);
268
275
  const protocol = getRequestProtocol(event);
269
- return new URL(event.path || "/", `${protocol}://${host}`);
276
+ const path = getRequestPath(event);
277
+ return new URL(path, `${protocol}://${host}`);
270
278
  }
271
279
 
272
280
  const RawBodySymbol = Symbol.for("h3RawBody");
@@ -276,9 +284,15 @@ function readRawBody(event, encoding = "utf8") {
276
284
  assertMethod(event, PayloadMethods$1);
277
285
  const _rawBody = event.node.req[RawBodySymbol] || event.node.req.body;
278
286
  if (_rawBody) {
279
- const promise2 = Promise.resolve(_rawBody).then(
280
- (_resolved) => Buffer.isBuffer(_resolved) ? _resolved : Buffer.from(_resolved)
281
- );
287
+ const promise2 = Promise.resolve(_rawBody).then((_resolved) => {
288
+ if (Buffer.isBuffer(_resolved)) {
289
+ return _resolved;
290
+ }
291
+ if (_resolved.constructor === Object) {
292
+ return Buffer.from(JSON.stringify(_resolved));
293
+ }
294
+ return Buffer.from(_resolved);
295
+ });
282
296
  return encoding ? promise2.then((buff) => buff.toString(encoding)) : promise2;
283
297
  }
284
298
  if (!Number.parseInt(event.node.req.headers["content-length"] || "")) {
@@ -363,7 +377,9 @@ function handleCacheHeaders(event, opts) {
363
377
  event.node.res.setHeader("cache-control", cacheControls.join(", "));
364
378
  if (cacheMatched) {
365
379
  event.node.res.statusCode = 304;
366
- event.node.res.end();
380
+ if (!event.handled) {
381
+ event.node.res.end();
382
+ }
367
383
  return true;
368
384
  }
369
385
  return false;
@@ -512,6 +528,7 @@ async function sendProxy(event, target, opts = {}) {
512
528
  event.node.res.statusCode
513
529
  );
514
530
  event.node.res.statusMessage = sanitizeStatusMessage(response.statusText);
531
+ const cookies = [];
515
532
  for (const [key, value] of response.headers.entries()) {
516
533
  if (key === "content-encoding") {
517
534
  continue;
@@ -520,7 +537,15 @@ async function sendProxy(event, target, opts = {}) {
520
537
  continue;
521
538
  }
522
539
  if (key === "set-cookie") {
523
- const cookies = splitCookiesString(value).map((cookie) => {
540
+ cookies.push(...splitCookiesString(value));
541
+ continue;
542
+ }
543
+ event.node.res.setHeader(key, value);
544
+ }
545
+ if (cookies.length > 0) {
546
+ event.node.res.setHeader(
547
+ "set-cookie",
548
+ cookies.map((cookie) => {
524
549
  if (opts.cookieDomainRewrite) {
525
550
  cookie = rewriteCookieProperty(
526
551
  cookie,
@@ -536,15 +561,18 @@ async function sendProxy(event, target, opts = {}) {
536
561
  );
537
562
  }
538
563
  return cookie;
539
- });
540
- event.node.res.setHeader("set-cookie", cookies);
541
- continue;
542
- }
543
- event.node.res.setHeader(key, value);
564
+ })
565
+ );
566
+ }
567
+ if (opts.onResponse) {
568
+ await opts.onResponse(event, response);
544
569
  }
545
570
  if (response._data !== void 0) {
546
571
  return response._data;
547
572
  }
573
+ if (event.handled) {
574
+ return;
575
+ }
548
576
  if (opts.sendStream === false) {
549
577
  const data = new Uint8Array(await response.arrayBuffer());
550
578
  return event.node.res.end(data);
@@ -603,14 +631,16 @@ function rewriteCookieProperty(header, map, property) {
603
631
  );
604
632
  }
605
633
 
606
- const defer = typeof setImmediate !== "undefined" ? setImmediate : (fn) => fn();
634
+ const defer = typeof setImmediate === "undefined" ? (fn) => fn() : setImmediate;
607
635
  function send(event, data, type) {
608
636
  if (type) {
609
637
  defaultContentType(event, type);
610
638
  }
611
639
  return new Promise((resolve) => {
612
640
  defer(() => {
613
- event.node.res.end(data);
641
+ if (!event.handled) {
642
+ event.node.res.end(data);
643
+ }
614
644
  resolve();
615
645
  });
616
646
  });
@@ -620,7 +650,9 @@ function sendNoContent(event, code = 204) {
620
650
  if (event.node.res.statusCode === 204) {
621
651
  event.node.res.removeHeader("content-length");
622
652
  }
623
- event.node.res.end();
653
+ if (!event.handled) {
654
+ event.node.res.end();
655
+ }
624
656
  }
625
657
  function setResponseStatus(event, code, text) {
626
658
  if (code) {
@@ -803,7 +835,7 @@ async function getSession(event, config) {
803
835
  Object.assign(session, unsealed);
804
836
  }
805
837
  if (!session.id) {
806
- session.id = (config.crypto || crypto).randomUUID();
838
+ session.id = config.generateId?.() ?? (config.crypto || crypto).randomUUID();
807
839
  session.createdAt = Date.now();
808
840
  await updateSession(event, config);
809
841
  }
@@ -1074,11 +1106,15 @@ class H3Response {
1074
1106
  class H3Event {
1075
1107
  constructor(req, res) {
1076
1108
  this["__is_event__"] = true;
1109
+ this._handled = false;
1077
1110
  this.context = {};
1078
1111
  this.node = { req, res };
1079
1112
  }
1080
1113
  get path() {
1081
- return this.req.url;
1114
+ return getRequestPath(this);
1115
+ }
1116
+ get handled() {
1117
+ return this._handled || this.node.res.writableEnded || this.node.res.headersSent;
1082
1118
  }
1083
1119
  /** @deprecated Please use `event.node.req` instead. **/
1084
1120
  get req() {
@@ -1091,35 +1127,37 @@ class H3Event {
1091
1127
  // Implementation of FetchEvent
1092
1128
  respondWith(r) {
1093
1129
  Promise.resolve(r).then((_response) => {
1094
- if (this.res.writableEnded) {
1130
+ if (this.handled) {
1095
1131
  return;
1096
1132
  }
1097
1133
  const response = _response instanceof H3Response ? _response : new H3Response(_response);
1098
1134
  for (const [key, value] of response.headers.entries()) {
1099
- this.res.setHeader(key, value);
1135
+ this.node.res.setHeader(key, value);
1100
1136
  }
1101
1137
  if (response.status) {
1102
- this.res.statusCode = sanitizeStatusCode(
1138
+ this.node.res.statusCode = sanitizeStatusCode(
1103
1139
  response.status,
1104
- this.res.statusCode
1140
+ this.node.res.statusCode
1105
1141
  );
1106
1142
  }
1107
1143
  if (response.statusText) {
1108
- this.res.statusMessage = sanitizeStatusMessage(response.statusText);
1144
+ this.node.res.statusMessage = sanitizeStatusMessage(
1145
+ response.statusText
1146
+ );
1109
1147
  }
1110
1148
  if (response.redirected) {
1111
- this.res.setHeader("location", response.url);
1149
+ this.node.res.setHeader("location", response.url);
1112
1150
  }
1113
1151
  if (!response._body) {
1114
- return this.res.end();
1152
+ return this.node.res.end();
1115
1153
  }
1116
1154
  if (typeof response._body === "string" || "buffer" in response._body || "byteLength" in response._body) {
1117
- return this.res.end(response._body);
1155
+ return this.node.res.end(response._body);
1118
1156
  }
1119
1157
  if (!response.headers.has("content-type")) {
1120
1158
  response.headers.set("content-type", MIMES.json);
1121
1159
  }
1122
- this.res.end(JSON.stringify(response._body));
1160
+ this.node.res.end(JSON.stringify(response._body));
1123
1161
  });
1124
1162
  }
1125
1163
  }
@@ -1245,7 +1283,7 @@ function createAppEventHandler(stack, options) {
1245
1283
  continue;
1246
1284
  }
1247
1285
  const val = await layer.handler(event);
1248
- if (event.node.res.writableEnded) {
1286
+ if (event.handled) {
1249
1287
  return;
1250
1288
  }
1251
1289
  const type = typeof val;
@@ -1270,10 +1308,10 @@ function createAppEventHandler(stack, options) {
1270
1308
  }
1271
1309
  }
1272
1310
  }
1273
- if (!event.node.res.writableEnded) {
1311
+ if (!event.handled) {
1274
1312
  throw createError({
1275
1313
  statusCode: 404,
1276
- statusMessage: `Cannot find any route matching ${event.node.req.url || "/"}.`
1314
+ statusMessage: `Cannot find any path matching ${event.node.req.url || "/"}.`
1277
1315
  });
1278
1316
  }
1279
1317
  });
@@ -1421,17 +1459,27 @@ function createRouter(opts = {}) {
1421
1459
  const method = (event.node.req.method || "get").toLowerCase();
1422
1460
  const handler = matched.handlers[method] || matched.handlers.all;
1423
1461
  if (!handler) {
1424
- throw createError({
1425
- statusCode: 405,
1426
- name: "Method Not Allowed",
1427
- statusMessage: `Method ${method} is not allowed on this route.`
1428
- });
1462
+ if (opts.preemptive || opts.preemtive) {
1463
+ throw createError({
1464
+ statusCode: 405,
1465
+ name: "Method Not Allowed",
1466
+ statusMessage: `Method ${method} is not allowed on this route.`
1467
+ });
1468
+ } else {
1469
+ return;
1470
+ }
1429
1471
  }
1430
1472
  const params = matched.params || {};
1431
1473
  event.context.params = params;
1432
- return handler(event);
1474
+ return Promise.resolve(handler(event)).then((res) => {
1475
+ if (res === void 0 && (opts.preemptive || opts.preemtive)) {
1476
+ setResponseStatus(event, 204);
1477
+ return "";
1478
+ }
1479
+ return res;
1480
+ });
1433
1481
  });
1434
1482
  return router;
1435
1483
  }
1436
1484
 
1437
- export { H3Error, H3Event, H3Headers, H3Response, MIMES, appendCorsHeaders, appendCorsPreflightHeaders, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getRequestHost, getRequestProtocol, getRequestURL, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, handleCors, isCorsOriginAllowed, isError, isEvent, isEventHandler, isMethod, isPreflightRequest, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, sanitizeStatusCode, sanitizeStatusMessage, sealSession, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, splitCookiesString, toEventHandler, toNodeListener, unsealSession, updateSession, use, useBase, useSession, writeEarlyHints };
1485
+ export { H3Error, H3Event, H3Headers, H3Response, MIMES, appendCorsHeaders, appendCorsPreflightHeaders, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getRequestHost, getRequestPath, getRequestProtocol, getRequestURL, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, handleCors, isCorsOriginAllowed, isError, isEvent, isEventHandler, isMethod, isPreflightRequest, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, sanitizeStatusCode, sanitizeStatusMessage, sealSession, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, splitCookiesString, toEventHandler, toNodeListener, unsealSession, updateSession, use, useBase, useSession, writeEarlyHints };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "h3",
3
- "version": "1.6.5",
3
+ "version": "1.7.0",
4
4
  "description": "Tiny JavaScript Server",
5
5
  "repository": "unjs/h3",
6
6
  "license": "MIT",
@@ -19,46 +19,46 @@
19
19
  "files": [
20
20
  "dist"
21
21
  ],
22
+ "scripts": {
23
+ "build": "unbuild",
24
+ "dev": "vitest",
25
+ "lint": "eslint --cache --ext .ts,.js,.mjs,.cjs . && prettier -c src test playground",
26
+ "lint:fix": "eslint --cache --ext .ts,.js,.mjs,.cjs . --fix && prettier -c src test playground -w",
27
+ "play": "jiti ./playground/index.ts",
28
+ "profile": "0x -o -D .profile -P 'autocannon -c 100 -p 10 -d 40 http://localhost:$PORT' ./playground/server.cjs",
29
+ "release": "pnpm test && pnpm build && changelogen --release && pnpm publish && git push --follow-tags",
30
+ "test": "pnpm lint && vitest run --coverage"
31
+ },
22
32
  "dependencies": {
23
33
  "cookie-es": "^1.0.0",
24
34
  "defu": "^6.1.2",
25
- "destr": "^1.2.2",
35
+ "destr": "^2.0.0",
26
36
  "iron-webcrypto": "^0.7.0",
27
37
  "radix3": "^1.0.1",
28
38
  "ufo": "^1.1.2",
29
- "uncrypto": "^0.1.2"
39
+ "uncrypto": "^0.1.3"
30
40
  },
31
41
  "devDependencies": {
32
42
  "0x": "^5.5.0",
33
43
  "@types/express": "^4.17.17",
34
- "@types/node": "^20.0.0",
44
+ "@types/node": "^20.3.1",
35
45
  "@types/supertest": "^2.0.12",
36
- "@vitest/coverage-c8": "^0.31.0",
46
+ "@vitest/coverage-v8": "^0.32.2",
37
47
  "autocannon": "^7.11.0",
38
48
  "changelogen": "^0.5.3",
39
49
  "connect": "^3.7.0",
40
- "eslint": "^8.39.0",
41
- "eslint-config-unjs": "^0.1.0",
50
+ "eslint": "^8.43.0",
51
+ "eslint-config-unjs": "^0.2.1",
42
52
  "express": "^4.18.2",
43
- "get-port": "^6.1.2",
53
+ "get-port": "^7.0.0",
44
54
  "jiti": "^1.18.2",
45
55
  "listhen": "^1.0.4",
46
- "node-fetch-native": "^1.1.0",
56
+ "node-fetch-native": "^1.2.0",
47
57
  "prettier": "^2.8.8",
48
58
  "supertest": "^6.3.3",
49
- "typescript": "^5.0.4",
59
+ "typescript": "^5.1.3",
50
60
  "unbuild": "^1.2.1",
51
- "vitest": "^0.31.0"
61
+ "vitest": "^0.32.2"
52
62
  },
53
- "packageManager": "pnpm@8.4.0",
54
- "scripts": {
55
- "build": "unbuild",
56
- "dev": "vitest",
57
- "lint": "eslint --cache --ext .ts,.js,.mjs,.cjs . && prettier -c src test playground",
58
- "lint:fix": "eslint --cache --ext .ts,.js,.mjs,.cjs . --fix && prettier -c src test playground -w",
59
- "play": "jiti ./playground/index.ts",
60
- "profile": "0x -o -D .profile -P 'autocannon -c 100 -p 10 -d 40 http://localhost:$PORT' ./playground/server.cjs",
61
- "release": "pnpm test && pnpm build && changelogen --release && pnpm publish && git push --follow-tags",
62
- "test": "pnpm lint && vitest run --coverage"
63
- }
63
+ "packageManager": "pnpm@8.6.3"
64
64
  }