routup 6.0.0-beta.3 → 6.0.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.
@@ -490,6 +490,152 @@ function setResponseHeaderContentType(event, input, ifNotExists) {
490
490
  if (contentType) event.response.headers.set(HeaderName.CONTENT_TYPE, contentType);
491
491
  }
492
492
  //#endregion
493
+ //#region src/response/helpers/send-file.ts
494
+ async function sendFile(event, options) {
495
+ let stats;
496
+ if (typeof options.stats === "function") stats = await options.stats();
497
+ else stats = options.stats;
498
+ const name = options.name || stats.name;
499
+ const { headers } = event.response;
500
+ const disposition = options.disposition ?? (options.attachment ? "attachment" : void 0);
501
+ if (name) {
502
+ const fileName = basename(name);
503
+ if (disposition) {
504
+ if (!headers.get(HeaderName.CONTENT_DISPOSITION)) if (disposition === "inline") setResponseHeaderInline(event, fileName);
505
+ else setResponseHeaderAttachment(event, fileName);
506
+ } else setResponseContentTypeByFileName(event, fileName);
507
+ }
508
+ const contentOptions = {};
509
+ let statusCode = event.response.status;
510
+ if (stats.size) {
511
+ const rangeHeader = event.headers.get(HeaderName.RANGE);
512
+ if (rangeHeader) {
513
+ const [x, y] = rangeHeader.replace("bytes=", "").split("-");
514
+ const parsedStart = Number.parseInt(x, 10);
515
+ const parsedEnd = Number.parseInt(y, 10);
516
+ contentOptions.start = Number.isFinite(parsedStart) && parsedStart >= 0 ? parsedStart : 0;
517
+ contentOptions.end = Number.isFinite(parsedEnd) && parsedEnd >= 0 ? Math.min(parsedEnd, stats.size - 1) : stats.size - 1;
518
+ if (contentOptions.start >= stats.size || contentOptions.start > contentOptions.end) {
519
+ const rangeHeaders = new Headers(headers);
520
+ rangeHeaders.set(HeaderName.CONTENT_RANGE, `bytes */${stats.size}`);
521
+ return new Response(null, {
522
+ status: 416,
523
+ headers: rangeHeaders
524
+ });
525
+ }
526
+ headers.set(HeaderName.CONTENT_RANGE, `bytes ${contentOptions.start}-${contentOptions.end}/${stats.size}`);
527
+ headers.set(HeaderName.CONTENT_LENGTH, `${contentOptions.end - contentOptions.start + 1}`);
528
+ statusCode = 206;
529
+ } else headers.set(HeaderName.CONTENT_LENGTH, `${stats.size}`);
530
+ headers.set(HeaderName.ACCEPT_RANGES, "bytes");
531
+ if (stats.mtime) {
532
+ const mtime = new Date(stats.mtime);
533
+ headers.set(HeaderName.LAST_MODIFIED, mtime.toUTCString());
534
+ headers.set(HeaderName.ETag, `W/"${stats.size}-${mtime.getTime()}"`);
535
+ }
536
+ }
537
+ const content = await options.content(contentOptions);
538
+ return new Response(content, {
539
+ status: statusCode,
540
+ headers
541
+ });
542
+ }
543
+ //#endregion
544
+ //#region src/request/helpers/header.ts
545
+ function getRequestHeader(event, name) {
546
+ return event.headers.get(name);
547
+ }
548
+ //#endregion
549
+ //#region src/request/helpers/negotiator.ts
550
+ const NEGOTIATOR_KEY = Symbol.for("routup:negotiator");
551
+ function headersToPlainObject(headers) {
552
+ const result = {};
553
+ headers.forEach((value, key) => {
554
+ result[key] = value;
555
+ });
556
+ return result;
557
+ }
558
+ function useRequestNegotiator(event) {
559
+ let value = event.store[NEGOTIATOR_KEY];
560
+ if (value) return value;
561
+ value = new Negotiator({ headers: headersToPlainObject(event.headers) });
562
+ event.store[NEGOTIATOR_KEY] = value;
563
+ return value;
564
+ }
565
+ //#endregion
566
+ //#region src/request/helpers/header-accept.ts
567
+ function getRequestAcceptableContentTypes(event) {
568
+ return useRequestNegotiator(event).mediaTypes();
569
+ }
570
+ function getRequestAcceptableContentType(event, input) {
571
+ input = input || [];
572
+ const items = Array.isArray(input) ? input : [input];
573
+ if (items.length === 0) return getRequestAcceptableContentTypes(event).shift();
574
+ if (!getRequestHeader(event, HeaderName.ACCEPT)) return items[0];
575
+ let polluted = false;
576
+ const mimeTypes = [];
577
+ for (const item of items) {
578
+ const mimeType = getMimeType(item);
579
+ if (mimeType) mimeTypes.push(mimeType);
580
+ else polluted = true;
581
+ }
582
+ const matches = useRequestNegotiator(event).mediaTypes(mimeTypes);
583
+ if (matches.length > 0) {
584
+ if (polluted) return items[0];
585
+ return items[mimeTypes.indexOf(matches[0])];
586
+ }
587
+ }
588
+ //#endregion
589
+ //#region src/response/helpers/send-format.ts
590
+ function sendFormat(event, input) {
591
+ const { default: formatDefault, ...formats } = input;
592
+ const contentTypes = Object.keys(formats);
593
+ if (contentTypes.length === 0) return formatDefault();
594
+ const contentType = getRequestAcceptableContentType(event, contentTypes);
595
+ if (contentType && formats[contentType]) return formats[contentType]();
596
+ return formatDefault();
597
+ }
598
+ //#endregion
599
+ //#region src/response/helpers/send-redirect.ts
600
+ function escapeHtml(str) {
601
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
602
+ }
603
+ function isAllowedRedirectUrl(location) {
604
+ if (location.startsWith("//")) return false;
605
+ if (location.startsWith("/") || location.startsWith(".")) return true;
606
+ try {
607
+ const url = new URL(location);
608
+ return url.protocol === "http:" || url.protocol === "https:";
609
+ } catch {
610
+ return true;
611
+ }
612
+ }
613
+ function sendRedirect(event, location, statusCode = 302) {
614
+ if (!isAllowedRedirectUrl(location)) throw new AppError({
615
+ status: 400,
616
+ message: "Invalid redirect URL scheme."
617
+ });
618
+ const sanitizedLocation = sanitizeHeaderValue(location);
619
+ const html = `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${escapeHtml(location)}"></head></html>`;
620
+ const headers = new Headers(event.response.headers);
621
+ headers.set("location", sanitizedLocation);
622
+ headers.set("content-type", "text/html; charset=utf-8");
623
+ headers.delete("content-length");
624
+ return new Response(html, {
625
+ status: statusCode,
626
+ headers
627
+ });
628
+ }
629
+ //#endregion
630
+ //#region src/response/helpers/send-stream.ts
631
+ function sendStream(event, stream) {
632
+ const { status, headers } = event.response;
633
+ return new Response(stream, {
634
+ status,
635
+ headers
636
+ });
637
+ }
638
+ //#endregion
493
639
  //#region src/error/is.ts
