@orieljs/framework 0.1.0-alpha.3 → 0.1.0-alpha.4

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.
@@ -9,7 +9,7 @@ import {
9
9
  currentContainer,
10
10
  facade,
11
11
  inject
12
- } from "./chunk-HU43NADT.js";
12
+ } from "./chunk-DCI2T7WJ.js";
13
13
  import {
14
14
  Application,
15
15
  AuthFactory,
@@ -17,6 +17,7 @@ import {
17
17
  ClassRegistry,
18
18
  ConfigRepository,
19
19
  ConsoleKernel,
20
+ CookieJar,
20
21
  CurrentRoute,
21
22
  DatabaseManager,
22
23
  Encrypter,
@@ -27,18 +28,22 @@ import {
27
28
  HttpKernel,
28
29
  HttpRequest,
29
30
  LiveManager,
31
+ LogManager,
30
32
  MailManager,
31
33
  MiddlewareCallbacks,
32
34
  NotificationDispatcher,
35
+ PipelineFactory,
33
36
  QueueManager,
34
37
  Redirector,
38
+ ResponseFactory,
35
39
  Router,
36
40
  RoutingPaths,
37
41
  Session,
38
42
  UrlGenerator,
39
43
  ValidationFactory,
40
- ViewFactory
41
- } from "./chunk-S7YC43IH.js";
44
+ ViewFactory,
45
+ token
46
+ } from "./chunk-WSGWYXAO.js";
42
47
 
43
48
  // ../Config/dist/index.js
44
49
  import { existsSync, readFileSync } from "fs";
@@ -88,12 +93,20 @@ function env(key, defaultValue) {
88
93
  const quoted = /^(["'])(.*)\1$/s.exec(raw);
89
94
  return quoted ? quoted[2] : raw;
90
95
  }
96
+ function singleQuoted(source) {
97
+ const keys = /* @__PURE__ */ new Set();
98
+ for (const match of source.matchAll(/^[ \t]*(?:export[ \t]+)?([\w.-]+)[ \t]*=[ \t]*'/gm)) keys.add(match[1]);
99
+ return keys;
100
+ }
91
101
  function loadEnvironment(basePath) {
92
102
  const appEnv = process.env.APP_ENV;
93
103
  const specific = appEnv ? join(basePath, `.env.${appEnv}`) : void 0;
94
104
  const file = specific && existsSync(specific) ? specific : join(basePath, ".env");
95
105
  if (!existsSync(file)) return void 0;
96
- for (const [key, value] of Object.entries(parse(readFileSync(file)))) {
106
+ const source = readFileSync(file, "utf8");
107
+ const literal = singleQuoted(source);
108
+ for (const [key, raw] of Object.entries(parse(source))) {
109
+ const value = literal.has(key) ? raw : raw.replace(/\$\{([A-Za-z_][A-Za-z0-9_.]*)\}/g, (match, name) => process.env[name] ?? match);
97
110
  if (process.env[key] === void 0) process.env[key] = value;
98
111
  }
99
112
  return file;
@@ -140,10 +153,11 @@ var ApplicationBuilder = class {
140
153
  return this;
141
154
  }
142
155
  withRouting(options) {
143
- const abs = (p) => p === void 0 ? void 0 : isAbsolute(p) ? p : join4(this.app.basePath(), p);
156
+ const abs = (p) => isAbsolute(p) ? p : join4(this.app.basePath(), p);
157
+ const each = (p) => Array.isArray(p) ? p.map(abs) : abs(p);
144
158
  const paths = { apiPrefix: options.apiPrefix ?? "api" };
145
- if (options.web !== void 0) paths.web = abs(options.web);
146
- if (options.api !== void 0) paths.api = abs(options.api);
159
+ if (options.web !== void 0) paths.web = each(options.web);
160
+ if (options.api !== void 0) paths.api = each(options.api);
147
161
  if (options.console !== void 0) paths.console = abs(options.console);
148
162
  if (options.health !== void 0) paths.health = options.health;
149
163
  this.routing = paths;
@@ -323,9 +337,9 @@ var Vite = class {
323
337
  const stylesheets = uniqueTags.filter((tag) => tag.startsWith("<link"));
324
338
  const scripts = uniqueTags.filter((tag) => !tag.startsWith("<link"));
325
339
  const seen = /* @__PURE__ */ new Set();
326
- const uniquePreloads = preloads.filter(([, url]) => seen.has(url) ? false : (seen.add(url), true));
327
- const sorted = [...uniquePreloads.filter(([, url]) => CSS_PATH.test(url)), ...uniquePreloads.filter(([, url]) => !CSS_PATH.test(url))];
328
- const preloadTags = sorted.map(([src, url, chunk2]) => this.#preloadTag(src, url, chunk2, manifest));
340
+ const uniquePreloads = preloads.filter(([, url2]) => seen.has(url2) ? false : (seen.add(url2), true));
341
+ const sorted = [...uniquePreloads.filter(([, url2]) => CSS_PATH.test(url2)), ...uniquePreloads.filter(([, url2]) => !CSS_PATH.test(url2))];
342
+ const preloadTags = sorted.map(([src, url2, chunk2]) => this.#preloadTag(src, url2, chunk2, manifest));
329
343
  return new ViteHtml(preloadTags.join("") + stylesheets.join("") + scripts.join(""));
330
344
  }
331
345
  /** Laravel's @viteReactRefresh: the React Refresh preamble while the dev server runs; nothing otherwise. */
@@ -410,41 +424,41 @@ var Vite = class {
410
424
  }
411
425
  return imports;
412
426
  }
413
- #tagForChunk(src, url, chunk2, manifest) {
414
- if (CSS_PATH.test(url)) {
415
- const attributes2 = this.#parseAttributes({ rel: "stylesheet", href: url, nonce: this.#nonce ?? false, ...this.#styleAttributes(src, url, chunk2, manifest) });
427
+ #tagForChunk(src, url2, chunk2, manifest) {
428
+ if (CSS_PATH.test(url2)) {
429
+ const attributes2 = this.#parseAttributes({ rel: "stylesheet", href: url2, nonce: this.#nonce ?? false, ...this.#styleAttributes(src, url2, chunk2, manifest) });
416
430
  return `<link ${attributes2.join(" ")} />`;
417
431
  }
418
- const attributes = this.#parseAttributes({ type: "module", src: url, nonce: this.#nonce ?? false, ...this.#scriptAttributes(src, url, chunk2, manifest) });
432
+ const attributes = this.#parseAttributes({ type: "module", src: url2, nonce: this.#nonce ?? false, ...this.#scriptAttributes(src, url2, chunk2, manifest) });
419
433
  return `<script ${attributes.join(" ")}></script>`;
420
434
  }
421
- #preloadTag(src, url, chunk2, manifest) {
422
- const css = CSS_PATH.test(url);
435
+ #preloadTag(src, url2, chunk2, manifest) {
436
+ const css = CSS_PATH.test(url2);
423
437
  let attributes = {
424
438
  rel: css ? "preload" : "modulepreload",
425
439
  as: css ? "style" : "script",
426
- href: url,
440
+ href: url2,
427
441
  nonce: this.#nonce ?? false,
428
- crossorigin: (css ? this.#styleAttributes(src, url, chunk2, manifest) : this.#scriptAttributes(src, url, chunk2, manifest)).crossorigin ?? false
442
+ crossorigin: (css ? this.#styleAttributes(src, url2, chunk2, manifest) : this.#scriptAttributes(src, url2, chunk2, manifest)).crossorigin ?? false
429
443
  };
430
444
  if (this.#integrityKey !== false) attributes.integrity = chunk2?.[this.#integrityKey] ?? false;
431
445
  for (const resolver of this.#preloadResolvers) {
432
- const resolved = resolver(src, url, chunk2, manifest);
446
+ const resolved = resolver(src, url2, chunk2, manifest);
433
447
  if (resolved === false) return "";
434
448
  attributes = { ...attributes, ...resolved };
435
449
  }
436
450
  const { href: _href, ...rest } = attributes;
437
- this.#preloaded[url] = this.#parseAttributes(rest);
451
+ this.#preloaded[url2] = this.#parseAttributes(rest);
438
452
  return `<link ${this.#parseAttributes(attributes).join(" ")} />`;
439
453
  }
440
- #scriptAttributes(src, url, chunk2, manifest) {
454
+ #scriptAttributes(src, url2, chunk2, manifest) {
441
455
  let attributes = this.#integrityKey !== false ? { integrity: chunk2?.[this.#integrityKey] ?? false } : {};
442
- for (const resolver of this.#scriptResolvers) attributes = { ...attributes, ...resolver(src, url, chunk2, manifest) };
456
+ for (const resolver of this.#scriptResolvers) attributes = { ...attributes, ...resolver(src, url2, chunk2, manifest) };
443
457
  return attributes;
444
458
  }
445
- #styleAttributes(src, url, chunk2, manifest) {
459
+ #styleAttributes(src, url2, chunk2, manifest) {
446
460
  let attributes = this.#integrityKey !== false ? { integrity: chunk2?.[this.#integrityKey] ?? false } : {};
447
- for (const resolver of this.#styleResolvers) attributes = { ...attributes, ...resolver(src, url, chunk2, manifest) };
461
+ for (const resolver of this.#styleResolvers) attributes = { ...attributes, ...resolver(src, url2, chunk2, manifest) };
448
462
  return attributes;
449
463
  }
450
464
  /** false/null/undefined are dropped, true is a bare attribute, anything else name="value". */
@@ -476,7 +490,7 @@ var Application2 = class _Application extends Container {
476
490
  this.scoped(Vite, () => new Vite(this));
477
491
  }
478
492
  base;
479
- version = "0.1.0-alpha.3";
493
+ version = "0.1.0-alpha.4";
480
494
  providers = [];
481
495
  bootingCallbacks = [];
482
496
  bootedCallbacks = [];
@@ -566,10 +580,10 @@ var Application2 = class _Application extends Container {
566
580
  terminating(callback) {
567
581
  this.terminatingCallbacks.push(callback);
568
582
  }
569
- async handle(request) {
583
+ async handle(request2) {
570
584
  await this.boot();
571
585
  const scope = this.createScope();
572
- return scope.run(() => scope.make(HttpKernel).handle(request));
586
+ return scope.run(() => scope.make(HttpKernel).handle(request2));
573
587
  }
574
588
  async terminate() {
575
589
  for (const callback of this.terminatingCallbacks) await callback();
@@ -594,10 +608,10 @@ var ClosureKernel = class {
594
608
  this.routes.set(`${method.toUpperCase()} ${path}`, handler);
595
609
  return this;
596
610
  }
597
- async handle(request) {
598
- const handler = this.routes.get(`${request.method} ${new URL(request.url).pathname}`);
611
+ async handle(request2) {
612
+ const handler = this.routes.get(`${request2.method} ${new URL(request2.url).pathname}`);
599
613
  if (!handler) return new Response("Not Found", { status: 404, headers: { "content-type": "text/plain; charset=utf-8" } });
600
- return toResponse(await handler(request));
614
+ return toResponse(await handler(request2));
601
615
  }
602
616
  };
603
617
  function toResponse(output) {
@@ -607,18 +621,1852 @@ function toResponse(output) {
607
621
  return Response.json(output);
608
622
  }
609
623
 
624
+ // ../Http/dist/index.js
625
+ import { createReadStream } from "fs";
626
+ import { stat } from "fs/promises";
627
+ import { basename } from "path";
628
+ import { Readable } from "stream";
629
+ import { readFile } from "fs/promises";
630
+ import { fileURLToPath } from "url";
631
+ import { createReadStream as createReadStream2 } from "fs";
632
+ import { stat as stat2 } from "fs/promises";
633
+ import { Readable as Readable2 } from "stream";
634
+ import { relative, resolve, sep } from "path";
635
+ import { createServer } from "http";
636
+ import { Readable as Readable3 } from "stream";
637
+ import { pipeline } from "stream/promises";
638
+ import { existsSync as existsSync4 } from "fs";
639
+ import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
640
+ import { dirname } from "path";
641
+ var HttpException = class extends Error {
642
+ constructor(status, message = "", headers = {}) {
643
+ super(message);
644
+ this.status = status;
645
+ this.headers = headers;
646
+ }
647
+ status;
648
+ headers;
649
+ kind = "Http";
650
+ name = "HttpException";
651
+ };
652
+ var HttpResponseException = class extends Error {
653
+ constructor(response2) {
654
+ super(`HTTP response exception (${response2.status}).`);
655
+ this.response = response2;
656
+ this.status = response2.status;
657
+ }
658
+ response;
659
+ kind = "Http";
660
+ name = "HttpResponseException";
661
+ status;
662
+ };
663
+ var HttpServerError = class extends Error {
664
+ kind = "Server";
665
+ status = 500;
666
+ name = "HttpServerError";
667
+ };
668
+ function exceedsJsonDepth(text, maxDepth) {
669
+ let depth = 0;
670
+ let inString = false;
671
+ for (let i = 0; i < text.length; i++) {
672
+ const code = text.charCodeAt(i);
673
+ if (inString) {
674
+ if (code === 92) i++;
675
+ else if (code === 34) inString = false;
676
+ } else if (code === 34) inString = true;
677
+ else if (code === 123 || code === 91) {
678
+ if (++depth > maxDepth) return true;
679
+ } else if (code === 125 || code === 93) depth--;
680
+ }
681
+ return false;
682
+ }
683
+ async function readBody(raw, limit) {
684
+ if (raw.body === null) return new Uint8Array(0);
685
+ const declared = Number(raw.headers.get("content-length") ?? NaN);
686
+ if (limit !== null && Number.isFinite(declared) && declared > limit) {
687
+ await raw.body.cancel();
688
+ throw new HttpException(413, "Content Too Large");
689
+ }
690
+ const reader = raw.body.getReader();
691
+ const chunks = [];
692
+ let size = 0;
693
+ for (; ; ) {
694
+ const { done, value } = await reader.read();
695
+ if (done) break;
696
+ size += value.byteLength;
697
+ if (limit !== null && size > limit) {
698
+ await reader.cancel();
699
+ throw new HttpException(413, "Content Too Large");
700
+ }
701
+ chunks.push(value);
702
+ }
703
+ const out = new Uint8Array(size);
704
+ let offset = 0;
705
+ for (const chunk2 of chunks) {
706
+ out.set(chunk2, offset);
707
+ offset += chunk2.byteLength;
708
+ }
709
+ return out;
710
+ }
711
+ function requestScope() {
712
+ const container = currentContainer();
713
+ return container !== void 0 && container.bound(HttpRequest) ? container : void 0;
714
+ }
715
+ function currentRequest() {
716
+ return requestScope()?.make(HttpRequest);
717
+ }
718
+ function optionalSession() {
719
+ const scope = requestScope();
720
+ return scope?.bound(Session) ? scope.make(Session) : void 0;
721
+ }
722
+ function optionalCookieJar() {
723
+ const scope = requestScope();
724
+ return scope?.bound(CookieJar) ? scope.make(CookieJar) : void 0;
725
+ }
726
+ function serializeCookie(cookie) {
727
+ const parts = [`${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value)}`, `Path=${cookie.path}`];
728
+ if (cookie.domain !== void 0) parts.push(`Domain=${cookie.domain}`);
729
+ if (cookie.secure) parts.push("Secure");
730
+ if (cookie.httpOnly) parts.push("HttpOnly");
731
+ parts.push(`SameSite=${cookie.sameSite.charAt(0).toUpperCase()}${cookie.sameSite.slice(1)}`);
732
+ if (cookie.minutes < 0) parts.push("Max-Age=0", "Expires=Thu, 01 Jan 1970 00:00:00 GMT");
733
+ else if (cookie.minutes > 0) parts.push(`Max-Age=${Math.round(cookie.minutes * 60)}`);
734
+ return parts.join("; ");
735
+ }
736
+ var HttpRedirectResponse = class {
737
+ constructor(targetUrl, status = 302, headers) {
738
+ this.targetUrl = targetUrl;
739
+ this.status = status;
740
+ this.#headers = new Headers(headers);
741
+ }
742
+ targetUrl;
743
+ status;
744
+ #headers;
745
+ with(key, value) {
746
+ const session = optionalSession();
747
+ if (session === void 0) return this;
748
+ if (typeof key === "string") session.flash(key, value);
749
+ else for (const [name, item] of Object.entries(key)) session.flash(name, item);
750
+ return this;
751
+ }
752
+ /** Flashes `errors` as { [bag]: { field: string[] } }, keeping the other bags already flashed. */
753
+ withErrors(errors, bag = "default") {
754
+ const session = optionalSession();
755
+ if (session === void 0) return this;
756
+ const source = typeof errors.toObject === "function" ? errors.toObject() : errors;
757
+ const normalized = Object.fromEntries(
758
+ Object.entries(source).map(([field, messages]) => [field, Array.isArray(messages) ? [...messages] : [messages]])
759
+ );
760
+ const existing = session.get("errors", {});
761
+ session.flash("errors", { ...existing, [bag]: normalized });
762
+ return this;
763
+ }
764
+ /** Flashes `input`, or the current request's input (files are never part of input()). */
765
+ withInput(input) {
766
+ const session = optionalSession();
767
+ if (session === void 0) return this;
768
+ session.flashInput(input ?? currentRequest()?.input() ?? {});
769
+ return this;
770
+ }
771
+ /**
772
+ * Always a Set-Cookie header on this response (Laravel's RedirectResponse::withCookie). A bound CookieJar only
773
+ * supplies the defaults (path, domain, secure, same-site); queueing would lose the cookie outside the web group.
774
+ */
775
+ withCookie(name, value, minutes = 0) {
776
+ const jar = optionalCookieJar();
777
+ const cookie = jar !== void 0 ? jar.make(name, value, minutes) : { name, value, minutes, path: "/", secure: false, httpOnly: true, sameSite: "lax" };
778
+ this.#headers.append("Set-Cookie", serializeCookie(cookie));
779
+ return this;
780
+ }
781
+ header(name, value) {
782
+ this.#headers.set(name, value);
783
+ return this;
784
+ }
785
+ toResponse() {
786
+ const headers = new Headers(this.#headers);
787
+ headers.set("Location", this.targetUrl);
788
+ return new Response(null, { status: this.status, headers });
789
+ }
790
+ };
791
+ function pathOf(url2) {
792
+ let parsed;
793
+ try {
794
+ parsed = new URL(url2, "http://localhost");
795
+ } catch {
796
+ return "/";
797
+ }
798
+ return `${parsed.pathname.replace(/^\/+/, "/")}${parsed.search}${parsed.hash}`;
799
+ }
800
+ var HttpRedirector = class {
801
+ to(path, status = 302, headers) {
802
+ return new HttpRedirectResponse(inject(UrlGenerator).to(path), status, headers);
803
+ }
804
+ route(name, parameters, status = 302) {
805
+ return new HttpRedirectResponse(inject(UrlGenerator).route(name, parameters), status);
806
+ }
807
+ back(fallback, status = 302) {
808
+ return new HttpRedirectResponse(inject(UrlGenerator).previous(fallback), status);
809
+ }
810
+ /** Pulls url.intended from the session (put there by guest()), else goes to `fallback`. */
811
+ intended(fallback = "/", status = 302) {
812
+ const path = optionalSession()?.pull("url.intended") ?? fallback;
813
+ return this.to(path, status);
814
+ }
815
+ away(url2, status = 302) {
816
+ return new HttpRedirectResponse(url2, status);
817
+ }
818
+ /**
819
+ * Remembers where a guest was going (a GET page, else the previous URL), then redirects to `path`. Only the path
820
+ * is stored, so intended() goes back on whichever host serves the app, and never to another site's Referer.
821
+ */
822
+ guest(path, status = 302) {
823
+ const session = optionalSession();
824
+ const request2 = currentRequest();
825
+ if (session !== void 0) {
826
+ const intended = request2 !== void 0 && request2.method() === "GET" && !request2.expectsJson() ? request2.fullUrl() : inject(UrlGenerator).previous();
827
+ session.put("url.intended", pathOf(intended));
828
+ }
829
+ return this.to(path, status);
830
+ }
831
+ setIntendedUrl(url2) {
832
+ optionalSession()?.put("url.intended", url2);
833
+ }
834
+ refresh(status = 302) {
835
+ return this.to(currentRequest()?.fullUrl() ?? inject(UrlGenerator).current(), status);
836
+ }
837
+ };
838
+ var KEY = /* @__PURE__ */ Symbol.for("oriel:http.client-address");
839
+ var store = globalThis[KEY] ??= /* @__PURE__ */ new WeakMap();
840
+ function setClientAddress(request2, address) {
841
+ if (address !== void 0 && address !== "") store.set(request2, address);
842
+ }
843
+ function clientAddress(request2) {
844
+ return store.get(request2);
845
+ }
846
+ var TYPES = {
847
+ avif: "image/avif",
848
+ css: "text/css",
849
+ csv: "text/csv",
850
+ doc: "application/msword",
851
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
852
+ gif: "image/gif",
853
+ gz: "application/gzip",
854
+ html: "text/html",
855
+ ico: "image/vnd.microsoft.icon",
856
+ jpg: "image/jpeg",
857
+ js: "text/javascript",
858
+ map: "application/json",
859
+ json: "application/json",
860
+ md: "text/markdown",
861
+ mp3: "audio/mpeg",
862
+ mp4: "video/mp4",
863
+ otf: "font/otf",
864
+ pdf: "application/pdf",
865
+ png: "image/png",
866
+ svg: "image/svg+xml",
867
+ ttf: "font/ttf",
868
+ txt: "text/plain",
869
+ wasm: "application/wasm",
870
+ webm: "video/webm",
871
+ webp: "image/webp",
872
+ woff: "font/woff",
873
+ woff2: "font/woff2",
874
+ xls: "application/vnd.ms-excel",
875
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
876
+ xml: "application/xml",
877
+ zip: "application/zip"
878
+ };
879
+ var ALIASES = { jpeg: "jpg", htm: "html", mjs: "js", text: "txt" };
880
+ var EXTENSIONS = Object.fromEntries(Object.entries(TYPES).map(([extension, type]) => [type, extension]));
881
+ function mimeFromExtension(pathOrExtension) {
882
+ const extension = pathOrExtension.slice(pathOrExtension.lastIndexOf(".") + 1).toLowerCase();
883
+ return TYPES[ALIASES[extension] ?? extension] ?? "application/octet-stream";
884
+ }
885
+ function extensionFromMime(type) {
886
+ return EXTENSIONS[type.split(";")[0].trim().toLowerCase()];
887
+ }
888
+ var FormUploadedFile = class {
889
+ constructor(file) {
890
+ this.file = file;
891
+ this.clientName = file.name ?? "";
892
+ this.mimeType = file.type === "" ? "application/octet-stream" : file.type;
893
+ this.size = file.size;
894
+ }
895
+ file;
896
+ clientName;
897
+ mimeType;
898
+ size;
899
+ /** Guessed from the MIME type, falling back to the client's extension (Laravel's extension()). */
900
+ extension() {
901
+ return extensionFromMime(this.mimeType) ?? this.clientExtension();
902
+ }
903
+ clientExtension() {
904
+ const dot = this.clientName.lastIndexOf(".");
905
+ return dot === -1 ? "" : this.clientName.slice(dot + 1).toLowerCase();
906
+ }
907
+ /** A random 40-character name with the guessed extension. */
908
+ hashName() {
909
+ const extension = this.extension();
910
+ return extension === "" ? Str.random(40) : `${Str.random(40)}.${extension}`;
911
+ }
912
+ arrayBuffer() {
913
+ return this.file.arrayBuffer();
914
+ }
915
+ text() {
916
+ return this.file.text();
917
+ }
918
+ stream() {
919
+ return this.file.stream();
920
+ }
921
+ store(directory, disk) {
922
+ return inject(FilesystemManager).disk(disk).putFile(directory, this);
923
+ }
924
+ storeAs(directory, name, disk) {
925
+ return inject(FilesystemManager).disk(disk).putFileAs(directory, this, name);
926
+ }
927
+ };
928
+ var FORBIDDEN = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
929
+ var INTEGER_KEY = /^(?:0|[1-9]\d*)$/;
930
+ var isPlainObject = (value) => {
931
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
932
+ const prototype = Object.getPrototypeOf(value);
933
+ return prototype === Object.prototype || prototype === null;
934
+ };
935
+ function parseBracketKey(name) {
936
+ const open = name.indexOf("[");
937
+ if (open <= 0 || !name.endsWith("]")) return [name];
938
+ const segments = [name.slice(0, open)];
939
+ const rest = name.slice(open);
940
+ const matches2 = rest.match(/\[[^[\]]*\]/g);
941
+ if (matches2 === null || matches2.join("") !== rest) return [name];
942
+ for (const match of matches2) segments.push(match.slice(1, -1));
943
+ return segments;
944
+ }
945
+ function normalizeLists(value) {
946
+ if (Array.isArray(value)) return value.map(normalizeLists);
947
+ if (!isPlainObject(value)) return value;
948
+ const keys = Object.keys(value);
949
+ const out = {};
950
+ for (const key of keys) out[key] = normalizeLists(value[key]);
951
+ if (keys.length > 0 && keys.every((key, index) => key === String(index))) return keys.map((key) => out[key]);
952
+ return out;
953
+ }
954
+ function fromEntries(entries) {
955
+ const root = {};
956
+ const nextIndex = /* @__PURE__ */ new WeakMap();
957
+ for (const [name, value] of entries) {
958
+ const segments = parseBracketKey(name);
959
+ if (segments.some((segment) => FORBIDDEN.has(segment))) continue;
960
+ let node = root;
961
+ for (let i = 0; i < segments.length; i++) {
962
+ let segment = segments[i];
963
+ const next = nextIndex.get(node) ?? 0;
964
+ if (segment === "") {
965
+ segment = String(next);
966
+ nextIndex.set(node, next + 1);
967
+ } else if (INTEGER_KEY.test(segment) && Number(segment) >= next) {
968
+ nextIndex.set(node, Number(segment) + 1);
969
+ }
970
+ if (i === segments.length - 1) {
971
+ node[segment] = value;
972
+ break;
973
+ }
974
+ if (!isPlainObject(node[segment])) node[segment] = {};
975
+ node = node[segment];
976
+ }
977
+ }
978
+ return normalizeLists(root);
979
+ }
980
+ function sanitize(value) {
981
+ if (Array.isArray(value)) return value.map(sanitize);
982
+ if (!isPlainObject(value)) return value;
983
+ const out = {};
984
+ for (const [key, item] of Object.entries(value)) if (!FORBIDDEN.has(key)) out[key] = sanitize(item);
985
+ return out;
986
+ }
987
+ function mergeRecursive(base, override) {
988
+ const out = { ...base };
989
+ for (const [key, value] of Object.entries(override)) {
990
+ if (FORBIDDEN.has(key)) continue;
991
+ const current = out[key];
992
+ out[key] = (isPlainObject(current) || Array.isArray(current)) && (isPlainObject(value) || Array.isArray(value)) ? normalizeLists(mergeRecursive({ ...current }, { ...value })) : value;
993
+ }
994
+ return out;
995
+ }
996
+ function isBlank(value) {
997
+ if (value === null || value === void 0) return true;
998
+ if (typeof value === "string") return value.trim() === "";
999
+ if (Array.isArray(value)) return value.length === 0;
1000
+ if (isPlainObject(value)) return Object.keys(value).length === 0;
1001
+ return false;
1002
+ }
1003
+ function transformLeaves(tree, transform, prefix = "") {
1004
+ const out = {};
1005
+ for (const [key, value] of Object.entries(tree)) out[key] = transformValue(`${prefix}${key}`, value, transform);
1006
+ return out;
1007
+ }
1008
+ function transformValue(path, value, transform) {
1009
+ if (Array.isArray(value)) return value.map((item, index) => transformValue(`${path}.${index}`, item, transform));
1010
+ if (isPlainObject(value)) return transformLeaves(value, transform, `${path}.`);
1011
+ return transform(path, value);
1012
+ }
1013
+ var TRUE_VALUES = /* @__PURE__ */ new Set(["1", "true", "on", "yes"]);
1014
+ var normalizeIp = (ip) => ip.trim().replace(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i, "$1");
1015
+ function ipv4ToNumber(ip) {
1016
+ const parts = ip.split(".");
1017
+ if (parts.length !== 4 || parts.some((part) => !/^\d{1,3}$/.test(part) || Number(part) > 255)) return void 0;
1018
+ return parts.reduce((total, part) => total * 256 + Number(part), 0);
1019
+ }
1020
+ function ipMatches(ip, patterns) {
1021
+ const address = normalizeIp(ip);
1022
+ return patterns.some((pattern) => {
1023
+ if (pattern === "*") return true;
1024
+ if (!pattern.includes("/")) return normalizeIp(pattern) === address;
1025
+ const [range, bits] = pattern.split("/");
1026
+ const base = ipv4ToNumber(range);
1027
+ const value = ipv4ToNumber(address);
1028
+ const size = Number(bits);
1029
+ if (base === void 0 || value === void 0 || !(size >= 0 && size <= 32)) return false;
1030
+ const mask = size === 0 ? 0 : ~0 << 32 - size >>> 0;
1031
+ return (base & mask) >>> 0 === (value & mask) >>> 0;
1032
+ });
1033
+ }
1034
+ var IncomingRequest = class {
1035
+ constructor(raw, options = {}) {
1036
+ this.raw = raw;
1037
+ this.#url = new URL(raw.url);
1038
+ this.#trustedProxies = options.trustedProxies ?? [];
1039
+ this.#maxBodySize = options.maxBodySize === void 0 ? 8 * 1024 * 1024 : options.maxBodySize;
1040
+ this.#maxJsonDepth = options.maxJsonDepth === void 0 ? 512 : options.maxJsonDepth;
1041
+ this.#query = fromEntries(this.#url.searchParams.entries());
1042
+ this.#method = raw.method.toUpperCase();
1043
+ }
1044
+ raw;
1045
+ #url;
1046
+ #trustedProxies;
1047
+ #maxBodySize;
1048
+ #maxJsonDepth;
1049
+ #query;
1050
+ #body = {};
1051
+ #files = {};
1052
+ #content = "";
1053
+ #method;
1054
+ #cookies;
1055
+ #loading;
1056
+ #userResolver;
1057
+ /** Reads and parses the body (JSON, urlencoded or multipart) once, then applies method spoofing. */
1058
+ load() {
1059
+ return this.#loading ??= this.#load();
1060
+ }
1061
+ async #load() {
1062
+ const method = this.raw.method.toUpperCase();
1063
+ if (method !== "GET" && method !== "HEAD" && this.raw.body !== null) {
1064
+ const type = (this.raw.headers.get("content-type") ?? "").toLowerCase();
1065
+ const isJson = type.includes("/json") || type.includes("+json");
1066
+ const isUrlencoded = type.startsWith("application/x-www-form-urlencoded");
1067
+ const isMultipart = type.startsWith("multipart/form-data");
1068
+ if (isJson || isUrlencoded || isMultipart) {
1069
+ const bytes = await readBody(this.raw, this.#maxBodySize);
1070
+ if (isJson && this.#maxJsonDepth !== null && exceedsJsonDepth(new TextDecoder().decode(bytes), this.#maxJsonDepth)) {
1071
+ throw new HttpException(400, "The request body is nested too deeply.");
1072
+ }
1073
+ try {
1074
+ if (isJson) {
1075
+ this.#content = new TextDecoder().decode(bytes);
1076
+ this.#body = this.#parseJson(this.#content);
1077
+ } else if (isUrlencoded) {
1078
+ this.#content = new TextDecoder().decode(bytes);
1079
+ this.#body = fromEntries(new URLSearchParams(this.#content).entries());
1080
+ } else {
1081
+ this.#parseForm(await new Response(bytes, { headers: { "content-type": this.raw.headers.get("content-type") ?? "" } }).formData());
1082
+ }
1083
+ } catch {
1084
+ }
1085
+ }
1086
+ }
1087
+ if (method === "POST") {
1088
+ const override = this.raw.headers.get("x-http-method-override") ?? this.#string(this.#body._method) ?? this.#string(this.#query._method);
1089
+ const spoofed = override?.toUpperCase();
1090
+ if (spoofed !== void 0 && /^[A-Z]+$/.test(spoofed) && !["GET", "HEAD", "CONNECT", "TRACE"].includes(spoofed)) {
1091
+ this.#method = spoofed;
1092
+ }
1093
+ }
1094
+ return this;
1095
+ }
1096
+ #parseJson(text) {
1097
+ let parsed;
1098
+ try {
1099
+ parsed = JSON.parse(text);
1100
+ } catch {
1101
+ return {};
1102
+ }
1103
+ if (Array.isArray(parsed)) return sanitize({ ...parsed });
1104
+ return isPlainObject(parsed) ? sanitize(parsed) : {};
1105
+ }
1106
+ #parseForm(form) {
1107
+ const fields = [];
1108
+ const files = [];
1109
+ for (const [name, value] of form.entries()) {
1110
+ if (typeof value === "string") fields.push([name, value]);
1111
+ else if (!((value.name ?? "") === "" && value.size === 0)) files.push([name, new FormUploadedFile(value)]);
1112
+ }
1113
+ this.#body = fromEntries(fields);
1114
+ this.#files = fromEntries(files);
1115
+ }
1116
+ #string(value) {
1117
+ return typeof value === "string" && value !== "" ? value : void 0;
1118
+ }
1119
+ method() {
1120
+ return this.#method;
1121
+ }
1122
+ isMethod(method) {
1123
+ return this.#method === method.toUpperCase();
1124
+ }
1125
+ /** The scheme and host, honouring X-Forwarded-Proto/Host from trusted proxies. */
1126
+ root() {
1127
+ const trusted = this.#fromTrustedProxy();
1128
+ const proto = trusted ? this.raw.headers.get("x-forwarded-proto")?.split(",")[0]?.trim() : void 0;
1129
+ const host = trusted ? this.raw.headers.get("x-forwarded-host")?.split(",")[0]?.trim() : void 0;
1130
+ const scheme = proto === "https" || proto === "http" ? proto : this.#url.protocol.slice(0, -1);
1131
+ return `${scheme}://${host !== void 0 && host !== "" ? host : this.#url.host}`;
1132
+ }
1133
+ /** The URL without the query string or a trailing slash. */
1134
+ url() {
1135
+ return `${this.root()}${this.#url.pathname}`.replace(/\/+$/, "");
1136
+ }
1137
+ fullUrl() {
1138
+ return this.#url.search === "" ? this.url() : `${this.url()}${this.#url.search}`;
1139
+ }
1140
+ /** 'posts/1', or '/' for the root. */
1141
+ path() {
1142
+ const trimmed = this.#url.pathname.replace(/^\/+|\/+$/g, "");
1143
+ return trimmed === "" ? "/" : trimmed;
1144
+ }
1145
+ decodedPath() {
1146
+ try {
1147
+ return decodeURIComponent(this.path());
1148
+ } catch {
1149
+ return this.path();
1150
+ }
1151
+ }
1152
+ secure() {
1153
+ return this.root().startsWith("https:");
1154
+ }
1155
+ /** request.is('admin/*'): wildcard match against the decoded path. */
1156
+ is(...patterns) {
1157
+ const path = this.decodedPath();
1158
+ return patterns.some((pattern) => {
1159
+ const trimmed = pattern.replace(/^\/+|\/+$/g, "");
1160
+ return Str.is(trimmed === "" ? "/" : trimmed, path);
1161
+ });
1162
+ }
1163
+ routeIs(...names) {
1164
+ const name = this.#currentRoute()?.route.getName();
1165
+ return name !== void 0 && names.some((pattern) => Str.is(pattern, name));
1166
+ }
1167
+ header(name, defaultValue) {
1168
+ return this.raw.headers.get(name) ?? defaultValue;
1169
+ }
1170
+ bearerToken() {
1171
+ const header = this.header("authorization", "");
1172
+ const position = header.toLowerCase().lastIndexOf("bearer ");
1173
+ if (position === -1) return void 0;
1174
+ const token2 = header.slice(position + 7).split(",")[0].trim();
1175
+ return token2 === "" ? void 0 : token2;
1176
+ }
1177
+ userAgent() {
1178
+ return this.header("user-agent");
1179
+ }
1180
+ /** The socket address, or the right-most untrusted X-Forwarded-For entry when the socket is a trusted proxy. */
1181
+ ip() {
1182
+ const socket = clientAddress(this.raw);
1183
+ if (socket === void 0) return void 0;
1184
+ const address = normalizeIp(socket);
1185
+ if (!this.#fromTrustedProxy()) return address;
1186
+ const chain = (this.raw.headers.get("x-forwarded-for") ?? "").split(",").map((entry) => normalizeIp(entry)).filter((entry) => entry !== "");
1187
+ for (let i = chain.length - 1; i >= 0; i--) if (!ipMatches(chain[i], this.#trustedProxies)) return chain[i];
1188
+ return chain[0] ?? address;
1189
+ }
1190
+ #fromTrustedProxy() {
1191
+ const socket = clientAddress(this.raw);
1192
+ return socket !== void 0 && this.#trustedProxies.length > 0 && ipMatches(socket, this.#trustedProxies);
1193
+ }
1194
+ ajax() {
1195
+ return this.header("x-requested-with") === "XMLHttpRequest";
1196
+ }
1197
+ pjax() {
1198
+ return this.header("x-pjax") === "true";
1199
+ }
1200
+ /** The first Accept type is JSON. */
1201
+ wantsJson() {
1202
+ const first = this.header("accept", "").split(",")[0].trim();
1203
+ return first.includes("/json") || first.includes("+json");
1204
+ }
1205
+ /** Laravel: (ajax and not pjax and accepts anything) or wantsJson. */
1206
+ expectsJson() {
1207
+ const first = this.header("accept", "").split(",")[0].split(";")[0].trim();
1208
+ const acceptsAny = first === "" || first === "*/*" || first === "*";
1209
+ return this.ajax() && !this.pjax() && acceptsAny || this.wantsJson();
1210
+ }
1211
+ /** The raw body text for JSON and urlencoded requests ('' otherwise; the raw stream stays unread for other types). */
1212
+ content() {
1213
+ return this.#content;
1214
+ }
1215
+ query(key, defaultValue) {
1216
+ return Arr.get(this.#query, key, defaultValue);
1217
+ }
1218
+ /** Body wins over the query string (PHP's $body + $query). Files are not input. */
1219
+ input(key, defaultValue) {
1220
+ return Arr.get({ ...this.#query, ...this.#body }, key, defaultValue);
1221
+ }
1222
+ /** Input plus files (array_replace_recursive). */
1223
+ all() {
1224
+ return mergeRecursive({ ...this.#query, ...this.#body }, this.#files);
1225
+ }
1226
+ only(...keys) {
1227
+ const all = this.all();
1228
+ const out = {};
1229
+ for (const key of keys.flat()) if (Arr.has(all, key)) Arr.set(out, key, Arr.get(all, key));
1230
+ return out;
1231
+ }
1232
+ except(...keys) {
1233
+ const all = structuredCloneInput(this.all());
1234
+ for (const key of keys.flat()) Arr.forget(all, key);
1235
+ return all;
1236
+ }
1237
+ has(...keys) {
1238
+ const all = this.all();
1239
+ return keys.flat().every((key) => Arr.has(all, key));
1240
+ }
1241
+ hasAny(...keys) {
1242
+ const all = this.all();
1243
+ return keys.flat().some((key) => Arr.has(all, key));
1244
+ }
1245
+ missing(...keys) {
1246
+ return !this.has(...keys);
1247
+ }
1248
+ filled(key) {
1249
+ return !isBlank(Arr.get(this.all(), key));
1250
+ }
1251
+ string(key, defaultValue = "") {
1252
+ const value = this.input(key);
1253
+ return value === void 0 || value === null ? defaultValue : String(value);
1254
+ }
1255
+ /** PHP's FILTER_VALIDATE_BOOLEAN: '1', 'true', 'on', 'yes' (any case) and true are true. */
1256
+ boolean(key, defaultValue = false) {
1257
+ const value = this.input(key);
1258
+ if (value === void 0) return defaultValue;
1259
+ if (typeof value === "boolean") return value;
1260
+ if (typeof value === "number") return value === 1;
1261
+ return typeof value === "string" && TRUE_VALUES.has(value.trim().toLowerCase());
1262
+ }
1263
+ /** PHP's intval: leading digits, else 0. */
1264
+ integer(key, defaultValue = 0) {
1265
+ const value = this.input(key);
1266
+ if (value === void 0) return defaultValue;
1267
+ if (typeof value === "number") return Number.isFinite(value) ? Math.trunc(value) : 0;
1268
+ if (typeof value === "boolean") return value ? 1 : 0;
1269
+ const parsed = Number.parseInt(String(value).trim(), 10);
1270
+ return Number.isNaN(parsed) ? 0 : parsed;
1271
+ }
1272
+ /** Adds or replaces top-level body values. */
1273
+ merge(values) {
1274
+ this.#body = { ...this.#body, ...sanitize(values) };
1275
+ return this;
1276
+ }
1277
+ /** Rewrites every scalar in the query and the body (TrimStrings and ConvertEmptyStringsToNull use this). */
1278
+ transformInput(transform) {
1279
+ this.#query = transformLeaves(this.#query, transform);
1280
+ this.#body = transformLeaves(this.#body, transform);
1281
+ return this;
1282
+ }
1283
+ file(key) {
1284
+ const value = Arr.get(this.#files, key);
1285
+ return value instanceof FormUploadedFile ? value : void 0;
1286
+ }
1287
+ hasFile(key) {
1288
+ const value = Arr.get(this.#files, key);
1289
+ return value instanceof FormUploadedFile || Array.isArray(value) && value.some((item) => item instanceof FormUploadedFile);
1290
+ }
1291
+ allFiles() {
1292
+ return { ...this.#files };
1293
+ }
1294
+ cookie(name) {
1295
+ return this.cookies()[name];
1296
+ }
1297
+ cookies() {
1298
+ if (this.#cookies === void 0) {
1299
+ const cookies = {};
1300
+ for (const part of (this.raw.headers.get("cookie") ?? "").split(";")) {
1301
+ const index = part.indexOf("=");
1302
+ if (index === -1) continue;
1303
+ const name = part.slice(0, index).trim();
1304
+ if (name === "" || Object.hasOwn(cookies, name)) continue;
1305
+ let value = part.slice(index + 1).trim();
1306
+ if (value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
1307
+ try {
1308
+ cookies[name] = decodeURIComponent(value);
1309
+ } catch {
1310
+ cookies[name] = value;
1311
+ }
1312
+ }
1313
+ this.#cookies = cookies;
1314
+ }
1315
+ return { ...this.#cookies };
1316
+ }
1317
+ /** Replaces the parsed cookies (EncryptCookies calls this after decrypting). */
1318
+ setCookies(cookies) {
1319
+ this.#cookies = { ...cookies };
1320
+ }
1321
+ /** One route parameter (after model binding), or all of them. */
1322
+ route(key) {
1323
+ const parameters = this.#currentRoute()?.parameters ?? {};
1324
+ return key === void 0 ? { ...parameters } : parameters[key];
1325
+ }
1326
+ session() {
1327
+ return inject(Session);
1328
+ }
1329
+ user(guard) {
1330
+ return this.#userResolver?.(guard);
1331
+ }
1332
+ setUserResolver(resolver) {
1333
+ this.#userResolver = resolver;
1334
+ }
1335
+ /** Validates all() through the ValidationFactory; throws its Validation error on failure. */
1336
+ validate(rules, messages, attributes) {
1337
+ return inject(ValidationFactory).make(this.all(), rules, messages, attributes).validated();
1338
+ }
1339
+ #currentRoute() {
1340
+ const container = currentContainer();
1341
+ return container?.bound(CurrentRoute) ? container.make(CurrentRoute) : void 0;
1342
+ }
1343
+ };
1344
+ function structuredCloneInput(tree) {
1345
+ const out = {};
1346
+ for (const [key, value] of Object.entries(tree)) {
1347
+ out[key] = isPlainObject(value) ? structuredCloneInput(value) : Array.isArray(value) ? [...value] : value;
1348
+ }
1349
+ return out;
1350
+ }
1351
+ function request(key, defaultValue) {
1352
+ const current = inject(HttpRequest);
1353
+ return key === void 0 ? current : current.input(key, defaultValue);
1354
+ }
1355
+ function response(content, status, headers) {
1356
+ const factory = inject(ResponseFactory);
1357
+ return content === void 0 ? factory : factory.make(content, status, headers);
1358
+ }
1359
+ function redirect(to, status) {
1360
+ const redirector = inject(Redirector);
1361
+ return to === void 0 ? redirector : redirector.to(to, status);
1362
+ }
1363
+ function back(fallback, status) {
1364
+ return inject(Redirector).back(fallback, status);
1365
+ }
1366
+ function url(path, query, absolute) {
1367
+ const generator = inject(UrlGenerator);
1368
+ return path === void 0 ? generator : generator.to(path, query, absolute);
1369
+ }
1370
+ function route(name, parameters, absolute) {
1371
+ return inject(UrlGenerator).route(name, parameters, absolute);
1372
+ }
1373
+ function abort(status, message = "", headers = {}) {
1374
+ if (status instanceof Response) throw new HttpResponseException(status);
1375
+ throw new HttpException(status, message, headers);
1376
+ }
1377
+ function abortIf(condition, status, message = "", headers = {}) {
1378
+ if (condition) abort(status, message, headers);
1379
+ }
1380
+ function abortUnless(condition, status, message = "", headers = {}) {
1381
+ if (!condition) abort(status, message, headers);
1382
+ }
1383
+ function contentDisposition(type, name) {
1384
+ const fallback = name.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "\\$&");
1385
+ const header = `${type}; filename="${fallback}"`;
1386
+ return /^[\x20-\x7e]*$/.test(name) ? header : `${header}; filename*=UTF-8''${encodeURIComponent(name)}`;
1387
+ }
1388
+ var HttpResponseFactory = class {
1389
+ /** Strings default to text/html; charset=utf-8, as in Laravel. */
1390
+ make(content = null, status = 200, headers) {
1391
+ const merged = new Headers(headers);
1392
+ if (typeof content === "string" && !merged.has("content-type")) merged.set("content-type", "text/html; charset=utf-8");
1393
+ return new Response(content, { status, headers: merged });
1394
+ }
1395
+ json(data, status = 200, headers) {
1396
+ const merged = new Headers(headers);
1397
+ if (!merged.has("content-type")) merged.set("content-type", "application/json");
1398
+ return new Response(JSON.stringify(data === void 0 ? null : data), { status, headers: merged });
1399
+ }
1400
+ noContent(status = 204) {
1401
+ return new Response(null, { status });
1402
+ }
1403
+ /** Renders a view (through the ViewFactory) into an HTML response. */
1404
+ async view(name, data = {}, status = 200, headers) {
1405
+ const html = await inject(ViewFactory).make(name, data).render();
1406
+ return this.make(html, status, headers);
1407
+ }
1408
+ /** Streams a file as an attachment. A missing file is a 404. */
1409
+ download(path, name = basename(path), headers) {
1410
+ return this.#file(path, "attachment", name, headers);
1411
+ }
1412
+ /** Streams a file for the browser to display (Laravel's response()->file()). */
1413
+ file(path, headers) {
1414
+ return this.#file(path, "inline", basename(path), headers);
1415
+ }
1416
+ stream(body, status = 200, headers) {
1417
+ return new Response(body, { status, headers });
1418
+ }
1419
+ async #file(path, disposition, name, headers) {
1420
+ const info = await stat(path).catch(() => void 0);
1421
+ if (info === void 0 || !info.isFile()) throw new HttpException(404);
1422
+ const merged = new Headers(headers);
1423
+ if (!merged.has("content-type")) merged.set("content-type", mimeFromExtension(name));
1424
+ merged.set("content-length", String(info.size));
1425
+ merged.set("content-disposition", contentDisposition(disposition, name));
1426
+ const body = Readable.toWeb(createReadStream(path));
1427
+ return new Response(body, { status: 200, headers: merged });
1428
+ }
1429
+ };
1430
+ var ConvertEmptyStringsToNull = class {
1431
+ async handle(request2, next) {
1432
+ if (request2 instanceof IncomingRequest) request2.transformInput((_key, value) => value === "" ? null : value);
1433
+ return next(request2);
1434
+ }
1435
+ };
1436
+ var TRIM = /^[\s\uFEFF\u200B\u200E]+|[\s\uFEFF\u200B\u200E]+$/g;
1437
+ var TrimStrings = class {
1438
+ /** Dotted input keys left untouched. Override in a subclass to change. */
1439
+ except = ["current_password", "password", "password_confirmation"];
1440
+ async handle(request2, next) {
1441
+ if (request2 instanceof IncomingRequest) {
1442
+ request2.transformInput(
1443
+ (key, value) => typeof value === "string" && !this.except.includes(key) ? value.replace(TRIM, "") : value
1444
+ );
1445
+ }
1446
+ return next(request2);
1447
+ }
1448
+ };
1449
+ var plainServerError = () => new Response("Server Error", { status: 500, headers: { "content-type": "text/plain; charset=utf-8" } });
1450
+ var Kernel = class {
1451
+ constructor(app) {
1452
+ this.app = app;
1453
+ }
1454
+ app;
1455
+ #middleware = [TrimStrings, ConvertEmptyStringsToNull];
1456
+ #requests = /* @__PURE__ */ new WeakMap();
1457
+ getGlobalMiddleware() {
1458
+ return [...this.#middleware];
1459
+ }
1460
+ setGlobalMiddleware(middleware) {
1461
+ this.#middleware = [...middleware];
1462
+ return this;
1463
+ }
1464
+ appendMiddleware(...middleware) {
1465
+ for (const cls of middleware) if (!this.#middleware.includes(cls)) this.#middleware.push(cls);
1466
+ return this;
1467
+ }
1468
+ prependMiddleware(...middleware) {
1469
+ for (const cls of [...middleware].reverse()) if (!this.#middleware.includes(cls)) this.#middleware.unshift(cls);
1470
+ return this;
1471
+ }
1472
+ hasMiddleware(middleware) {
1473
+ return this.#middleware.includes(middleware);
1474
+ }
1475
+ /** Runs in the current request scope (Application.handle() opens one); called directly, it opens its own. */
1476
+ handle(raw) {
1477
+ const current = currentContainer();
1478
+ const scope = current !== void 0 && current !== this.app ? current : this.app.createScope();
1479
+ return scope.run(() => this.#handle(raw, scope));
1480
+ }
1481
+ async #handle(raw, scope) {
1482
+ let request2;
1483
+ let response2;
1484
+ try {
1485
+ request2 = new IncomingRequest(raw, {
1486
+ trustedProxies: this.#trustedProxies(),
1487
+ maxBodySize: this.#maxBodySize(),
1488
+ maxJsonDepth: this.#maxJsonDepth()
1489
+ });
1490
+ scope.instance(HttpRequest, request2);
1491
+ const state = { scope, request: request2, middleware: [] };
1492
+ this.#requests.set(raw, state);
1493
+ await request2.load();
1494
+ response2 = await this.#sendThroughGlobal(state);
1495
+ } catch (error) {
1496
+ request2 ??= this.#bareRequest(scope, raw);
1497
+ if (request2 !== void 0) {
1498
+ response2 = await this.#renderLast(scope, request2, error);
1499
+ } else {
1500
+ await this.#report(scope, error);
1501
+ response2 = plainServerError();
1502
+ }
1503
+ }
1504
+ if (raw.method.toUpperCase() === "HEAD" && response2.body !== null) {
1505
+ await response2.body.cancel().catch(() => void 0);
1506
+ response2 = new Response(null, { status: response2.status, statusText: response2.statusText, headers: response2.headers });
1507
+ }
1508
+ return response2;
1509
+ }
1510
+ /** The request without options, for rendering an error raised while the real one was being built. */
1511
+ #bareRequest(scope, raw) {
1512
+ try {
1513
+ const request2 = new IncomingRequest(raw);
1514
+ scope.instance(HttpRequest, request2);
1515
+ return request2;
1516
+ } catch {
1517
+ return void 0;
1518
+ }
1519
+ }
1520
+ /** Runs terminate() on the global and route middleware (inside the request's scope), then Application.terminate(), inside the same scope, so terminating callbacks see the request's bindings. */
1521
+ async terminate(raw, response2) {
1522
+ const state = this.#requests.get(raw);
1523
+ if (state !== void 0) {
1524
+ this.#requests.delete(raw);
1525
+ await state.scope.run(async () => {
1526
+ const route2 = state.scope.bound(CurrentRoute) ? state.scope.make(CurrentRoute).middleware : [];
1527
+ for (const middleware of [...state.middleware, ...route2]) {
1528
+ if (typeof middleware.terminate !== "function") continue;
1529
+ try {
1530
+ await middleware.terminate(state.request, response2);
1531
+ } catch (error) {
1532
+ await this.#report(state.scope, error);
1533
+ }
1534
+ }
1535
+ await this.app.terminate();
1536
+ });
1537
+ return;
1538
+ }
1539
+ await this.app.terminate();
1540
+ }
1541
+ #sendThroughGlobal(state) {
1542
+ const { scope, request: request2 } = state;
1543
+ const pipes = this.#middleware.map((cls) => {
1544
+ const instance = scope.make(cls);
1545
+ state.middleware.push(instance);
1546
+ return (passable, next) => this.#guard(scope, passable, () => instance.handle(passable, next));
1547
+ });
1548
+ return scope.make(PipelineFactory).make().send(request2).through(pipes).then((passable) => this.#guard(scope, passable, () => this.#dispatch(scope, passable)));
1549
+ }
1550
+ #dispatch(scope, request2) {
1551
+ return scope.make(Router).dispatch(request2);
1552
+ }
1553
+ /** Converts a Responsable result, and renders a thrown error at this stage (Laravel's Routing\Pipeline). */
1554
+ async #guard(scope, request2, run) {
1555
+ try {
1556
+ const result = await run();
1557
+ if (result instanceof Response) return result;
1558
+ const responsable = result;
1559
+ if (typeof responsable?.toResponse === "function") return await responsable.toResponse(request2);
1560
+ throw new HttpServerError("Middleware must return a Response.");
1561
+ } catch (error) {
1562
+ if (!scope.bound(ExceptionHandler)) throw error;
1563
+ const handler = scope.make(ExceptionHandler);
1564
+ await handler.report(error);
1565
+ return handler.render(request2, error);
1566
+ }
1567
+ }
1568
+ /** Last resort: the handler failed or isn't bound. Never throws. */
1569
+ async #renderLast(scope, request2, error) {
1570
+ if (scope.bound(ExceptionHandler)) {
1571
+ try {
1572
+ const handler = scope.make(ExceptionHandler);
1573
+ await handler.report(error);
1574
+ return await handler.render(request2, error);
1575
+ } catch (renderError) {
1576
+ console.error(renderError);
1577
+ }
1578
+ } else {
1579
+ console.error(error);
1580
+ }
1581
+ return plainServerError();
1582
+ }
1583
+ async #report(scope, error) {
1584
+ try {
1585
+ if (scope.bound(ExceptionHandler)) await scope.make(ExceptionHandler).report(error);
1586
+ else console.error(error);
1587
+ } catch (reportError) {
1588
+ console.error(reportError);
1589
+ }
1590
+ }
1591
+ /** config('http.max_body_size'): bytes, or null for no limit. Anything else is a configuration error. */
1592
+ #maxBodySize() {
1593
+ if (!this.app.bound(ConfigRepository)) return 8 * 1024 * 1024;
1594
+ const value = this.app.make(ConfigRepository).get("http.max_body_size", 8 * 1024 * 1024);
1595
+ if (value === null) return null;
1596
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
1597
+ throw new HttpServerError(
1598
+ `Invalid http.max_body_size ${JSON.stringify(value) ?? String(value)}: use a number of bytes (0 or more), or null for no limit.`
1599
+ );
1600
+ }
1601
+ /** config('http.max_json_depth'): how deep a JSON body may nest, or null for no limit. */
1602
+ #maxJsonDepth() {
1603
+ if (!this.app.bound(ConfigRepository)) return 512;
1604
+ const value = this.app.make(ConfigRepository).get("http.max_json_depth", 512);
1605
+ if (value === null) return null;
1606
+ if (typeof value === "number" && Number.isInteger(value) && value >= 1) return value;
1607
+ throw new HttpServerError(
1608
+ `Invalid http.max_json_depth ${JSON.stringify(value) ?? String(value)}: use a whole number (1 or more), or null for no limit.`
1609
+ );
1610
+ }
1611
+ /** config('http.trusted_proxies'): an array, or a comma-separated string such as '*' or '10.0.0.0/8,::1'. */
1612
+ #trustedProxies() {
1613
+ if (!this.app.bound(ConfigRepository)) return [];
1614
+ const value = this.app.make(ConfigRepository).get("http.trusted_proxies", []);
1615
+ if (typeof value === "string") return value.split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
1616
+ return Array.isArray(value) ? value.map(String) : [];
1617
+ }
1618
+ };
1619
+ var MiddlewareConfiguration = class {
1620
+ constructor(kernel, router, config) {
1621
+ this.kernel = kernel;
1622
+ this.router = router;
1623
+ this.config = config;
1624
+ }
1625
+ kernel;
1626
+ router;
1627
+ config;
1628
+ append(...middleware) {
1629
+ this.kernel.appendMiddleware(...middleware);
1630
+ return this;
1631
+ }
1632
+ prepend(...middleware) {
1633
+ this.kernel.prependMiddleware(...middleware);
1634
+ return this;
1635
+ }
1636
+ web(options) {
1637
+ return this.#modifyGroup("web", options);
1638
+ }
1639
+ api(options) {
1640
+ return this.#modifyGroup("api", options);
1641
+ }
1642
+ alias(aliases) {
1643
+ for (const [name, middleware] of Object.entries(aliases)) this.router.aliasMiddleware(name, middleware);
1644
+ return this;
1645
+ }
1646
+ group(name, middleware) {
1647
+ this.router.middlewareGroup(name, middleware);
1648
+ return this;
1649
+ }
1650
+ /** Stored in config('http.csrf_except'), which the Session component's VerifyCsrfToken reads. */
1651
+ validateCsrfTokens(options) {
1652
+ const current = this.config?.get("http.csrf_except", []) ?? [];
1653
+ this.config?.set("http.csrf_except", [...current, ...options.except]);
1654
+ return this;
1655
+ }
1656
+ /** Stored in config('http.redirect_guests_to'), which the Auth middleware and the exception handler read. */
1657
+ redirectGuestsTo(url2) {
1658
+ this.config?.set("http.redirect_guests_to", url2);
1659
+ return this;
1660
+ }
1661
+ /** Laravel 11's redirectUsersTo(): stored in config('http.redirect_users_to') for Auth's RedirectIfAuthenticated. */
1662
+ redirectUsersTo(url2) {
1663
+ this.config?.set("http.redirect_users_to", url2);
1664
+ return this;
1665
+ }
1666
+ /** Laravel's order: prepends, then the group minus removals, then appends. */
1667
+ #modifyGroup(name, { append = [], prepend = [], remove = [] }) {
1668
+ const current = this.router.getMiddlewareGroups()[name] ?? [];
1669
+ const moved = /* @__PURE__ */ new Set([...append, ...prepend, ...remove]);
1670
+ this.router.middlewareGroup(name, [...prepend, ...current.filter((entry) => !moved.has(entry)), ...append]);
1671
+ return this;
1672
+ }
1673
+ };
1674
+ function applyMiddlewareCallbacks(app, kernel) {
1675
+ if (!app.bound(MiddlewareCallbacks)) return;
1676
+ const callbacks = app.make(MiddlewareCallbacks);
1677
+ if (callbacks.length === 0) return;
1678
+ const config = app.bound(ConfigRepository) ? app.make(ConfigRepository) : void 0;
1679
+ const configurator = new MiddlewareConfiguration(kernel, app.make(Router), config);
1680
+ for (const callback of callbacks) callback(configurator);
1681
+ }
1682
+ var STATUS_TEXTS = {
1683
+ 400: "Bad Request",
1684
+ 401: "Unauthorized",
1685
+ 402: "Payment Required",
1686
+ 403: "Forbidden",
1687
+ 404: "Not Found",
1688
+ 405: "Method Not Allowed",
1689
+ 408: "Request Timeout",
1690
+ 409: "Conflict",
1691
+ 410: "Gone",
1692
+ 413: "Content Too Large",
1693
+ 415: "Unsupported Media Type",
1694
+ 419: "Page Expired",
1695
+ 422: "Unprocessable Content",
1696
+ 429: "Too Many Requests",
1697
+ 500: "Server Error",
1698
+ 502: "Bad Gateway",
1699
+ 503: "Service Unavailable",
1700
+ 504: "Gateway Timeout"
1701
+ };
1702
+ function statusText(status) {
1703
+ return STATUS_TEXTS[status] ?? (status >= 500 ? "Server Error" : "Error");
1704
+ }
1705
+ function escapeHtml(value) {
1706
+ return value.replace(/[&<>"']/g, (char) => `&#${char.charCodeAt(0)};`);
1707
+ }
1708
+ function minimalPage(status, message) {
1709
+ const title = escapeHtml(message);
1710
+ return `<!DOCTYPE html>
1711
+ <html lang="en">
1712
+ <head>
1713
+ <meta charset="utf-8">
1714
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1715
+ <title>${title}</title>
1716
+ <style>body{margin:0;font-family:ui-sans-serif,system-ui,sans-serif;background:#f7fafc;color:#1a202c;display:flex;align-items:center;justify-content:center;min-height:100vh}.code{padding:0 1rem;border-right:1px solid #a0aec0;font-size:1.125rem}.message{padding:0 1rem;font-size:1.125rem;text-transform:uppercase;letter-spacing:.05em}</style>
1717
+ </head>
1718
+ <body><div class="code">${status}</div><div class="message">${title}</div></body>
1719
+ </html>`;
1720
+ }
1721
+ function stackFrames(error) {
1722
+ const frames = [];
1723
+ for (const raw of (error.stack ?? "").split("\n").slice(1)) {
1724
+ const text = raw.trim();
1725
+ const match = /(?:\(|@|at )((?:file:\/\/)?(?:\/|[A-Za-z]:\\)[^()]*?):(\d+):(\d+)\)?$/.exec(text);
1726
+ if (match === null) continue;
1727
+ const location = match[1];
1728
+ const file = location.startsWith("file://") ? fileURLToPath(location) : location;
1729
+ frames.push({ file, line: Number(match[2]), column: Number(match[3]), text });
1730
+ }
1731
+ return frames;
1732
+ }
1733
+ async function excerpt(frame, radius = 5) {
1734
+ const source = await readFile(frame.file, "utf8").catch(() => void 0);
1735
+ if (source === void 0) return void 0;
1736
+ const lines = source.split("\n");
1737
+ const start = Math.max(1, frame.line - radius);
1738
+ const end = Math.min(lines.length, frame.line + radius);
1739
+ const rows = [];
1740
+ for (let number = start; number <= end; number++) {
1741
+ const current = number === frame.line ? ' class="current"' : "";
1742
+ rows.push(`<tr${current}><td class="n">${number}</td><td><code>${escapeHtml(lines[number - 1] ?? "")}</code></td></tr>`);
1743
+ }
1744
+ return `<table class="source">${rows.join("")}</table>`;
1745
+ }
1746
+ async function debugPage(error, method, url2) {
1747
+ const err = error instanceof Error ? error : new Error(String(error));
1748
+ const frames = stackFrames(err);
1749
+ const appFrame = frames.find((frame) => !frame.file.includes("node_modules") && !frame.file.startsWith("node:")) ?? frames[0];
1750
+ const source = appFrame === void 0 ? void 0 : await excerpt(appFrame);
1751
+ const causes = [];
1752
+ for (let cause = err.cause, depth = 0; cause !== void 0 && depth < 5; cause = cause.cause, depth++) {
1753
+ causes.push(`<li>${escapeHtml(cause instanceof Error ? `${cause.name}: ${cause.message}` : String(cause))}</li>`);
1754
+ }
1755
+ return `<!DOCTYPE html>
1756
+ <html lang="en">
1757
+ <head>
1758
+ <meta charset="utf-8">
1759
+ <title>${escapeHtml(err.message || err.name)}</title>
1760
+ <style>body{margin:0;font-family:ui-sans-serif,system-ui,sans-serif;background:#f3f4f6;color:#111827}main{max-width:1100px;margin:2rem auto;padding:0 1rem}header{background:#fff;border-radius:.5rem;padding:1.5rem;box-shadow:0 1px 3px #0001}.class{color:#dc2626;font-weight:600}.message{font-size:1.5rem;margin:.5rem 0}.request{color:#6b7280}section{background:#fff;border-radius:.5rem;padding:1rem;margin-top:1rem;box-shadow:0 1px 3px #0001;overflow:auto}.source td{padding:0 .5rem;white-space:pre;font-size:.85rem}.source .n{color:#9ca3af;text-align:right}.source .current{background:#fee2e2}ol{font-family:ui-monospace,monospace;font-size:.85rem}</style>
1761
+ </head>
1762
+ <body><main>
1763
+ <header><div class="class">${escapeHtml(err.name)}</div><div class="message">${escapeHtml(err.message)}</div><div class="request">${escapeHtml(method)} ${escapeHtml(url2)}</div></header>
1764
+ ${appFrame === void 0 ? "" : `<section><div>${escapeHtml(appFrame.file)}:${appFrame.line}</div>${source ?? ""}</section>`}
1765
+ <section><ol>${frames.map((frame) => `<li>${escapeHtml(frame.text)}</li>`).join("")}</ol></section>
1766
+ ${causes.length === 0 ? "" : `<section><div>Caused by</div><ul>${causes.join("")}</ul></section>`}
1767
+ </main></body>
1768
+ </html>`;
1769
+ }
1770
+ var NOT_REPORTED = /* @__PURE__ */ new Set(["Http", "ModelNotFound", "Validation", "Authentication", "Authorization", "TokenMismatch"]);
1771
+ var HTTP_KINDS = /* @__PURE__ */ new Set(["Http", "ModelNotFound", "Authorization", "TokenMismatch"]);
1772
+ var kindOf = (error) => typeof error === "object" && error !== null && typeof error.kind === "string" ? error.kind : void 0;
1773
+ function statusFor(error) {
1774
+ const status = error?.status;
1775
+ switch (kindOf(error)) {
1776
+ case "Http":
1777
+ return status ?? 500;
1778
+ case "ModelNotFound":
1779
+ return 404;
1780
+ case "Authorization":
1781
+ return status ?? 403;
1782
+ case "TokenMismatch":
1783
+ return 419;
1784
+ case "Authentication":
1785
+ return 401;
1786
+ case "Validation":
1787
+ return status ?? 422;
1788
+ case "Server":
1789
+ return status ?? 500;
1790
+ default:
1791
+ return 500;
1792
+ }
1793
+ }
1794
+ function summarizeValidation(errors) {
1795
+ const messages = Object.values(errors).flat();
1796
+ if (messages.length === 0) return "The given data was invalid.";
1797
+ const more = messages.length - 1;
1798
+ return more === 0 ? messages[0] : `${messages[0]} (and ${more} more ${more === 1 ? "error" : "errors"})`;
1799
+ }
1800
+ var isHttpLike = (error) => {
1801
+ const kind = kindOf(error);
1802
+ return kind !== void 0 && HTTP_KINDS.has(kind);
1803
+ };
1804
+ var ErrorHandler = class {
1805
+ constructor(app) {
1806
+ this.app = app;
1807
+ }
1808
+ app;
1809
+ /** Input keys never flashed back on a validation redirect. */
1810
+ dontFlash = ["current_password", "password", "password_confirmation"];
1811
+ #reportCallbacks = [];
1812
+ #renderCallbacks = [];
1813
+ #dontReport = [];
1814
+ #reported = /* @__PURE__ */ new WeakSet();
1815
+ #jsonWhen;
1816
+ reportUsing(callback) {
1817
+ this.#reportCallbacks.push(callback);
1818
+ }
1819
+ renderUsing(callback) {
1820
+ this.#renderCallbacks.push(callback);
1821
+ }
1822
+ ignore(...classes) {
1823
+ this.#dontReport.push(...classes);
1824
+ }
1825
+ /** Laravel's shouldRenderJsonWhen(): the callback decides JSON or HTML instead of the Accept header. */
1826
+ shouldRenderJsonWhen(callback) {
1827
+ this.#jsonWhen = callback;
1828
+ }
1829
+ /** Laravel's shouldReturnJson(): the shouldRenderJsonWhen() callback when set, else request.expectsJson(). */
1830
+ shouldReturnJson(request2, error) {
1831
+ return this.#jsonWhen !== void 0 ? this.#jsonWhen(request2, error) : request2.expectsJson();
1832
+ }
1833
+ shouldReport(error) {
1834
+ const kind = kindOf(error);
1835
+ if (kind !== void 0 && NOT_REPORTED.has(kind)) return false;
1836
+ return !this.#dontReport.some((cls) => error instanceof cls);
1837
+ }
1838
+ /** Report callbacks first (returning false stops the default log), then LogManager.error(), else console.error. Once per error. */
1839
+ async report(error) {
1840
+ if (!this.shouldReport(error)) return;
1841
+ if (typeof error === "object" && error !== null) {
1842
+ if (this.#reported.has(error)) return;
1843
+ this.#reported.add(error);
1844
+ }
1845
+ try {
1846
+ for (const callback of this.#reportCallbacks) if (await callback(error) === false) return;
1847
+ const container = this.#container();
1848
+ if (container.bound(LogManager)) {
1849
+ const message = error instanceof Error ? error.message : String(error);
1850
+ container.make(LogManager).error(message, { exception: error });
1851
+ } else {
1852
+ console.error(error);
1853
+ }
1854
+ } catch (reportError) {
1855
+ console.error(reportError);
1856
+ }
1857
+ }
1858
+ async render(request2, error) {
1859
+ for (const callback of this.#renderCallbacks) {
1860
+ const response3 = await callback(error, request2);
1861
+ if (response3 instanceof Response) return response3;
1862
+ }
1863
+ if (error instanceof HttpResponseException) return error.response;
1864
+ const custom = await this.#renderCustom(request2, error);
1865
+ if (custom !== void 0) return custom;
1866
+ const http = this.#httpRequest(request2);
1867
+ const kind = kindOf(error);
1868
+ let response2;
1869
+ if (kind === "Validation") response2 = await this.#renderValidation(http, error);
1870
+ else if (kind === "Authentication") response2 = await this.#renderAuthentication(http, error);
1871
+ else if (this.shouldReturnJson(http, error)) response2 = this.#renderJson(error);
1872
+ else response2 = await this.#renderHtml(http, error);
1873
+ const headers = error?.headers;
1874
+ if (headers !== void 0) for (const [name, value] of Object.entries(headers)) response2.headers.set(name, value);
1875
+ return response2;
1876
+ }
1877
+ /** An error with its own render(request) or toResponse(request) (Laravel's renderable exceptions / Responsable). */
1878
+ async #renderCustom(request2, error) {
1879
+ if (typeof error !== "object" || error === null) return void 0;
1880
+ const candidate = error;
1881
+ for (const method of [candidate.render, candidate.toResponse]) {
1882
+ if (typeof method !== "function") continue;
1883
+ const response2 = await method.call(error, request2);
1884
+ if (response2 instanceof Response) return response2;
1885
+ }
1886
+ return void 0;
1887
+ }
1888
+ async #renderValidation(request2, error) {
1889
+ if (this.shouldReturnJson(request2, error)) {
1890
+ return this.#json({ message: summarizeValidation(error.errors), errors: error.errors }, statusFor(error));
1891
+ }
1892
+ const input = { ...request2.input() };
1893
+ for (const key of this.dontFlash) delete input[key];
1894
+ const container = this.#container();
1895
+ if (!container.bound(Redirector)) {
1896
+ return this.#redirect(error.redirectTo ?? request2.header("referer") ?? "/");
1897
+ }
1898
+ const redirector = container.make(Redirector);
1899
+ const redirect2 = error.redirectTo !== void 0 ? redirector.to(error.redirectTo) : redirector.back();
1900
+ const response2 = await redirect2.withInput(input).withErrors(error.errors, error.errorBag).toResponse(request2);
1901
+ return response2;
1902
+ }
1903
+ async #renderAuthentication(request2, error) {
1904
+ if (this.shouldReturnJson(request2, error)) return this.#json({ message: error.message || "Unauthenticated." }, 401);
1905
+ const target2 = error.redirectTo ?? this.#guestRedirect(request2);
1906
+ const container = this.#container();
1907
+ if (!container.bound(Redirector)) return this.#redirect(target2);
1908
+ const redirector = container.make(Redirector);
1909
+ const redirect2 = typeof redirector.guest === "function" ? redirector.guest(target2) : redirector.to(target2);
1910
+ return await redirect2.toResponse(request2);
1911
+ }
1912
+ /** config('http.redirect_guests_to'), then route('login'), then '/login'. */
1913
+ #guestRedirect(request2) {
1914
+ const configured = this.#config("http.redirect_guests_to", void 0);
1915
+ if (typeof configured === "function") return String(configured(request2));
1916
+ if (typeof configured === "string" && configured !== "") return configured;
1917
+ const container = this.#container();
1918
+ if (container.bound(UrlGenerator)) {
1919
+ const url2 = container.make(UrlGenerator);
1920
+ if (url2.hasRoute("login")) return url2.route("login");
1921
+ }
1922
+ return "/login";
1923
+ }
1924
+ #renderJson(error) {
1925
+ const status = statusFor(error);
1926
+ if (isHttpLike(error)) {
1927
+ const message = error instanceof Error && error.message !== "" ? error.message : statusText(status);
1928
+ return this.#json({ message }, status);
1929
+ }
1930
+ if (!this.#debug()) return this.#json({ message: "Server Error" }, status);
1931
+ const err = error instanceof Error ? error : new Error(String(error));
1932
+ const [frame] = stackFrames(err);
1933
+ return this.#json(
1934
+ {
1935
+ message: err.message,
1936
+ exception: err.name,
1937
+ file: frame?.file,
1938
+ line: frame?.line,
1939
+ trace: stackFrames(err).map((entry) => entry.text)
1940
+ },
1941
+ status
1942
+ );
1943
+ }
1944
+ async #renderHtml(request2, error) {
1945
+ const status = statusFor(error);
1946
+ if (!isHttpLike(error) && this.#debug()) {
1947
+ return this.#html(await debugPage(error, request2.method(), request2.fullUrl()), status);
1948
+ }
1949
+ const message = status === 403 && error instanceof Error && error.message !== "" ? error.message : statusText(status);
1950
+ const view = await this.#errorView(status, error, message);
1951
+ return this.#html(view ?? minimalPage(status, message), status);
1952
+ }
1953
+ /** resources/views/errors/{status}.tsx, then errors/4xx or errors/5xx, through the ViewFactory. */
1954
+ async #errorView(status, error, message) {
1955
+ const container = this.#container();
1956
+ if (!container.bound(ViewFactory)) return void 0;
1957
+ const views = container.make(ViewFactory);
1958
+ for (const name of [`errors.${status}`, `errors.${Math.floor(status / 100)}xx`]) {
1959
+ if (!views.exists(name)) continue;
1960
+ try {
1961
+ return await views.make(name, { exception: error, status, message }).render();
1962
+ } catch (viewError) {
1963
+ await this.report(viewError);
1964
+ return void 0;
1965
+ }
1966
+ }
1967
+ return void 0;
1968
+ }
1969
+ #httpRequest(request2) {
1970
+ if (typeof request2 === "object" && request2 !== null && typeof request2.expectsJson === "function") {
1971
+ return request2;
1972
+ }
1973
+ return new IncomingRequest(request2 instanceof Request ? request2 : new Request("http://localhost/"));
1974
+ }
1975
+ #json(body, status) {
1976
+ return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
1977
+ }
1978
+ #html(body, status) {
1979
+ return new Response(body, { status, headers: { "content-type": "text/html; charset=utf-8" } });
1980
+ }
1981
+ #redirect(location) {
1982
+ return new Response(null, { status: 302, headers: { Location: location } });
1983
+ }
1984
+ #debug() {
1985
+ return this.#config("app.debug", false) === true;
1986
+ }
1987
+ #config(key, fallback) {
1988
+ return this.app.bound(ConfigRepository) ? this.app.make(ConfigRepository).get(key, fallback) : fallback;
1989
+ }
1990
+ /** The request scope while handling a request (so scoped services resolve), else the application. */
1991
+ #container() {
1992
+ return currentContainer() ?? this.app;
1993
+ }
1994
+ };
1995
+ var ExceptionsConfiguration = class {
1996
+ constructor(handler) {
1997
+ this.handler = handler;
1998
+ }
1999
+ handler;
2000
+ report(callback) {
2001
+ this.handler.reportUsing(callback);
2002
+ return this;
2003
+ }
2004
+ render(callback) {
2005
+ this.handler.renderUsing(callback);
2006
+ return this;
2007
+ }
2008
+ dontReport(...classes) {
2009
+ this.handler.ignore(...classes);
2010
+ return this;
2011
+ }
2012
+ /** shouldRenderJsonWhen((request) => request.is('api/*')): JSON errors for those requests, whatever they accept. */
2013
+ shouldRenderJsonWhen(callback) {
2014
+ this.handler.shouldRenderJsonWhen(callback);
2015
+ return this;
2016
+ }
2017
+ };
2018
+ function applyExceptionCallbacks(app, handler) {
2019
+ if (!app.bound(ExceptionCallbacks)) return;
2020
+ const configurator = new ExceptionsConfiguration(handler);
2021
+ for (const callback of app.make(ExceptionCallbacks)) callback(configurator);
2022
+ }
2023
+ var HIDDEN_SEGMENT = /^\.|\.(ts|tsx|mts|cts)$|[. ]$/i;
2024
+ async function servePublicFile(publicPath, request2) {
2025
+ if (request2.method !== "GET" && request2.method !== "HEAD") return void 0;
2026
+ let pathname;
2027
+ try {
2028
+ pathname = decodeURIComponent(new URL(request2.url).pathname);
2029
+ } catch {
2030
+ return void 0;
2031
+ }
2032
+ if (pathname === "/" || pathname.includes("\0")) return void 0;
2033
+ const root = resolve(publicPath);
2034
+ const file = resolve(root, `.${pathname}`);
2035
+ if (!file.startsWith(root + sep)) return void 0;
2036
+ if (relative(root, file).split(/[\\/]/).some((segment) => HIDDEN_SEGMENT.test(segment))) return void 0;
2037
+ const info = await stat2(file).catch(() => void 0);
2038
+ if (info === void 0 || !info.isFile()) return void 0;
2039
+ const type = mimeFromExtension(file);
2040
+ const headers = new Headers({
2041
+ "content-type": type.startsWith("text/") || type === "application/json" ? `${type}; charset=utf-8` : type,
2042
+ "content-length": String(info.size),
2043
+ "last-modified": info.mtime.toUTCString()
2044
+ });
2045
+ if (pathname.startsWith("/build/assets/")) headers.set("cache-control", "public, max-age=31536000, immutable");
2046
+ if (request2.method === "HEAD") return new Response(null, { status: 200, headers });
2047
+ const body = Readable2.toWeb(createReadStream2(file));
2048
+ return new Response(body, { status: 200, headers });
2049
+ }
2050
+ async function terminateRequest(app, request2, response2) {
2051
+ try {
2052
+ if (app.bound(HttpKernel)) await app.make(HttpKernel).terminate?.(request2, response2);
2053
+ } catch (error) {
2054
+ console.error(error);
2055
+ }
2056
+ }
2057
+ async function handleSafely(app, request2, publicPath) {
2058
+ try {
2059
+ if (publicPath !== void 0) {
2060
+ const file = await servePublicFile(publicPath, request2);
2061
+ if (file !== void 0) return file;
2062
+ }
2063
+ return await app.handle(request2);
2064
+ } catch (error) {
2065
+ console.error(error);
2066
+ return plainServerError();
2067
+ }
2068
+ }
2069
+ function exitOnSignals(close, closed) {
2070
+ const onSignal = () => {
2071
+ void close().then(() => process.exit(0));
2072
+ };
2073
+ process.once("SIGINT", onSignal);
2074
+ process.once("SIGTERM", onSignal);
2075
+ void closed.then(() => {
2076
+ process.off("SIGINT", onSignal);
2077
+ process.off("SIGTERM", onSignal);
2078
+ });
2079
+ }
2080
+ var displayHost = (hostname) => hostname.includes(":") ? `[${hostname}]` : hostname;
2081
+ function bunRuntime() {
2082
+ return globalThis.Bun;
2083
+ }
2084
+ async function serveBun(app, options) {
2085
+ const bun = bunRuntime();
2086
+ if (bun === void 0) throw new HttpServerError("Bun.serve is not available: the process is not running on Bun.");
2087
+ let markClosed = () => void 0;
2088
+ const closed = new Promise((resolve2) => markClosed = resolve2);
2089
+ const server = bun.serve({
2090
+ port: options.port,
2091
+ hostname: options.hostname,
2092
+ async fetch(request2, current) {
2093
+ setClientAddress(request2, current.requestIP(request2)?.address);
2094
+ const response2 = await handleSafely(app, request2, options.publicPath);
2095
+ setTimeout(() => void terminateRequest(app, request2, response2), 0);
2096
+ return response2;
2097
+ },
2098
+ error(error) {
2099
+ console.error(error);
2100
+ return plainServerError();
2101
+ }
2102
+ });
2103
+ let closing;
2104
+ const close = () => closing ??= (async () => {
2105
+ let timer;
2106
+ const forced = new Promise((resolve2) => {
2107
+ timer = setTimeout(() => void Promise.resolve(server.stop(true)).then(resolve2), options.shutdownTimeout);
2108
+ });
2109
+ await Promise.race([Promise.resolve(server.stop(false)), forced]);
2110
+ clearTimeout(timer);
2111
+ markClosed();
2112
+ })();
2113
+ if (options.signals) exitOnSignals(close, closed);
2114
+ return {
2115
+ runtime: "bun",
2116
+ hostname: options.hostname,
2117
+ port: server.port,
2118
+ url: `http://${displayHost(options.hostname)}:${server.port}`,
2119
+ close,
2120
+ closed
2121
+ };
2122
+ }
2123
+ function requestBody(req) {
2124
+ let finished = false;
2125
+ const detach = () => {
2126
+ finished = true;
2127
+ req.off("data", onData);
2128
+ req.off("end", onEnd);
2129
+ req.off("error", onError);
2130
+ req.off("close", onClose);
2131
+ };
2132
+ let controller;
2133
+ const onData = (chunk2) => {
2134
+ if (finished) return;
2135
+ controller.enqueue(new Uint8Array(chunk2.buffer, chunk2.byteOffset, chunk2.byteLength));
2136
+ if ((controller.desiredSize ?? 0) <= 0) req.pause();
2137
+ };
2138
+ const onEnd = () => {
2139
+ if (finished) return;
2140
+ detach();
2141
+ controller.close();
2142
+ };
2143
+ const onError = (error) => {
2144
+ if (finished) return;
2145
+ detach();
2146
+ controller.error(error);
2147
+ };
2148
+ const onClose = () => {
2149
+ if (!finished && !req.complete) onError(new Error("The request was aborted."));
2150
+ };
2151
+ return new ReadableStream(
2152
+ {
2153
+ start(c) {
2154
+ controller = c;
2155
+ req.on("data", onData);
2156
+ req.on("end", onEnd);
2157
+ req.on("error", onError);
2158
+ req.on("close", onClose);
2159
+ req.pause();
2160
+ },
2161
+ pull() {
2162
+ req.resume();
2163
+ },
2164
+ cancel() {
2165
+ detach();
2166
+ }
2167
+ },
2168
+ { highWaterMark: 0 }
2169
+ );
2170
+ }
2171
+ function toWebRequest(req, res, fallbackHost) {
2172
+ const target2 = req.url ?? "/";
2173
+ const url2 = new URL(`http://${req.headers.host ?? fallbackHost}${target2.startsWith("/") ? target2 : `/${target2}`}`);
2174
+ const headers = new Headers();
2175
+ for (let i = 0; i < req.rawHeaders.length; i += 2) {
2176
+ const name = req.rawHeaders[i];
2177
+ if (!name.startsWith(":")) headers.append(name, req.rawHeaders[i + 1]);
2178
+ }
2179
+ const method = (req.method ?? "GET").toUpperCase();
2180
+ const controller = new AbortController();
2181
+ res.once("close", () => {
2182
+ if (!res.writableFinished) controller.abort();
2183
+ });
2184
+ const init = { method, headers, signal: controller.signal };
2185
+ if (method !== "GET" && method !== "HEAD") {
2186
+ init.body = requestBody(req);
2187
+ init.duplex = "half";
2188
+ }
2189
+ return new Request(url2, init);
2190
+ }
2191
+ async function writeResponse(res, response2) {
2192
+ res.statusCode = response2.status;
2193
+ if (response2.statusText !== "") res.statusMessage = response2.statusText;
2194
+ response2.headers.forEach((value, name) => {
2195
+ if (name !== "set-cookie") res.setHeader(name, value);
2196
+ });
2197
+ const cookies = response2.headers.getSetCookie();
2198
+ if (cookies.length > 0) res.setHeader("set-cookie", cookies);
2199
+ if (response2.body === null) {
2200
+ res.end();
2201
+ return;
2202
+ }
2203
+ await pipeline(Readable3.fromWeb(response2.body), res);
2204
+ }
2205
+ async function serveNode(app, options) {
2206
+ const fallbackHost = `${displayHost(options.hostname)}:${options.port}`;
2207
+ const server = createServer((req, res) => {
2208
+ void (async () => {
2209
+ let request2;
2210
+ try {
2211
+ request2 = toWebRequest(req, res, fallbackHost);
2212
+ } catch {
2213
+ res.statusCode = 400;
2214
+ res.end("Bad Request");
2215
+ return;
2216
+ }
2217
+ setClientAddress(request2, req.socket.remoteAddress);
2218
+ const response2 = await handleSafely(app, request2, options.publicPath);
2219
+ try {
2220
+ await writeResponse(res, response2);
2221
+ } catch {
2222
+ res.destroy();
2223
+ }
2224
+ await terminateRequest(app, request2, response2);
2225
+ })();
2226
+ });
2227
+ await new Promise((resolve2, reject) => {
2228
+ server.once("error", reject);
2229
+ server.listen(options.port, options.hostname, () => {
2230
+ server.off("error", reject);
2231
+ resolve2();
2232
+ });
2233
+ });
2234
+ const address = server.address();
2235
+ const closed = new Promise((resolve2) => server.once("close", () => resolve2()));
2236
+ let closing;
2237
+ const close = () => closing ??= (async () => {
2238
+ const done = new Promise((resolve2) => server.close(() => resolve2()));
2239
+ server.closeIdleConnections();
2240
+ const idle = setInterval(() => server.closeIdleConnections(), 50);
2241
+ const force = setTimeout(() => server.closeAllConnections(), options.shutdownTimeout);
2242
+ await done;
2243
+ clearInterval(idle);
2244
+ clearTimeout(force);
2245
+ })();
2246
+ if (options.signals) exitOnSignals(close, closed);
2247
+ return {
2248
+ runtime: "node",
2249
+ hostname: options.hostname,
2250
+ port: address.port,
2251
+ url: `http://${displayHost(options.hostname)}:${address.port}`,
2252
+ close,
2253
+ closed
2254
+ };
2255
+ }
2256
+ function detectRuntime() {
2257
+ return bunRuntime() !== void 0 ? "bun" : "node";
2258
+ }
2259
+ async function serve(app, options = {}) {
2260
+ const resolved = {
2261
+ port: options.port ?? Number(process.env.PORT ?? process.env.SERVER_PORT ?? 8e3),
2262
+ hostname: options.hostname ?? "127.0.0.1",
2263
+ signals: options.signals ?? true,
2264
+ shutdownTimeout: options.shutdownTimeout ?? 1e4,
2265
+ ...options.publicPath !== false && { publicPath: options.publicPath ?? app.publicPath() }
2266
+ };
2267
+ await app.boot();
2268
+ const runtime = options.runtime ?? detectRuntime();
2269
+ const server = runtime === "bun" ? await serveBun(app, resolved) : await serveNode(app, resolved);
2270
+ const onListen = options.onListen ?? ((running) => console.log(`Server running on [${running.url}].`));
2271
+ onListen(server);
2272
+ return server;
2273
+ }
2274
+ function io(command) {
2275
+ return {
2276
+ option: (name) => {
2277
+ const value = command.option(name);
2278
+ return value === void 0 || value === null || value === false || value === "" ? void 0 : String(value);
2279
+ },
2280
+ flag: (name) => {
2281
+ const value = command.option(name);
2282
+ return value === true || value === "true" || value === "1";
2283
+ },
2284
+ info: (message) => command.info(message),
2285
+ line: (message) => command.line(message),
2286
+ warn: (message) => command.warn(message),
2287
+ error: (message) => command.error(message)
2288
+ };
2289
+ }
2290
+ var middlewareName = (reference) => typeof reference === "string" ? reference : reference.name;
2291
+ function routeListRows(routes) {
2292
+ const rows = routes.map((route2) => {
2293
+ const listable = route2;
2294
+ return {
2295
+ method: route2.methods.join("|"),
2296
+ uri: route2.uri,
2297
+ name: route2.getName() ?? null,
2298
+ action: typeof listable.getActionName === "function" ? listable.getActionName() : "Closure",
2299
+ middleware: typeof listable.gatherMiddleware === "function" ? listable.gatherMiddleware().map(middlewareName) : []
2300
+ };
2301
+ });
2302
+ return rows.sort((a, b) => a.uri < b.uri ? -1 : a.uri > b.uri ? 1 : 0);
2303
+ }
2304
+ function filterRouteList(rows, filters) {
2305
+ const excluded = (filters.exceptPath ?? "").split(",").filter((path) => path !== "");
2306
+ return rows.filter((row) => {
2307
+ if (filters.name !== void 0 && !(row.name ?? "").includes(filters.name)) return false;
2308
+ if (filters.path !== void 0 && !row.uri.includes(filters.path)) return false;
2309
+ if (filters.method !== void 0 && !row.method.includes(filters.method.toUpperCase())) return false;
2310
+ return !excluded.some((path) => row.uri.includes(path));
2311
+ });
2312
+ }
2313
+ function formatRouteList(rows, width) {
2314
+ const methodWidth = Math.max(...rows.map((row) => row.method.length));
2315
+ const lines = rows.map((row) => {
2316
+ const spaces = " ".repeat(Math.max(methodWidth + 6 - row.method.length, 0));
2317
+ const action = row.action === "Closure" ? row.name ?? "" : row.name === null ? row.action : `${row.name} \u203A ${row.action}`;
2318
+ const used = row.method.length + spaces.length + row.uri.length + action.length + 6 + (action === "" ? 0 : 1);
2319
+ const dots = ".".repeat(Math.max(width - used, 0));
2320
+ return ` ${row.method} ${spaces}${row.uri}${dots === "" ? "" : ` ${dots}`}${action === "" ? "" : ` ${action}`}`;
2321
+ });
2322
+ const count = `Showing [${rows.length}] ${rows.length === 1 ? "route" : "routes"}`;
2323
+ return [...lines, "", `${" ".repeat(Math.max(width - count.length - 2, 0))}${count}`];
2324
+ }
2325
+ var API_ROUTES_STUB = `import { request, Route } from '@orieljs/framework'
2326
+
2327
+ Route.get('/user', () => request().user()).middleware('auth:token')
2328
+ `;
2329
+ function addApiRouting(source) {
2330
+ const code = source.replace(/^[ \t]*\/\/.*$/gm, "");
2331
+ if (/withRouting\(\s*\{[^}]*\bapi\s*:/.test(code)) return source;
2332
+ const commented = /^([ \t]*)\/\/\s*(api:\s*['"`][^'"`\n]*['"`],?)[ \t]*$/m.exec(source);
2333
+ if (commented !== null) return source.replace(commented[0], () => `${commented[1]}${commented[2]}`);
2334
+ const web = /^([ \t]*)web:\s*(['"`])[^'"`\n]*\2[ \t]*,?[ \t]*$/m.exec(source);
2335
+ if (web !== null) {
2336
+ const line = web[0].trimEnd();
2337
+ return source.replace(web[0], () => `${line.endsWith(",") ? line : `${line},`}
2338
+ ${web[1]}api: 'routes/api.ts',`);
2339
+ }
2340
+ const inline = /withRouting\(\s*\{/.exec(source);
2341
+ if (inline !== null) return source.replace(inline[0], () => `${inline[0]} api: 'routes/api.ts',`);
2342
+ const create = /^([ \t]*)\.create\(\)/m.exec(source);
2343
+ if (create !== null) return source.replace(create[0], () => `${create[1]}.withRouting({ api: 'routes/api.ts' })
2344
+ ${create[0]}`);
2345
+ return void 0;
2346
+ }
2347
+ function registerHttpCommands(kernel, app, options = {}) {
2348
+ const serveApp = options.serve ?? serve;
2349
+ const terminalWidth = options.terminalWidth ?? (() => process.stdout.columns ?? 120);
2350
+ kernel.command(
2351
+ "serve {--host=127.0.0.1 : The host address to serve the application on} {--port= : The port to serve the application on}",
2352
+ async function() {
2353
+ const out = io(this);
2354
+ const port = out.option("port");
2355
+ if (port !== void 0 && !/^\d+$/.test(port)) {
2356
+ out.error("The --port option must be a number.");
2357
+ return 1;
2358
+ }
2359
+ const server = await serveApp(app, {
2360
+ hostname: out.option("host") ?? "127.0.0.1",
2361
+ ...port !== void 0 && { port: Number(port) },
2362
+ onListen: (running) => {
2363
+ out.info(`Server running on [${running.url}].`);
2364
+ out.line("Press Ctrl+C to stop the server");
2365
+ }
2366
+ });
2367
+ await server.closed;
2368
+ return 0;
2369
+ }
2370
+ ).purpose("Serve the application on the Oriel development server");
2371
+ kernel.command(
2372
+ "route:list {--method= : Filter the routes by method} {--name= : Filter the routes by name} {--path= : Only show routes matching the given path pattern} {--except-path= : Do not display the routes matching the given path pattern} {--reverse : Reverse the ordering of the routes} {--json : Output the route list as JSON}",
2373
+ function() {
2374
+ const out = io(this);
2375
+ if (!app.bound(Router)) {
2376
+ out.error("The router is not available. Is the Routing service provider registered?");
2377
+ return 1;
2378
+ }
2379
+ const all = routeListRows(app.make(Router).getRoutes());
2380
+ if (all.length === 0) {
2381
+ out.error("Your application doesn't have any routes.");
2382
+ return 0;
2383
+ }
2384
+ const rows = filterRouteList(all, {
2385
+ ...out.option("method") !== void 0 && { method: out.option("method") },
2386
+ ...out.option("name") !== void 0 && { name: out.option("name") },
2387
+ ...out.option("path") !== void 0 && { path: out.option("path") },
2388
+ ...out.option("except-path") !== void 0 && { exceptPath: out.option("except-path") }
2389
+ });
2390
+ if (rows.length === 0) {
2391
+ out.error("Your application doesn't have any routes matching the given criteria.");
2392
+ return 0;
2393
+ }
2394
+ if (out.flag("reverse")) rows.reverse();
2395
+ if (out.flag("json")) {
2396
+ out.line(JSON.stringify(rows));
2397
+ return 0;
2398
+ }
2399
+ for (const line of formatRouteList(rows, terminalWidth())) out.line(line);
2400
+ return 0;
2401
+ }
2402
+ ).purpose("List all registered routes");
2403
+ kernel.command("install:api {--force : Overwrite any existing API routes file}", async function() {
2404
+ const out = io(this);
2405
+ const routesFile = app.basePath("routes/api.ts");
2406
+ if (existsSync4(routesFile) && !out.flag("force")) {
2407
+ out.error("API routes file already exists.");
2408
+ } else {
2409
+ await mkdir(dirname(routesFile), { recursive: true });
2410
+ await writeFile(routesFile, API_ROUTES_STUB);
2411
+ out.info("Published API routes file.");
2412
+ }
2413
+ const bootstrapFile = app.bootstrapPath("app.ts");
2414
+ const source = existsSync4(bootstrapFile) ? await readFile2(bootstrapFile, "utf8") : void 0;
2415
+ const updated = source === void 0 ? void 0 : addApiRouting(source);
2416
+ if (updated === void 0) {
2417
+ out.warn(
2418
+ "Unable to automatically add API route definition to bootstrap file. Add api: 'routes/api.ts' to withRouting() in bootstrap/app.ts."
2419
+ );
2420
+ } else if (updated !== source) {
2421
+ await writeFile(bootstrapFile, updated);
2422
+ }
2423
+ out.info("API scaffolding installed. Run [oriel migrate] to create the personal access tokens table.");
2424
+ return 0;
2425
+ }).purpose("Create an API routes file and prepare the application for API authentication");
2426
+ }
2427
+ var HttpServiceProvider = class extends ServiceProvider {
2428
+ register() {
2429
+ this.mergeConfigFrom(
2430
+ {
2431
+ trusted_proxies: [],
2432
+ max_body_size: 8 * 1024 * 1024,
2433
+ max_json_depth: 512,
2434
+ csrf_except: [],
2435
+ redirect_guests_to: void 0,
2436
+ redirect_users_to: void 0
2437
+ },
2438
+ "http"
2439
+ );
2440
+ this.app.singleton(HttpKernel, () => new Kernel(this.app));
2441
+ this.app.singleton(ExceptionHandler, () => {
2442
+ const handler = new ErrorHandler(this.app);
2443
+ applyExceptionCallbacks(this.app, handler);
2444
+ return handler;
2445
+ });
2446
+ this.app.singleton(ResponseFactory, () => new HttpResponseFactory());
2447
+ this.app.singleton(Redirector, () => new HttpRedirector());
2448
+ }
2449
+ boot() {
2450
+ this.app.booted(() => {
2451
+ const kernel = this.app.make(HttpKernel);
2452
+ if (kernel instanceof Kernel && this.app.bound(Router)) applyMiddlewareCallbacks(this.app, kernel);
2453
+ });
2454
+ if (this.app.bound(ConsoleKernel)) registerHttpCommands(this.app.make(ConsoleKernel), this.app);
2455
+ }
2456
+ };
2457
+
610
2458
  // ../Live/dist/index.js
611
2459
  import { AssertionError } from "assert";
612
2460
  import { isDeepStrictEqual } from "util";
613
2461
  import { AsyncLocalStorage } from "async_hooks";
614
2462
  import { randomBytes as randomBytes2 } from "crypto";
615
2463
  import { createHmac, timingSafeEqual } from "crypto";
616
- import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
617
- import { join as join5, relative, sep } from "path";
2464
+ import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
2465
+ import { join as join5, relative as relative2, sep as sep2 } from "path";
618
2466
  import { pathToFileURL as pathToFileURL2 } from "url";
619
2467
  import { existsSync as existsSync22 } from "fs";
620
- import { mkdir, writeFile } from "fs/promises";
621
- import { dirname, join as join23, relative as relative2, sep as sep2 } from "path";
2468
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
2469
+ import { dirname as dirname2, join as join23, relative as relative22, sep as sep22 } from "path";
622
2470
  Symbol.metadata ??= /* @__PURE__ */ Symbol.for("Symbol.metadata");
623
2471
  var LISTENERS = /* @__PURE__ */ Symbol.for("oriel:live.listeners");
624
2472
  var LOCKED = /* @__PURE__ */ Symbol.for("oriel:live.locked");
@@ -771,9 +2619,9 @@ var Component = class {
771
2619
  return new PendingEvent(event);
772
2620
  }
773
2621
  /** Sends the browser to `url` after this request; `navigate` uses o:navigate instead of a full page load. */
774
- redirect(url, options = {}) {
2622
+ redirect(url2, options = {}) {
775
2623
  const state = stateOf(this);
776
- state.redirect = url;
2624
+ state.redirect = url2;
777
2625
  if (options.navigate === true) state.redirectUsingNavigate = true;
778
2626
  else delete state.redirectUsingNavigate;
779
2627
  }
@@ -884,14 +2732,14 @@ function rootTag(html, component) {
884
2732
  let rootEnd = -1;
885
2733
  TOKEN.lastIndex = 0;
886
2734
  for (let match = TOKEN.exec(source); match !== null; match = TOKEN.exec(source)) {
887
- const [token, closing, opening, selfClosing] = match;
2735
+ const [token2, closing, opening, selfClosing] = match;
888
2736
  if (root === void 0 && match.index !== 0) fail22();
889
2737
  if (depth === 0 && root !== void 0) fail22();
890
2738
  if (opening !== void 0) {
891
2739
  const tag = opening.toLowerCase();
892
2740
  root ??= tag;
893
2741
  if (selfClosing === "/" || VOID.has(tag)) {
894
- if (depth === 0) rootEnd = match.index + token.length;
2742
+ if (depth === 0) rootEnd = match.index + token2.length;
895
2743
  continue;
896
2744
  }
897
2745
  if (RAW_TEXT.has(tag)) {
@@ -903,7 +2751,7 @@ function rootTag(html, component) {
903
2751
  depth++;
904
2752
  } else if (closing !== void 0) {
905
2753
  if (--depth < 0) fail22();
906
- if (depth === 0) rootEnd = match.index + token.length;
2754
+ if (depth === 0) rootEnd = match.index + token2.length;
907
2755
  }
908
2756
  }
909
2757
  if (root === void 0 || depth !== 0 || source.slice(rootEnd).trim() !== "") fail22();
@@ -920,23 +2768,31 @@ var fail = (message, actual, expected) => {
920
2768
  throw new AssertionError({ message, actual, expected, operator: "live" });
921
2769
  };
922
2770
  var getPath = (target2, path) => path.split(".").reduce((node, segment) => typeof node === "object" && node !== null ? node[segment] : void 0, target2);
2771
+ var TestRequestScope = token("Testing.RequestScope");
2772
+ function requestScope2() {
2773
+ const container = currentContainer() ?? Container.getInstance();
2774
+ return container?.bound(TestRequestScope) === true ? container.make(TestRequestScope) : (run) => run();
2775
+ }
923
2776
  var Testable = class _Testable {
924
- constructor(manager, result) {
2777
+ constructor(manager, scope, result) {
925
2778
  this.manager = manager;
2779
+ this.scope = scope;
926
2780
  this.#snapshot = JSON.stringify(result.snapshot);
927
2781
  this.#html = result.html ?? "";
928
2782
  this.#effects = result.effects;
929
2783
  this.#instance = result.instance;
930
2784
  }
931
2785
  manager;
2786
+ scope;
932
2787
  #snapshot;
933
2788
  #html;
934
2789
  #effects;
935
2790
  #instance;
936
2791
  /** Mounts `component` with `params`, as `<Live component={…} {...params} />` would. */
937
2792
  static async create(component, params = {}) {
2793
+ const scope = requestScope2();
938
2794
  const manager = inject(LiveManager);
939
- return new _Testable(manager, await manager.mountComponent(component, params));
2795
+ return new _Testable(manager, scope, await scope(() => manager.mountComponent(component, params)));
940
2796
  }
941
2797
  /** The component instance after the last request. */
942
2798
  instance() {
@@ -971,7 +2827,7 @@ var Testable = class _Testable {
971
2827
  return this.#update({}, [{ method: "$refresh", params: [] }]);
972
2828
  }
973
2829
  async #update(updates, calls) {
974
- const result = await this.manager.updateComponent(this.#snapshot, updates, calls);
2830
+ const result = await this.scope(() => this.manager.updateComponent(this.#snapshot, updates, calls));
975
2831
  this.#snapshot = JSON.stringify(result.snapshot);
976
2832
  if (result.html !== void 0) this.#html = result.html;
977
2833
  this.#effects = result.effects;
@@ -1029,10 +2885,10 @@ var Testable = class _Testable {
1029
2885
  if (found.length > 0) fail(`Component has errors: "${found.join('", "')}"`, errors);
1030
2886
  return this;
1031
2887
  }
1032
- assertRedirect(url) {
1033
- const redirect = this.#effects.redirect;
1034
- if (redirect === void 0) fail("Component did not perform a redirect.");
1035
- if (url !== void 0 && redirect !== url) fail("Component redirected to an unexpected URL.", redirect, url);
2888
+ assertRedirect(url2) {
2889
+ const redirect2 = this.#effects.redirect;
2890
+ if (redirect2 === void 0) fail("Component did not perform a redirect.");
2891
+ if (url2 !== void 0 && redirect2 !== url2) fail("Component redirected to an unexpected URL.", redirect2, url2);
1036
2892
  return this;
1037
2893
  }
1038
2894
  assertNoRedirect() {
@@ -1088,8 +2944,8 @@ function defaultName(cls) {
1088
2944
  return typeof name === "string" && name !== "" ? Str.kebab(name) : void 0;
1089
2945
  }
1090
2946
  function componentFiles(directory) {
1091
- if (!existsSync4(directory)) return [];
1092
- return readdirSync2(directory, { recursive: true, encoding: "utf8" }).filter((file) => /\.(tsx|ts|jsx|js|mts|mjs)$/.test(file) && !/\.(test|spec|d)\.[^.]+$/.test(file)).map((file) => file.split(sep).join("/")).sort();
2947
+ if (!existsSync5(directory)) return [];
2948
+ return readdirSync2(directory, { recursive: true, encoding: "utf8" }).filter((file) => /\.(tsx|ts|jsx|js|mts|mjs)$/.test(file) && !/\.(test|spec|d)\.[^.]+$/.test(file)).map((file) => file.split(sep2).join("/")).sort();
1093
2949
  }
1094
2950
  async function discover(directory) {
1095
2951
  const found = [];
@@ -1098,16 +2954,87 @@ async function discover(directory) {
1098
2954
  /* @vite-ignore */
1099
2955
  pathToFileURL2(join5(directory, file)).href
1100
2956
  );
1101
- found.push([componentName(relative(directory, join5(directory, file))), module.default]);
2957
+ found.push([componentName(relative2(directory, join5(directory, file))), module.default]);
1102
2958
  }
1103
2959
  return found;
1104
2960
  }
2961
+ var PERSISTENT_MIDDLEWARE = ["auth", "verified", "guest"];
2962
+ function canMatch(router) {
2963
+ const methods = router;
2964
+ return ["getRoutesByMatchOrder", "getPatterns", "getPatternVersion", "getMiddleware", "getMiddlewareGroups"].every((name) => typeof methods[name] === "function");
2965
+ }
2966
+ function isMatchingRoute(route2) {
2967
+ const candidate = route2;
2968
+ return typeof candidate === "object" && candidate !== null && Array.isArray(candidate.methods) && ["match", "gatherMiddleware", "excludedMiddleware"].every((name) => typeof candidate[name] === "function");
2969
+ }
2970
+ function routePath(path) {
2971
+ const trimmed = path.replace(/^\/+|\/+$/g, "").replace(/\/{2,}/g, "/");
2972
+ return trimmed === "" ? "" : `/${trimmed}`;
2973
+ }
2974
+ function expand(references, aliases, groups, out = [], visiting = /* @__PURE__ */ new Set()) {
2975
+ for (const reference of references) {
2976
+ if (typeof reference === "function") {
2977
+ out.push({ cls: reference, parameters: [] });
2978
+ continue;
2979
+ }
2980
+ const index = reference.indexOf(":");
2981
+ const name = index === -1 ? reference : reference.slice(0, index);
2982
+ if (index === -1 && Object.hasOwn(groups, name)) {
2983
+ if (visiting.has(name)) continue;
2984
+ visiting.add(name);
2985
+ expand(groups[name], aliases, groups, out, visiting);
2986
+ visiting.delete(name);
2987
+ } else if (Object.hasOwn(aliases, name)) {
2988
+ out.push({ cls: aliases[name], parameters: index === -1 ? [] : reference.slice(index + 1).split(",") });
2989
+ }
2990
+ }
2991
+ return out;
2992
+ }
2993
+ function persistentMiddleware(router, persistent, method, path) {
2994
+ if (!canMatch(router)) throw new LiveError("Live re-applies the page route's middleware to updates, which needs Oriel's router.");
2995
+ const routes = router.getRoutesByMatchOrder();
2996
+ if (!Array.isArray(routes) || !routes.every(isMatchingRoute)) throw new LiveError("Live re-applies the page route's middleware to updates, which needs Oriel's routes.");
2997
+ const verb = method.toUpperCase();
2998
+ const target2 = routePath(path);
2999
+ const patterns = router.getPatterns();
3000
+ const version = router.getPatternVersion();
3001
+ const route2 = routes.find((candidate) => candidate.methods.includes(verb) && candidate.match(target2, patterns, version) !== void 0);
3002
+ if (route2 === void 0) return [];
3003
+ const aliases = router.getMiddleware();
3004
+ const groups = router.getMiddlewareGroups();
3005
+ const wanted = new Set(persistent.flatMap((name) => Object.hasOwn(aliases, name) ? [aliases[name]] : []));
3006
+ const excluded = new Set(expand(route2.excludedMiddleware(), aliases, groups).map((entry) => entry.cls));
3007
+ const ids = /* @__PURE__ */ new Map();
3008
+ const seen = /* @__PURE__ */ new Set();
3009
+ return expand(route2.gatherMiddleware(), aliases, groups).filter((entry) => {
3010
+ if (!ids.has(entry.cls)) ids.set(entry.cls, ids.size);
3011
+ const key = `${ids.get(entry.cls)}:${entry.parameters.join(",")}`;
3012
+ if (!wanted.has(entry.cls) || excluded.has(entry.cls) || seen.has(key)) return false;
3013
+ seen.add(key);
3014
+ return true;
3015
+ });
3016
+ }
3017
+ async function throughMiddleware(container, request2, middleware, destination) {
3018
+ let ran;
3019
+ const run = async (index) => {
3020
+ if (index === middleware.length) {
3021
+ ran = { result: await destination() };
3022
+ return new Response(null, { status: 204 });
3023
+ }
3024
+ const { cls, parameters } = middleware[index];
3025
+ const instance = container.make(cls);
3026
+ const next = () => run(index + 1);
3027
+ return instance.handle(request2, next, ...parameters);
3028
+ };
3029
+ const response2 = await run(0);
3030
+ return ran ? ran.result : response2;
3031
+ }
1105
3032
  function isModel(value) {
1106
3033
  if (typeof value !== "object" || value === null) return false;
1107
3034
  const candidate = value;
1108
3035
  return typeof candidate.getKey === "function" && typeof candidate.getKeyName === "function" && typeof candidate.getTable === "function" && typeof candidate.exists === "boolean";
1109
3036
  }
1110
- var isPlainObject = (value) => {
3037
+ var isPlainObject2 = (value) => {
1111
3038
  const proto = Object.getPrototypeOf(value);
1112
3039
  return proto === Object.prototype || proto === null;
1113
3040
  };
@@ -1154,7 +3081,7 @@ function dehydrate(value, registry, path, seen = /* @__PURE__ */ new Set()) {
1154
3081
  }
1155
3082
  return [items.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), { s: "clctn" }];
1156
3083
  }
1157
- if (isPlainObject(object)) {
3084
+ if (isPlainObject2(object)) {
1158
3085
  const out = {};
1159
3086
  for (const [key, item] of Object.entries(object)) out[key] = dehydrate(item, registry, `${path}.${key}`, seen);
1160
3087
  return [out, { s: "obj" }];
@@ -1241,10 +3168,11 @@ function isUpdatable(instance, path) {
1241
3168
  return !isModel(node) && !(node instanceof Collection);
1242
3169
  }
1243
3170
  var renderContext = new AsyncLocalStorage();
1244
- function optional(container, token) {
1245
- if (!container.bound(token)) return void 0;
3171
+ var pageRequest = new AsyncLocalStorage();
3172
+ function optional(container, token2) {
3173
+ if (!container.bound(token2)) return void 0;
1246
3174
  try {
1247
- return container.make(token);
3175
+ return container.make(token2);
1248
3176
  } catch {
1249
3177
  return void 0;
1250
3178
  }
@@ -1291,8 +3219,8 @@ var LiveComponentManager = class {
1291
3219
  if (!isComponentClass(cls)) throw new LiveComponentNotFoundError(name);
1292
3220
  return cls;
1293
3221
  }
1294
- isLiveRequest(request) {
1295
- return request.header("X-Oriel-Live") === "true";
3222
+ isLiveRequest(request2) {
3223
+ return request2.header("X-Oriel-Live") === "true";
1296
3224
  }
1297
3225
  /** The first render: HTML with o:id, o:snapshot and o:effects on the root element. */
1298
3226
  async mount(component, params = {}, key) {
@@ -1322,8 +3250,9 @@ var LiveComponentManager = class {
1322
3250
  }
1323
3251
  this.prepareComputed(instance);
1324
3252
  await hook(instance, "mount", params);
1325
- const request = optional(this.container(), HttpRequest);
1326
- const memo = { id: state.id, name, path: request?.path() ?? "", method: request?.method() ?? "GET", children: {}, errors: {} };
3253
+ const request2 = optional(this.container(), HttpRequest);
3254
+ const page = pageRequest.getStore() ?? { path: request2?.path() ?? "", method: request2?.method() ?? "GET" };
3255
+ const memo = { id: state.id, name, path: page.path, method: page.method, children: {}, errors: {} };
1327
3256
  const { html, tag } = await this.render(instance, memo, {});
1328
3257
  const effects = this.effects(instance);
1329
3258
  const listeners = Object.keys(listenersOf(cls));
@@ -1333,10 +3262,11 @@ var LiveComponentManager = class {
1333
3262
  return { instance, snapshot, effects, html: insertAttributesIntoRoot(html, attributes), tag };
1334
3263
  }
1335
3264
  /** Applies each component's updates and calls, re-renders it and returns its new snapshot and effects. */
1336
- async update(request) {
3265
+ async update(request2) {
1337
3266
  const components = [];
1338
- for (const component of request.components) {
1339
- const result = await this.updateComponent(component.snapshot, component.updates, component.calls);
3267
+ for (const component of request2.components) {
3268
+ const { memo } = this.verify(component.snapshot);
3269
+ const result = await pageRequest.run({ path: memo.path, method: memo.method }, () => this.updateComponent(component.snapshot, component.updates, component.calls));
1340
3270
  components.push({ snapshot: JSON.stringify(result.snapshot), effects: result.effects });
1341
3271
  }
1342
3272
  return { components };
@@ -1378,10 +3308,27 @@ var LiveComponentManager = class {
1378
3308
  if (calls.length > 0) effects.returns = returns;
1379
3309
  return { instance, snapshot: await this.snapshot(instance, memo), effects, html, tag };
1380
3310
  }
1381
- /** The route action of POST /oriel/live/update. */
1382
- async handle(request) {
1383
- if (!this.isLiveRequest(request)) throw new InvalidLiveRequestError("Live update requests need the X-Oriel-Live header.");
1384
- return this.update(this.payload(request));
3311
+ /**
3312
+ * The route action of POST /oriel/live/update. Each component's update runs behind the auth middleware of the route
3313
+ * that rendered its page (Livewire's persistent middleware): a signed-out tab gets a 401, never the action. When a
3314
+ * middleware answers instead (a redirect), that is the response.
3315
+ */
3316
+ async handle(request2) {
3317
+ if (!this.isLiveRequest(request2)) throw new InvalidLiveRequestError("Live update requests need the X-Oriel-Live header.");
3318
+ const container = this.container();
3319
+ const persistent = optional(container, ConfigRepository)?.get("live.persistent_middleware", PERSISTENT_MIDDLEWARE) ?? PERSISTENT_MIDDLEWARE;
3320
+ const components = [];
3321
+ for (const component of this.payload(request2).components) {
3322
+ const { memo } = this.verify(component.snapshot);
3323
+ const middleware = persistentMiddleware(container.make(Router), persistent, memo.method, memo.path);
3324
+ const result = await pageRequest.run(
3325
+ { path: memo.path, method: memo.method },
3326
+ () => throughMiddleware(container, request2, middleware, () => this.updateComponent(component.snapshot, component.updates, component.calls))
3327
+ );
3328
+ if (result instanceof Response) return result;
3329
+ components.push({ snapshot: JSON.stringify(result.snapshot), effects: result.effects });
3330
+ }
3331
+ return { components };
1385
3332
  }
1386
3333
  /** A full-page component (Route.get('/counter', Counter)): mounted with the route parameters, inside the layout. */
1387
3334
  async page(component) {
@@ -1389,9 +3336,9 @@ var LiveComponentManager = class {
1389
3336
  const params = { ...optional(container, CurrentRoute)?.parameters ?? {} };
1390
3337
  const cls = component.constructor;
1391
3338
  const result = await this.mountComponent(cls, params, component);
1392
- const redirect = result.effects.redirect;
1393
- if (redirect !== void 0) {
1394
- return optional(container, Redirector)?.to(redirect) ?? new Response(null, { status: 302, headers: { Location: redirect } });
3339
+ const redirect2 = result.effects.redirect;
3340
+ if (redirect2 !== void 0) {
3341
+ return optional(container, Redirector)?.to(redirect2) ?? new Response(null, { status: 302, headers: { Location: redirect2 } });
1395
3342
  }
1396
3343
  const layout = cls.layout ?? optional(container, ConfigRepository)?.get("live.layout") ?? "components.layouts.app";
1397
3344
  const html = result.html;
@@ -1424,11 +3371,11 @@ var LiveComponentManager = class {
1424
3371
  return this.container().make(Encrypter).getKey();
1425
3372
  }
1426
3373
  /** The request body, read from the raw JSON when the request has it, so TrimStrings can't alter typed values. */
1427
- payload(request) {
3374
+ payload(request2) {
1428
3375
  let body;
1429
3376
  try {
1430
- const content = request.content;
1431
- body = typeof content === "function" ? JSON.parse(content.call(request)) : request.all();
3377
+ const content = request2.content;
3378
+ body = typeof content === "function" ? JSON.parse(content.call(request2)) : request2.all();
1432
3379
  } catch {
1433
3380
  throw new InvalidLiveRequestError("The Live update request body is not valid JSON.");
1434
3381
  }
@@ -1563,7 +3510,7 @@ test('{{ name }} renders', async () => {
1563
3510
  `;
1564
3511
  var CLASS_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
1565
3512
  function importPath(from, to) {
1566
- const path = relative2(dirname(from), to).split(sep2).join("/").replace(/\.tsx?$/, "");
3513
+ const path = relative22(dirname2(from), to).split(sep22).join("/").replace(/\.tsx?$/, "");
1567
3514
  return path.startsWith(".") ? path : `./${path}`;
1568
3515
  }
1569
3516
  function registerLiveCommands(kernel, app) {
@@ -1579,23 +3526,23 @@ function registerLiveCommands(kernel, app) {
1579
3526
  const file = join23(directory, ...segments.slice(0, -1), `${className}.tsx`);
1580
3527
  const force = this.option("force") === true;
1581
3528
  if (existsSync22(file) && !force) {
1582
- this.error(`Component [${relative2(app.basePath(), file)}] already exists.`);
3529
+ this.error(`Component [${relative22(app.basePath(), file)}] already exists.`);
1583
3530
  return 1;
1584
3531
  }
1585
- await mkdir(dirname(file), { recursive: true });
1586
- await writeFile(file, COMPONENT_STUB.replace("{{ class }}", () => className));
1587
- this.info(`Component [${relative2(app.basePath(), file)}] created successfully.`);
3532
+ await mkdir2(dirname2(file), { recursive: true });
3533
+ await writeFile2(file, COMPONENT_STUB.replace("{{ class }}", () => className));
3534
+ this.info(`Component [${relative22(app.basePath(), file)}] created successfully.`);
1588
3535
  if (this.option("test") === true) {
1589
3536
  const testFile = app.basePath(join23("tests", "Feature", "Live", ...segments.slice(0, -1), `${className}.test.ts`));
1590
3537
  if (existsSync22(testFile) && !force) {
1591
- this.error(`Test [${relative2(app.basePath(), testFile)}] already exists.`);
3538
+ this.error(`Test [${relative22(app.basePath(), testFile)}] already exists.`);
1592
3539
  return 1;
1593
3540
  }
1594
3541
  const name = segments.map((segment) => Str.kebab(segment)).join(".");
1595
3542
  const test = TEST_STUB.replaceAll("{{ class }}", () => className).replace("{{ component }}", () => importPath(testFile, file)).replace("{{ testCase }}", () => importPath(testFile, app.basePath(join23("tests", "TestCase.ts")))).replace("{{ name }}", () => name);
1596
- await mkdir(dirname(testFile), { recursive: true });
1597
- await writeFile(testFile, test);
1598
- this.info(`Test [${relative2(app.basePath(), testFile)}] created successfully.`);
3543
+ await mkdir2(dirname2(testFile), { recursive: true });
3544
+ await writeFile2(testFile, test);
3545
+ this.info(`Test [${relative22(app.basePath(), testFile)}] created successfully.`);
1599
3546
  }
1600
3547
  return 0;
1601
3548
  }).purpose("Create a new Live component");
@@ -1603,7 +3550,7 @@ function registerLiveCommands(kernel, app) {
1603
3550
  var UPDATE_URI = "/oriel/live/update";
1604
3551
  var LiveServiceProvider = class extends ServiceProvider {
1605
3552
  register() {
1606
- this.mergeConfigFrom({ class_path: this.app.appPath("Live"), layout: "components.layouts.app" }, "live");
3553
+ this.mergeConfigFrom({ class_path: this.app.appPath("Live"), layout: "components.layouts.app", persistent_middleware: [...PERSISTENT_MIDDLEWARE] }, "live");
1607
3554
  this.app.singleton(LiveComponentManager, () => new LiveComponentManager(this.app));
1608
3555
  this.app.singleton(LiveManager, (container) => container.make(LiveComponentManager));
1609
3556
  }
@@ -1623,7 +3570,7 @@ var LiveServiceProvider = class extends ServiceProvider {
1623
3570
  // ../Testing/dist/index.js
1624
3571
  import { AssertionError as AssertionError2 } from "assert";
1625
3572
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
1626
- import { existsSync as existsSync5 } from "fs";
3573
+ import { existsSync as existsSync6 } from "fs";
1627
3574
  import { join as join6 } from "path";
1628
3575
  import { createHmac as createHmac2 } from "crypto";
1629
3576
  import { randomUUID } from "crypto";
@@ -1649,7 +3596,7 @@ function format(value) {
1649
3596
  return String(value);
1650
3597
  }
1651
3598
  }
1652
- function escapeHtml(value) {
3599
+ function escapeHtml2(value) {
1653
3600
  return value.replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#039;" })[char]);
1654
3601
  }
1655
3602
  function stripTags(html) {
@@ -2162,12 +4109,12 @@ function sizeOf(value) {
2162
4109
  function toPlain(value) {
2163
4110
  return value === void 0 ? void 0 : JSON.parse(JSON.stringify(value));
2164
4111
  }
2165
- function inertiaPageOf(response) {
4112
+ function inertiaPageOf(response2) {
2166
4113
  let page;
2167
4114
  try {
2168
- if (response.headers.has("X-Inertia")) page = JSON.parse(response.content);
4115
+ if (response2.headers.has("X-Inertia")) page = JSON.parse(response2.content);
2169
4116
  else {
2170
- const match = /<script data-page="[^"]*" type="application\/json">([\s\S]*?)<\/script>/.exec(response.content);
4117
+ const match = /<script data-page="[^"]*" type="application\/json">([\s\S]*?)<\/script>/.exec(response2.content);
2171
4118
  page = match === null ? void 0 : JSON.parse(match[1]);
2172
4119
  }
2173
4120
  } catch {
@@ -2180,11 +4127,11 @@ function inertiaPageOf(response) {
2180
4127
  var AssertableInertia = class _AssertableInertia extends AssertableJson {
2181
4128
  #page;
2182
4129
  #response;
2183
- static fromTestResponse(response) {
2184
- const page = inertiaPageOf(response);
4130
+ static fromTestResponse(response2) {
4131
+ const page = inertiaPageOf(response2);
2185
4132
  const instance = new _AssertableInertia(page.props);
2186
4133
  instance.#page = page;
2187
- instance.#response = response;
4134
+ instance.#response = response2;
2188
4135
  return instance;
2189
4136
  }
2190
4137
  /** Scopes are plain AssertableJson: the page-level methods only make sense at the root. */
@@ -2204,7 +4151,7 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
2204
4151
  if (shouldExist === true || shouldExist === void 0 && config?.get("inertia.testing.ensure_pages_exist", true) !== false) {
2205
4152
  const paths = config?.get("inertia.pages.paths", []) ?? [];
2206
4153
  const extensions = config?.get("inertia.pages.extensions", []) ?? [];
2207
- const exists = paths.some((path) => extensions.some((extension) => existsSync5(join6(path, `${value}.${extension}`))));
4154
+ const exists = paths.some((path) => extensions.some((extension) => existsSync6(join6(path, `${value}.${extension}`))));
2208
4155
  if (!exists) fail2(`Inertia page component file [${value}] does not exist.`);
2209
4156
  }
2210
4157
  return this;
@@ -2253,8 +4200,8 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
2253
4200
  if (onlyList !== void 0 || exceptList !== void 0) headers["X-Inertia-Partial-Component"] = this.page.component;
2254
4201
  if (onlyList !== void 0) headers["X-Inertia-Partial-Data"] = onlyList;
2255
4202
  if (exceptList !== void 0) headers["X-Inertia-Partial-Except"] = exceptList;
2256
- const response = await client.request("GET", this.page.url, { headers });
2257
- const reloaded = _AssertableInertia.fromTestResponse(response);
4203
+ const response2 = await client.request("GET", this.page.url, { headers });
4204
+ const reloaded = _AssertableInertia.fromTestResponse(response2);
2258
4205
  reloaded.component(this.page.component, false).url(this.page.url).version(this.page.version);
2259
4206
  await callback?.(reloaded);
2260
4207
  return this;
@@ -2349,8 +4296,8 @@ function isExpired(cookie, now = Date.now()) {
2349
4296
  if (cookie.maxAge !== void 0 && !Number.isNaN(cookie.maxAge)) return cookie.maxAge <= 0;
2350
4297
  return cookie.expires !== void 0 && cookie.expires.getTime() <= now;
2351
4298
  }
2352
- function responseCookies(response) {
2353
- return response.headers.getSetCookie().map(parseSetCookie);
4299
+ function responseCookies(response2) {
4300
+ return response2.headers.getSetCookie().map(parseSetCookie);
2354
4301
  }
2355
4302
  function cookieHeader(cookies) {
2356
4303
  return [...cookies].map(([name, value]) => `${encodeURIComponent(name)}=${encodeURIComponent(value)}`).join("; ");
@@ -2367,8 +4314,8 @@ var TestResponse = class _TestResponse {
2367
4314
  context;
2368
4315
  decoded;
2369
4316
  /** Reads the body once, so every assertion is synchronous. */
2370
- static async from(response, context = {}) {
2371
- return new _TestResponse(response, await response.text(), context);
4317
+ static async from(response2, context = {}) {
4318
+ return new _TestResponse(response2, await response2.text(), context);
2372
4319
  }
2373
4320
  get status() {
2374
4321
  return this.baseResponse.status;
@@ -2860,11 +4807,11 @@ ${JSON.stringify(errors, null, 2)}`, { actual: errors, expected: {} });
2860
4807
  return Arr.get(this.viewDataOf(this.ensureView()), key);
2861
4808
  }
2862
4809
  // Internals ---------------------------------------------------------------------------------------------
2863
- absolute(url) {
4810
+ absolute(url2) {
2864
4811
  try {
2865
- return new URL(url, this.context.request?.url ?? "http://localhost").href;
4812
+ return new URL(url2, this.context.request?.url ?? "http://localhost").href;
2866
4813
  } catch {
2867
- return url;
4814
+ return url2;
2868
4815
  }
2869
4816
  }
2870
4817
  excerpt(text = this.content) {
@@ -2919,7 +4866,7 @@ ${this.excerpt()}`;
2919
4866
  return decryptCookie(app.make(Encrypter), cookie.name, cookie.value);
2920
4867
  }
2921
4868
  needles(value, escape) {
2922
- return [value].flat().map((needle) => escape ? escapeHtml(needle) : needle);
4869
+ return [value].flat().map((needle) => escape ? escapeHtml2(needle) : needle);
2923
4870
  }
2924
4871
  inOrder(haystack, needles) {
2925
4872
  let position = 0;
@@ -2964,9 +4911,9 @@ function recordingViewFactory(factory, views) {
2964
4911
  }
2965
4912
  function recordingExceptionHandler(handler, record) {
2966
4913
  return forward(handler, {
2967
- async render(request, error) {
4914
+ async render(request2, error) {
2968
4915
  record(error);
2969
- return handler.render(request, error);
4916
+ return handler.render(request2, error);
2970
4917
  }
2971
4918
  });
2972
4919
  }
@@ -3004,13 +4951,13 @@ var HttpClient = class {
3004
4951
  /** Requests whose actingAs users reached the guards (the AuthFactory they went through). */
3005
4952
  actingAsApplied = /* @__PURE__ */ new WeakMap();
3006
4953
  async request(method, uri, options = {}) {
3007
- let response = await this.send(this.buildRequest(method, uri, options));
3008
- if (!this.followRedirects) return response;
3009
- for (let hops = 0; isRedirect(response.status); hops++) {
3010
- if (hops >= MAX_REDIRECTS) fail2(`Too many redirects (more than ${MAX_REDIRECTS}), last location [${response.headers.get("Location")}].`);
3011
- response = await this.send(this.buildRequest("GET", response.headers.get("Location") ?? "/", {}));
4954
+ let response2 = await this.send(this.buildRequest(method, uri, options));
4955
+ if (!this.followRedirects) return response2;
4956
+ for (let hops = 0; isRedirect(response2.status); hops++) {
4957
+ if (hops >= MAX_REDIRECTS) fail2(`Too many redirects (more than ${MAX_REDIRECTS}), last location [${response2.headers.get("Location")}].`);
4958
+ response2 = await this.send(this.buildRequest("GET", response2.headers.get("Location") ?? "/", {}));
3012
4959
  }
3013
- return response;
4960
+ return response2;
3014
4961
  }
3015
4962
  /** Runs `fn` in a scope prepared like a request's (carried session, actingAs users), e.g. for assertAuthenticated(). */
3016
4963
  async inScope(fn) {
@@ -3021,23 +4968,48 @@ var HttpClient = class {
3021
4968
  return fn(scope);
3022
4969
  });
3023
4970
  }
4971
+ /**
4972
+ * Runs `run` in a request scope like this client's requests have (a request for `method uri` bound, the carried
4973
+ * session, actingAs users), then carries the session and the users on, as after a request. Live.test() runs its
4974
+ * mount and updates through it.
4975
+ */
4976
+ async withinRequest(run, method = "GET", uri = "/") {
4977
+ await this.app.boot();
4978
+ const request2 = this.buildRequest(method, uri, {});
4979
+ setClientAddress2(request2, "127.0.0.1");
4980
+ const scope = this.app.createScope();
4981
+ const context = { request: request2, app: this.app, client: this };
4982
+ const result = await scope.run(async () => {
4983
+ this.beforeRequest(scope, context);
4984
+ scope.instance(HttpRequest, new IncomingRequest(request2));
4985
+ if (scope.bound(AuthFactory)) {
4986
+ try {
4987
+ await scope.make(AuthFactory).guard().check();
4988
+ } catch {
4989
+ }
4990
+ }
4991
+ return run();
4992
+ });
4993
+ await scope.run(() => this.afterRequest(scope, context));
4994
+ return result;
4995
+ }
3024
4996
  /** Runs the request through the app's real HttpKernel, in a request scope this client owns. */
3025
- async send(request) {
4997
+ async send(request2) {
3026
4998
  const app = this.app;
3027
4999
  await app.boot();
3028
- setClientAddress(request, "127.0.0.1");
5000
+ setClientAddress2(request2, "127.0.0.1");
3029
5001
  const scope = app.createScope();
3030
- const context = { request, app, client: this };
3031
- const response = await scope.run(async () => {
5002
+ const context = { request: request2, app, client: this };
5003
+ const response2 = await scope.run(async () => {
3032
5004
  await this.beforeRequest(scope, context);
3033
5005
  this.previousUrl = void 0;
3034
- return scope.make(HttpKernel).handle(request);
5006
+ return scope.make(HttpKernel).handle(request2);
3035
5007
  });
3036
5008
  await scope.run(() => this.afterRequest(scope, context));
3037
- this.rememberCookies(response);
3038
- await app.make(HttpKernel).terminate?.(request, response);
5009
+ this.rememberCookies(response2);
5010
+ await app.make(HttpKernel).terminate?.(request2, response2);
3039
5011
  if (!this.handleExceptions && context.exception !== void 0) throw context.exception;
3040
- return TestResponse.from(response, context);
5012
+ return TestResponse.from(response2, context);
3041
5013
  }
3042
5014
  /** Prepares the request scope: recorders, the carried session and actingAs users. */
3043
5015
  beforeRequest(scope, context) {
@@ -3092,12 +5064,12 @@ var HttpClient = class {
3092
5064
  }
3093
5065
  buildRequest(method, uri, options) {
3094
5066
  const verb = method.toUpperCase();
3095
- const url = new URL(uri, this.baseUrl());
5067
+ const url2 = new URL(uri, this.baseUrl());
3096
5068
  const headers = new Headers({ ...this.headers, ...options.headers });
3097
5069
  let body;
3098
5070
  const data = options.data ?? {};
3099
5071
  if (verb === "GET" || verb === "HEAD") {
3100
- for (const [key, value] of flatten(data)) if (typeof value === "string") url.searchParams.append(key, value);
5072
+ for (const [key, value] of flatten(data)) if (typeof value === "string") url2.searchParams.append(key, value);
3101
5073
  } else if (options.json) {
3102
5074
  body = JSON.stringify(data);
3103
5075
  } else if (Object.keys(data).length > 0) {
@@ -3116,7 +5088,7 @@ var HttpClient = class {
3116
5088
  }
3117
5089
  const cookies = this.cookieHeader();
3118
5090
  if (cookies !== "") headers.set("Cookie", cookies);
3119
- return new Request(url, { method: verb, headers, body });
5091
+ return new Request(url2, { method: verb, headers, body });
3120
5092
  }
3121
5093
  baseUrl() {
3122
5094
  const configured = this.app.bound(ConfigRepository) ? this.app.make(ConfigRepository).get("app.url") : void 0;
@@ -3131,8 +5103,8 @@ var HttpClient = class {
3131
5103
  return cookieHeader(values);
3132
5104
  }
3133
5105
  /** Like a browser: cookies the app sets are sent back on later requests in the same test; deleted ones are dropped. */
3134
- rememberCookies(response) {
3135
- for (const cookie of responseCookies(response)) {
5106
+ rememberCookies(response2) {
5107
+ for (const cookie of responseCookies(response2)) {
3136
5108
  if (isExpired(cookie)) this.cookies.delete(cookie.name);
3137
5109
  else this.cookies.set(cookie.name, { value: cookie.value, encrypt: false });
3138
5110
  }
@@ -3141,9 +5113,9 @@ var HttpClient = class {
3141
5113
  function isRedirect(status) {
3142
5114
  return status >= 300 && status < 400 && status !== 304;
3143
5115
  }
3144
- function setClientAddress(request, address) {
5116
+ function setClientAddress2(request2, address) {
3145
5117
  const g = globalThis;
3146
- (g[CLIENT_ADDRESS] ??= /* @__PURE__ */ new WeakMap()).set(request, address);
5118
+ (g[CLIENT_ADDRESS] ??= /* @__PURE__ */ new WeakMap()).set(request2, address);
3147
5119
  }
3148
5120
  function flatten(data, prefix = "") {
3149
5121
  const out = [];
@@ -3239,11 +5211,43 @@ var InteractsWithApplication = class {
3239
5211
  if (global === app) Container.setInstance(void 0);
3240
5212
  }
3241
5213
  }
5214
+ /**
5215
+ * Laravel's withoutVite(): in this test's requests vite([...]) and viteReactRefresh() render nothing, so feature
5216
+ * tests of pages pass before `vite build` has written public/build/manifest.json.
5217
+ */
5218
+ withoutVite() {
5219
+ const app = this.app;
5220
+ app.scoped(Vite, () => new WithoutVite(app));
5221
+ return this;
5222
+ }
3242
5223
  /** Runs in tearDown(), before the application is thrown away (in registration order). */
3243
5224
  beforeApplicationDestroyed(callback) {
3244
5225
  this.destroying.push(callback);
3245
5226
  }
3246
5227
  };
5228
+ var WithoutVite = class extends Vite {
5229
+ tags() {
5230
+ return new ViteHtml("");
5231
+ }
5232
+ reactRefresh() {
5233
+ return void 0;
5234
+ }
5235
+ asset() {
5236
+ return "";
5237
+ }
5238
+ content() {
5239
+ return "";
5240
+ }
5241
+ manifestHash() {
5242
+ return void 0;
5243
+ }
5244
+ preloadedAssets() {
5245
+ return {};
5246
+ }
5247
+ toHtml() {
5248
+ return "";
5249
+ }
5250
+ };
3247
5251
  async function disconnectAll(db) {
3248
5252
  const manager = db;
3249
5253
  if (typeof manager.disconnectAll === "function") await manager.disconnectAll();
@@ -3258,6 +5262,11 @@ var MakesHttpRequests = class extends InteractsWithApplication {
3258
5262
  createHttpClient() {
3259
5263
  return new HttpClient(this.app);
3260
5264
  }
5265
+ /** Live.test() runs in this test's request scope (Live reads the same token: no import between the packages). */
5266
+ async setUp() {
5267
+ await super.setUp();
5268
+ this.app.instance(token("Testing.RequestScope"), (run) => this.http.withinRequest(run));
5269
+ }
3261
5270
  async tearDown() {
3262
5271
  try {
3263
5272
  await super.tearDown();
@@ -3273,8 +5282,8 @@ var MakesHttpRequests = class extends InteractsWithApplication {
3273
5282
  this.http.headers[name] = value;
3274
5283
  return this;
3275
5284
  }
3276
- withToken(token, type = "Bearer") {
3277
- return this.withHeader("Authorization", `${type} ${token}`);
5285
+ withToken(token2, type = "Bearer") {
5286
+ return this.withHeader("Authorization", `${type} ${token2}`);
3278
5287
  }
3279
5288
  withBasicAuth(username, password) {
3280
5289
  return this.withToken(Buffer.from(`${username}:${password}`).toString("base64"), "Basic");
@@ -3305,9 +5314,9 @@ var MakesHttpRequests = class extends InteractsWithApplication {
3305
5314
  return this;
3306
5315
  }
3307
5316
  /** Sets the Referer and the session's previous URL, so redirect()->back() goes there. */
3308
- from(url) {
3309
- this.http.previousUrl = url;
3310
- return this.withHeader("Referer", url);
5317
+ from(url2) {
5318
+ this.http.previousUrl = url2;
5319
+ return this.withHeader("Referer", url2);
3311
5320
  }
3312
5321
  /** Puts data in the session for the following requests (Laravel's withSession()). */
3313
5322
  withSession(data) {
@@ -3457,13 +5466,13 @@ var InteractsWithConsole = class extends InteractsWithDatabase {
3457
5466
  };
3458
5467
  var TestCase = class extends InteractsWithConsole {
3459
5468
  };
3460
- function install(token, fake) {
3461
- facade(token).swap(fake);
5469
+ function install(token2, fake) {
5470
+ facade(token2).swap(fake);
3462
5471
  return fake;
3463
5472
  }
3464
- function bound(token) {
5473
+ function bound(token2) {
3465
5474
  const container = currentContainer();
3466
- return container !== void 0 && container.bound(token) ? container.make(token) : void 0;
5475
+ return container !== void 0 && container.bound(token2) ? container.make(token2) : void 0;
3467
5476
  }
3468
5477
  function nameOf(target2) {
3469
5478
  if (typeof target2 === "string") return target2;
@@ -4159,8 +6168,8 @@ var AnonymousNotifiable = class {
4159
6168
  }
4160
6169
  dispatcher;
4161
6170
  routes = /* @__PURE__ */ new Map();
4162
- route(channel, route) {
4163
- this.routes.set(channel, route);
6171
+ route(channel, route2) {
6172
+ this.routes.set(channel, route2);
4164
6173
  return this;
4165
6174
  }
4166
6175
  routeNotificationFor(channel) {
@@ -4204,8 +6213,8 @@ var NotificationFake = class _NotificationFake {
4204
6213
  async sendNow(notifiables, notification, channels) {
4205
6214
  this.record(notifiables, notification, channels);
4206
6215
  }
4207
- route(channel, route) {
4208
- return new AnonymousNotifiable(this).route(channel, route);
6216
+ route(channel, route2) {
6217
+ return new AnonymousNotifiable(this).route(channel, route2);
4209
6218
  }
4210
6219
  /** The Notifications provider calls this in boot(); a fake has no channels to register. */
4211
6220
  extend(_channel, _factory) {
@@ -4626,6 +6635,70 @@ export {
4626
6635
  viteReactRefresh,
4627
6636
  Application2 as Application,
4628
6637
  ClosureKernel,
6638
+ HttpException,
6639
+ HttpResponseException,
6640
+ HttpServerError,
6641
+ exceedsJsonDepth,
6642
+ requestScope,
6643
+ currentRequest,
6644
+ optionalSession,
6645
+ optionalCookieJar,
6646
+ HttpRedirectResponse,
6647
+ HttpRedirector,
6648
+ setClientAddress,
6649
+ clientAddress,
6650
+ extensionFromMime,
6651
+ FormUploadedFile,
6652
+ parseBracketKey,
6653
+ normalizeLists,
6654
+ fromEntries,
6655
+ sanitize,
6656
+ mergeRecursive,
6657
+ isBlank,
6658
+ transformLeaves,
6659
+ ipMatches,
6660
+ IncomingRequest,
6661
+ request,
6662
+ response,
6663
+ redirect,
6664
+ back,
6665
+ url,
6666
+ route,
6667
+ abort,
6668
+ abortIf,
6669
+ abortUnless,
6670
+ contentDisposition,
6671
+ HttpResponseFactory,
6672
+ ConvertEmptyStringsToNull,
6673
+ TrimStrings,
6674
+ Kernel,
6675
+ MiddlewareConfiguration,
6676
+ applyMiddlewareCallbacks,
6677
+ statusText,
6678
+ escapeHtml,
6679
+ minimalPage,
6680
+ stackFrames,
6681
+ debugPage,
6682
+ statusFor,
6683
+ summarizeValidation,
6684
+ ErrorHandler,
6685
+ ExceptionsConfiguration,
6686
+ applyExceptionCallbacks,
6687
+ servePublicFile,
6688
+ bunRuntime,
6689
+ serveBun,
6690
+ toWebRequest,
6691
+ writeResponse,
6692
+ serveNode,
6693
+ detectRuntime,
6694
+ serve,
6695
+ routeListRows,
6696
+ filterRouteList,
6697
+ formatRouteList,
6698
+ API_ROUTES_STUB,
6699
+ addApiRouting,
6700
+ registerHttpCommands,
6701
+ HttpServiceProvider,
4629
6702
  On,
4630
6703
  Locked,
4631
6704
  Computed,
@@ -4638,10 +6711,13 @@ export {
4638
6711
  PropertyNotUpdatableError,
4639
6712
  LiveComponentNotFoundError,
4640
6713
  InvalidLiveRequestError,
6714
+ TestRequestScope,
4641
6715
  Testable,
4642
6716
  live,
4643
6717
  Live,
4644
6718
  componentName,
6719
+ PERSISTENT_MIDDLEWARE,
6720
+ persistentMiddleware,
4645
6721
  LiveComponentManager,
4646
6722
  UPDATE_URI,
4647
6723
  LiveServiceProvider,
@@ -4690,4 +6766,4 @@ export {
4690
6766
  StorageFake,
4691
6767
  FakeFile
4692
6768
  };
4693
- //# sourceMappingURL=chunk-I2OMKIQZ.js.map
6769
+ //# sourceMappingURL=chunk-SBZQ6L3Z.js.map