494
640
  function isError(input) {
495
641
  return hasInstanceof(input, ErrorSymbol);
@@ -638,164 +784,6 @@ function toResponse(value, event) {
638
784
  });
639
785
  }
640
786
  //#endregion
641
- //#region src/response/helpers/send-accepted.ts
642
- async function sendAccepted(event, data) {
643
- event.response.status = 202;
644
- return await toResponse(data ?? "", event);
645
- }
646
- //#endregion
647
- //#region src/response/helpers/send-created.ts
648
- async function sendCreated(event, data) {
649
- event.response.status = 201;
650
- return await toResponse(data ?? "", event);
651
- }
652
- //#endregion
653
- //#region src/response/helpers/send-file.ts
654
- async function sendFile(event, options) {
655
- let stats;
656
- if (typeof options.stats === "function") stats = await options.stats();
657
- else stats = options.stats;
658
- const name = options.name || stats.name;
659
- const { headers } = event.response;
660
- const disposition = options.disposition ?? (options.attachment ? "attachment" : void 0);
661
- if (name) {
662
- const fileName = basename(name);
663
- if (disposition) {
664
- if (!headers.get(HeaderName.CONTENT_DISPOSITION)) if (disposition === "inline") setResponseHeaderInline(event, fileName);
665
- else setResponseHeaderAttachment(event, fileName);
666
- } else setResponseContentTypeByFileName(event, fileName);
667
- }
668
- const contentOptions = {};
669
- let statusCode = event.response.status;
670
- if (stats.size) {
671
- const rangeHeader = event.headers.get(HeaderName.RANGE);
672
- if (rangeHeader) {
673
- const [x, y] = rangeHeader.replace("bytes=", "").split("-");
674
- const parsedStart = Number.parseInt(x, 10);
675
- const parsedEnd = Number.parseInt(y, 10);
676
- contentOptions.start = Number.isFinite(parsedStart) && parsedStart >= 0 ? parsedStart : 0;
677
- contentOptions.end = Number.isFinite(parsedEnd) && parsedEnd >= 0 ? Math.min(parsedEnd, stats.size - 1) : stats.size - 1;
678
- if (contentOptions.start >= stats.size || contentOptions.start > contentOptions.end) {
679
- const rangeHeaders = new Headers(headers);
680
- rangeHeaders.set(HeaderName.CONTENT_RANGE, `bytes */${stats.size}`);
681
- return new Response(null, {
682
- status: 416,
683
- headers: rangeHeaders
684
- });
685
- }
686
- headers.set(HeaderName.CONTENT_RANGE, `bytes ${contentOptions.start}-${contentOptions.end}/${stats.size}`);
687
- headers.set(HeaderName.CONTENT_LENGTH, `${contentOptions.end - contentOptions.start + 1}`);
688
- statusCode = 206;
689
- } else headers.set(HeaderName.CONTENT_LENGTH, `${stats.size}`);
690
- headers.set(HeaderName.ACCEPT_RANGES, "bytes");
691
- if (stats.mtime) {
692
- const mtime = new Date(stats.mtime);
693
- headers.set(HeaderName.LAST_MODIFIED, mtime.toUTCString());
694
- headers.set(HeaderName.ETag, `W/"${stats.size}-${mtime.getTime()}"`);
695
- }
696
- }
697
- const content = await options.content(contentOptions);
698
- return new Response(content, {
699
- status: statusCode,
700
- headers
701
- });
702
- }
703
- //#endregion
704
- //#region src/request/helpers/header.ts
705
- function getRequestHeader(event, name) {
706
- return event.headers.get(name);
707
- }
708
- //#endregion
709
- //#region src/request/helpers/negotiator.ts
710
- const NEGOTIATOR_KEY = Symbol.for("routup:negotiator");
711
- function headersToPlainObject(headers) {
712
- const result = {};
713
- headers.forEach((value, key) => {
714
- result[key] = value;
715
- });
716
- return result;
717
- }
718
- function useRequestNegotiator(event) {
719
- let value = event.store[NEGOTIATOR_KEY];
720
- if (value) return value;
721
- value = new Negotiator({ headers: headersToPlainObject(event.headers) });
722
- event.store[NEGOTIATOR_KEY] = value;
723
- return value;
724
- }
725
- //#endregion
726
- //#region src/request/helpers/header-accept.ts
727
- function getRequestAcceptableContentTypes(event) {
728
- return useRequestNegotiator(event).mediaTypes();
729
- }
730
- function getRequestAcceptableContentType(event, input) {
731
- input = input || [];
732
- const items = Array.isArray(input) ? input : [input];
733
- if (items.length === 0) return getRequestAcceptableContentTypes(event).shift();
734
- if (!getRequestHeader(event, HeaderName.ACCEPT)) return items[0];
735
- let polluted = false;
736
- const mimeTypes = [];
737
- for (const item of items) {
738
- const mimeType = getMimeType(item);
739
- if (mimeType) mimeTypes.push(mimeType);
740
- else polluted = true;
741
- }
742
- const matches = useRequestNegotiator(event).mediaTypes(mimeTypes);
743
- if (matches.length > 0) {
744
- if (polluted) return items[0];
745
- return items[mimeTypes.indexOf(matches[0])];
746
- }
747
- }
748
- //#endregion
749
- //#region src/response/helpers/send-format.ts
750
- function sendFormat(event, input) {
751
- const { default: formatDefault, ...formats } = input;
752
- const contentTypes = Object.keys(formats);
753
- if (contentTypes.length === 0) return formatDefault();
754
- const contentType = getRequestAcceptableContentType(event, contentTypes);
755
- if (contentType && formats[contentType]) return formats[contentType]();
756
- return formatDefault();
757
- }
758
- //#endregion
759
- //#region src/response/helpers/send-redirect.ts
760
- function escapeHtml(str) {
761
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
762
- }
763
- function isAllowedRedirectUrl(location) {
764
- if (location.startsWith("//")) return false;
765
- if (location.startsWith("/") || location.startsWith(".")) return true;
766
- try {
767
- const url = new URL(location);
768
- return url.protocol === "http:" || url.protocol === "https:";
769
- } catch {
770
- return true;
771
- }
772
- }
773
- function sendRedirect(event, location, statusCode = 302) {
774
- if (!isAllowedRedirectUrl(location)) throw new AppError({
775
- status: 400,
776
- message: "Invalid redirect URL scheme."
777
- });
778
- const sanitizedLocation = sanitizeHeaderValue(location);
779
- const html = `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${escapeHtml(location)}"></head></html>`;
780
- const headers = new Headers(event.response.headers);
781
- headers.set("location", sanitizedLocation);
782
- headers.set("content-type", "text/html; charset=utf-8");
783
- headers.delete("content-length");
784
- return new Response(html, {
785
- status: statusCode,
786
- headers
787
- });
788
- }
789
- //#endregion
790
- //#region src/response/helpers/send-stream.ts
791
- function sendStream(event, stream) {
792
- const { status, headers } = event.response;
793
- return new Response(stream, {
794
- status,
795
- headers
796
- });
797
- }
798
- //#endregion
799
787
  //#region src/dispatcher/module.ts
800
788
  var DispatcherEvent = class {
801
789
  request;
@@ -2491,7 +2479,7 @@ var App = class App {
2491
2479
  let response;
2492
2480
  try {
2493
2481
  const matches = this.router.lookup(event.path, event.method);
2494
- response = await this.runMatches(event, matches, event.path, 0);
2482
+ response = await this.runMatches(event, matches, 0);
2495
2483
  if (!event.error && !event.dispatched && isRoot && event.method === MethodName.OPTIONS) {
2496
2484
  if (event.methodsAllowed.has(MethodName.GET)) event.methodsAllowed.add(MethodName.HEAD);
2497
2485
  const options = [...event.methodsAllowed].map((key) => key.toUpperCase()).join(",");
@@ -2518,8 +2506,16 @@ var App = class App {
2518
2506
  * Walk the matched routes for the current event, dispatching each
2519
2507
  * handler in order. Re-entered (recursively) from the `setNext`
2520
2508
  * continuation so `event.next()` resumes from the next match.
2509
+ *
2510
+ * The match list is captured once per dispatch — there is no
2511
+ * mid-walk path-rewrite refresh. `IAppEvent.path` is a snapshot
2512
+ * on the handler's facade event (see `event/module.ts`), so user
2513
+ * middleware cannot mutate the dispatcher's path before calling
2514
+ * `event.next()`. If a future API surface lets middleware rewrite
2515
+ * paths end-to-end, this loop will need a per-call refresh + a
2516
+ * `methodsAllowed` reset (see closed issue #913).
2521
2517
  */
2522
- async runMatches(event, matches, matchesPath, startIndex) {
2518
+ async runMatches(event, matches, startIndex) {
2523
2519
  let i = startIndex;
2524
2520
  let response;
2525
2521
  while (!event.dispatched && i < matches.length) {
@@ -2539,15 +2535,10 @@ var App = class App {
2539
2535
  const savedMountPath = event.mountPath;
2540
2536
  if (typeof match.path === "string") event.mountPath = match.path;
2541
2537
  const capturedMatches = matches;
2542
- const capturedMatchesPath = matchesPath;
2543
2538
  const nextIndex = i + 1;
2544
2539
  event.setNext(async (error) => {
2545
2540
  if (error) event.error = createError(error);
2546
- const pathChanged = event.path !== capturedMatchesPath;
2547
- const nextMatches = pathChanged ? this.router.lookup(event.path, event.method) : capturedMatches;
2548
- const nextMatchesPath = pathChanged ? event.path : capturedMatchesPath;
2549
- const nextStart = pathChanged ? 0 : nextIndex;
2550
- return this.runMatches(event, nextMatches, nextMatchesPath, nextStart);
2541
+ return this.runMatches(event, capturedMatches, nextIndex);
2551
2542
  });
2552
2543
  try {
2553
2544
  const dispatchResponse = await handler.dispatch(event);
@@ -2755,6 +2746,6 @@ var App = class App {
2755
2746
  }
2756
2747
  };
2757
2748
  //#endregion
2758
- export { setResponseHeaderContentType as $, isWebHandler as A, sendStream as B, getRequestAcceptableCharset as C, isHandler as D, matchHandlerMethod as E, defineCoreHandler as F, useRequestNegotiator as G, sendFormat as H, Handler as I, sendCreated as J, getRequestHeader as K, HandlerSymbol as L, fromNodeHandler as M, fromNodeMiddleware as N, isHandlerOptions as O, defineErrorHandler as P, isError as Q, HandlerType as R, getRequestAcceptableEncodings as S, isRequestCacheable as T, getRequestAcceptableContentType as U, sendRedirect as V, getRequestAcceptableContentTypes as W, toResponse as X, sendAccepted as Y, createError as Z, getRequestHostName as _, LinearRouter as a, createEventStream as at, getRequestAcceptableLanguages as b, PluginNotInstalledError as c, ErrorSymbol as ct, isPluginError as d, HeaderName as dt, setResponseHeaderAttachment as et, PluginErrorCode as f, MethodName as ft, getRequestIP as g, getRequestProtocol as h, TrieRouter as i, appendResponseHeaderDirective as it, isWebHandlerProvider as j, fromWebHandler as k, PluginInstallError as l, setResponseCacheHeaders as lt, PathMatcher as m, normalizeAppOptions as n, setResponseContentTypeByFileName as nt, buildRoutePathMatcher as o, serializeEventStreamMessage as ot, isPath as p, LruCache as pt, sendFile as q, SmartRouter as r, appendResponseHeader as rt, isPlugin as s, AppError as st, App as t, setResponseHeaderInline as tt, PluginError as u, AppEvent as ut, matchRequestContentType as v, getRequestAcceptableCharsets as w, getRequestAcceptableEncoding as x, getRequestAcceptableLanguage as y, DispatcherEvent as z };
2749
+ export { setResponseHeaderInline as $, isWebHandler as A, toResponse as B, getRequestAcceptableCharset as C, isHandler as D, matchHandlerMethod as E, defineCoreHandler as F, sendFormat as G, isError as H, Handler as I, useRequestNegotiator as J, getRequestAcceptableContentType as K, HandlerSymbol as L, fromNodeHandler as M, fromNodeMiddleware as N, isHandlerOptions as O, defineErrorHandler as P, setResponseHeaderAttachment as Q, HandlerType as R, getRequestAcceptableEncodings as S, isRequestCacheable as T, sendStream as U, createError as V, sendRedirect as W, sendFile as X, getRequestHeader as Y, setResponseHeaderContentType as Z, getRequestHostName as _, LinearRouter as a, AppError as at, getRequestAcceptableLanguages as b, PluginNotInstalledError as c, AppEvent as ct, isPluginError as d, LruCache as dt, setResponseContentTypeByFileName as et, PluginErrorCode as f, getRequestIP as g, getRequestProtocol as h, TrieRouter as i, serializeEventStreamMessage as it, isWebHandlerProvider as j, fromWebHandler as k, PluginInstallError as l, HeaderName as lt, PathMatcher as m, normalizeAppOptions as n, appendResponseHeaderDirective as nt, buildRoutePathMatcher as o, ErrorSymbol as ot, isPath as p, getRequestAcceptableContentTypes as q, SmartRouter as r, createEventStream as rt, isPlugin as s, setResponseCacheHeaders as st, App as t, appendResponseHeader as tt, PluginError as u, MethodName as ut, matchRequestContentType as v, getRequestAcceptableCharsets as w, getRequestAcceptableEncoding as x, getRequestAcceptableLanguage as y, DispatcherEvent as z };
2759
2750
 
2760
- //# sourceMappingURL=src-KLwWuArk.mjs.map
2751
+ //# sourceMappingURL=src-CA6xFXqy.mjs.map