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

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.
@@ -8,8 +8,9 @@ import {
8
8
  Str,
9
9
  currentContainer,
10
10
  facade,
11
+ fileType_exports,
11
12
  inject
12
- } from "./chunk-HU43NADT.js";
13
+ } from "./chunk-PM5YTG57.js";
13
14
  import {
14
15
  Application,
15
16
  AuthFactory,
@@ -17,6 +18,7 @@ import {
17
18
  ClassRegistry,
18
19
  ConfigRepository,
19
20
  ConsoleKernel,
21
+ CookieJar,
20
22
  CurrentRoute,
21
23
  DatabaseManager,
22
24
  Encrypter,
@@ -27,18 +29,23 @@ import {
27
29
  HttpKernel,
28
30
  HttpRequest,
29
31
  LiveManager,
32
+ LogManager,
30
33
  MailManager,
31
34
  MiddlewareCallbacks,
32
35
  NotificationDispatcher,
36
+ PaginationState,
37
+ PipelineFactory,
33
38
  QueueManager,
34
39
  Redirector,
40
+ ResponseFactory,
35
41
  Router,
36
42
  RoutingPaths,
37
43
  Session,
38
44
  UrlGenerator,
39
45
  ValidationFactory,
40
- ViewFactory
41
- } from "./chunk-S7YC43IH.js";
46
+ ViewFactory,
47
+ token
48
+ } from "./chunk-BSQKOLSK.js";
42
49
 
43
50
  // ../Config/dist/index.js
44
51
  import { existsSync, readFileSync } from "fs";
@@ -88,12 +95,20 @@ function env(key, defaultValue) {
88
95
  const quoted = /^(["'])(.*)\1$/s.exec(raw);
89
96
  return quoted ? quoted[2] : raw;
90
97
  }
98
+ function singleQuoted(source) {
99
+ const keys = /* @__PURE__ */ new Set();
100
+ for (const match of source.matchAll(/^[ \t]*(?:export[ \t]+)?([\w.-]+)[ \t]*=[ \t]*'/gm)) keys.add(match[1]);
101
+ return keys;
102
+ }
91
103
  function loadEnvironment(basePath) {
92
104
  const appEnv = process.env.APP_ENV;
93
105
  const specific = appEnv ? join(basePath, `.env.${appEnv}`) : void 0;
94
106
  const file = specific && existsSync(specific) ? specific : join(basePath, ".env");
95
107
  if (!existsSync(file)) return void 0;
96
- for (const [key, value] of Object.entries(parse(readFileSync(file)))) {
108
+ const source = readFileSync(file, "utf8");
109
+ const literal = singleQuoted(source);
110
+ for (const [key, raw] of Object.entries(parse(source))) {
111
+ const value = literal.has(key) ? raw : raw.replace(/\$\{([A-Za-z_][A-Za-z0-9_.]*)\}/g, (match, name) => process.env[name] ?? match);
97
112
  if (process.env[key] === void 0) process.env[key] = value;
98
113
  }
99
114
  return file;
@@ -140,10 +155,11 @@ var ApplicationBuilder = class {
140
155
  return this;
141
156
  }
142
157
  withRouting(options) {
143
- const abs = (p) => p === void 0 ? void 0 : isAbsolute(p) ? p : join4(this.app.basePath(), p);
158
+ const abs = (p) => isAbsolute(p) ? p : join4(this.app.basePath(), p);
159
+ const each = (p) => Array.isArray(p) ? p.map(abs) : abs(p);
144
160
  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);
161
+ if (options.web !== void 0) paths.web = each(options.web);
162
+ if (options.api !== void 0) paths.api = each(options.api);
147
163
  if (options.console !== void 0) paths.console = abs(options.console);
148
164
  if (options.health !== void 0) paths.health = options.health;
149
165
  this.routing = paths;
@@ -323,9 +339,9 @@ var Vite = class {
323
339
  const stylesheets = uniqueTags.filter((tag) => tag.startsWith("<link"));
324
340
  const scripts = uniqueTags.filter((tag) => !tag.startsWith("<link"));
325
341
  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));
342
+ const uniquePreloads = preloads.filter(([, url2]) => seen.has(url2) ? false : (seen.add(url2), true));
343
+ const sorted = [...uniquePreloads.filter(([, url2]) => CSS_PATH.test(url2)), ...uniquePreloads.filter(([, url2]) => !CSS_PATH.test(url2))];
344
+ const preloadTags = sorted.map(([src, url2, chunk2]) => this.#preloadTag(src, url2, chunk2, manifest));
329
345
  return new ViteHtml(preloadTags.join("") + stylesheets.join("") + scripts.join(""));
330
346
  }
331
347
  /** Laravel's @viteReactRefresh: the React Refresh preamble while the dev server runs; nothing otherwise. */
@@ -410,41 +426,41 @@ var Vite = class {
410
426
  }
411
427
  return imports;
412
428
  }
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) });
429
+ #tagForChunk(src, url2, chunk2, manifest) {
430
+ if (CSS_PATH.test(url2)) {
431
+ const attributes2 = this.#parseAttributes({ rel: "stylesheet", href: url2, nonce: this.#nonce ?? false, ...this.#styleAttributes(src, url2, chunk2, manifest) });
416
432
  return `<link ${attributes2.join(" ")} />`;
417
433
  }
418
- const attributes = this.#parseAttributes({ type: "module", src: url, nonce: this.#nonce ?? false, ...this.#scriptAttributes(src, url, chunk2, manifest) });
434
+ const attributes = this.#parseAttributes({ type: "module", src: url2, nonce: this.#nonce ?? false, ...this.#scriptAttributes(src, url2, chunk2, manifest) });
419
435
  return `<script ${attributes.join(" ")}></script>`;
420
436
  }
421
- #preloadTag(src, url, chunk2, manifest) {
422
- const css = CSS_PATH.test(url);
437
+ #preloadTag(src, url2, chunk2, manifest) {
438
+ const css = CSS_PATH.test(url2);
423
439
  let attributes = {
424
440
  rel: css ? "preload" : "modulepreload",
425
441
  as: css ? "style" : "script",
426
- href: url,
442
+ href: url2,
427
443
  nonce: this.#nonce ?? false,
428
- crossorigin: (css ? this.#styleAttributes(src, url, chunk2, manifest) : this.#scriptAttributes(src, url, chunk2, manifest)).crossorigin ?? false
444
+ crossorigin: (css ? this.#styleAttributes(src, url2, chunk2, manifest) : this.#scriptAttributes(src, url2, chunk2, manifest)).crossorigin ?? false
429
445
  };
430
446
  if (this.#integrityKey !== false) attributes.integrity = chunk2?.[this.#integrityKey] ?? false;
431
447
  for (const resolver of this.#preloadResolvers) {
432
- const resolved = resolver(src, url, chunk2, manifest);
448
+ const resolved = resolver(src, url2, chunk2, manifest);
433
449
  if (resolved === false) return "";
434
450
  attributes = { ...attributes, ...resolved };
435
451
  }
436
452
  const { href: _href, ...rest } = attributes;
437
- this.#preloaded[url] = this.#parseAttributes(rest);
453
+ this.#preloaded[url2] = this.#parseAttributes(rest);
438
454
  return `<link ${this.#parseAttributes(attributes).join(" ")} />`;
439
455
  }
440
- #scriptAttributes(src, url, chunk2, manifest) {
456
+ #scriptAttributes(src, url2, chunk2, manifest) {
441
457
  let attributes = this.#integrityKey !== false ? { integrity: chunk2?.[this.#integrityKey] ?? false } : {};
442
- for (const resolver of this.#scriptResolvers) attributes = { ...attributes, ...resolver(src, url, chunk2, manifest) };
458
+ for (const resolver of this.#scriptResolvers) attributes = { ...attributes, ...resolver(src, url2, chunk2, manifest) };
443
459
  return attributes;
444
460
  }
445
- #styleAttributes(src, url, chunk2, manifest) {
461
+ #styleAttributes(src, url2, chunk2, manifest) {
446
462
  let attributes = this.#integrityKey !== false ? { integrity: chunk2?.[this.#integrityKey] ?? false } : {};
447
- for (const resolver of this.#styleResolvers) attributes = { ...attributes, ...resolver(src, url, chunk2, manifest) };
463
+ for (const resolver of this.#styleResolvers) attributes = { ...attributes, ...resolver(src, url2, chunk2, manifest) };
448
464
  return attributes;
449
465
  }
450
466
  /** false/null/undefined are dropped, true is a bare attribute, anything else name="value". */
@@ -476,7 +492,7 @@ var Application2 = class _Application extends Container {
476
492
  this.scoped(Vite, () => new Vite(this));
477
493
  }
478
494
  base;
479
- version = "0.1.0-alpha.3";
495
+ version = "0.1.0-alpha.5";
480
496
  providers = [];
481
497
  bootingCallbacks = [];
482
498
  bootedCallbacks = [];
@@ -566,59 +582,1934 @@ var Application2 = class _Application extends Container {
566
582
  terminating(callback) {
567
583
  this.terminatingCallbacks.push(callback);
568
584
  }
569
- async handle(request) {
585
+ async handle(request2) {
570
586
  await this.boot();
571
587
  const scope = this.createScope();
572
- return scope.run(() => scope.make(HttpKernel).handle(request));
588
+ return scope.run(() => scope.make(HttpKernel).handle(request2));
573
589
  }
574
590
  async terminate() {
575
591
  for (const callback of this.terminatingCallbacks) await callback();
576
592
  }
577
- async runBoot() {
578
- for (const callback of this.bootingCallbacks) await callback(this);
579
- for (let i = 0; i < this.providers.length; i++) await this.bootProvider(this.providers[i]);
580
- this.hasBooted = true;
581
- for (const callback of this.bootedCallbacks) await callback(this);
593
+ async runBoot() {
594
+ for (const callback of this.bootingCallbacks) await callback(this);
595
+ for (let i = 0; i < this.providers.length; i++) await this.bootProvider(this.providers[i]);
596
+ this.hasBooted = true;
597
+ for (const callback of this.bootedCallbacks) await callback(this);
598
+ }
599
+ async bootProvider(provider) {
600
+ try {
601
+ await provider.boot?.();
602
+ } catch (error) {
603
+ throw new ProviderBootError(provider.constructor.name, error);
604
+ }
605
+ }
606
+ };
607
+ var ClosureKernel = class {
608
+ routes = /* @__PURE__ */ new Map();
609
+ on(method, path, handler) {
610
+ this.routes.set(`${method.toUpperCase()} ${path}`, handler);
611
+ return this;
612
+ }
613
+ async handle(request2) {
614
+ const handler = this.routes.get(`${request2.method} ${new URL(request2.url).pathname}`);
615
+ if (!handler) return new Response("Not Found", { status: 404, headers: { "content-type": "text/plain; charset=utf-8" } });
616
+ return toResponse(await handler(request2));
617
+ }
618
+ };
619
+ function toResponse(output) {
620
+ if (output instanceof Response) return output;
621
+ if (output === void 0 || output === null) return new Response(null, { status: 204 });
622
+ if (typeof output === "string") return new Response(output, { headers: { "content-type": "text/html; charset=utf-8" } });
623
+ return Response.json(output);
624
+ }
625
+
626
+ // ../Http/dist/index.js
627
+ import { createReadStream } from "fs";
628
+ import { stat } from "fs/promises";
629
+ import { basename } from "path";
630
+ import { Readable } from "stream";
631
+ import { readFile } from "fs/promises";
632
+ import { fileURLToPath } from "url";
633
+ import { createReadStream as createReadStream2 } from "fs";
634
+ import { stat as stat2 } from "fs/promises";
635
+ import { Readable as Readable2 } from "stream";
636
+ import { relative, resolve, sep } from "path";
637
+ import { createServer } from "http";
638
+ import { Readable as Readable3 } from "stream";
639
+ import { pipeline } from "stream/promises";
640
+ import { existsSync as existsSync4 } from "fs";
641
+ import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
642
+ import { dirname } from "path";
643
+ var HttpException = class extends Error {
644
+ constructor(status, message = "", headers = {}) {
645
+ super(message);
646
+ this.status = status;
647
+ this.headers = headers;
648
+ }
649
+ status;
650
+ headers;
651
+ kind = "Http";
652
+ name = "HttpException";
653
+ };
654
+ var HttpResponseException = class extends Error {
655
+ constructor(response2) {
656
+ super(`HTTP response exception (${response2.status}).`);
657
+ this.response = response2;
658
+ this.status = response2.status;
659
+ }
660
+ response;
661
+ kind = "Http";
662
+ name = "HttpResponseException";
663
+ status;
664
+ };
665
+ var HttpServerError = class extends Error {
666
+ kind = "Server";
667
+ status = 500;
668
+ name = "HttpServerError";
669
+ };
670
+ function exceedsJsonDepth(text, maxDepth) {
671
+ let depth = 0;
672
+ let inString = false;
673
+ for (let i = 0; i < text.length; i++) {
674
+ const code = text.charCodeAt(i);
675
+ if (inString) {
676
+ if (code === 92) i++;
677
+ else if (code === 34) inString = false;
678
+ } else if (code === 34) inString = true;
679
+ else if (code === 123 || code === 91) {
680
+ if (++depth > maxDepth) return true;
681
+ } else if (code === 125 || code === 93) depth--;
682
+ }
683
+ return false;
684
+ }
685
+ async function readBody(raw, limit) {
686
+ if (raw.body === null) return new Uint8Array(0);
687
+ const declared = Number(raw.headers.get("content-length") ?? NaN);
688
+ if (limit !== null && Number.isFinite(declared) && declared > limit) {
689
+ await raw.body.cancel();
690
+ throw new HttpException(413, "Content Too Large");
691
+ }
692
+ const reader = raw.body.getReader();
693
+ const chunks = [];
694
+ let size = 0;
695
+ for (; ; ) {
696
+ const { done, value } = await reader.read();
697
+ if (done) break;
698
+ size += value.byteLength;
699
+ if (limit !== null && size > limit) {
700
+ await reader.cancel();
701
+ throw new HttpException(413, "Content Too Large");
702
+ }
703
+ chunks.push(value);
704
+ }
705
+ const out = new Uint8Array(size);
706
+ let offset = 0;
707
+ for (const chunk2 of chunks) {
708
+ out.set(chunk2, offset);
709
+ offset += chunk2.byteLength;
710
+ }
711
+ return out;
712
+ }
713
+ function requestScope() {
714
+ const container = currentContainer();
715
+ return container !== void 0 && container.bound(HttpRequest) ? container : void 0;
716
+ }
717
+ function currentRequest() {
718
+ return requestScope()?.make(HttpRequest);
719
+ }
720
+ function optionalSession() {
721
+ const scope = requestScope();
722
+ return scope?.bound(Session) ? scope.make(Session) : void 0;
723
+ }
724
+ function optionalCookieJar() {
725
+ const scope = requestScope();
726
+ return scope?.bound(CookieJar) ? scope.make(CookieJar) : void 0;
727
+ }
728
+ function serializeCookie(cookie) {
729
+ const parts = [`${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value)}`, `Path=${cookie.path}`];
730
+ if (cookie.domain !== void 0) parts.push(`Domain=${cookie.domain}`);
731
+ if (cookie.secure) parts.push("Secure");
732
+ if (cookie.httpOnly) parts.push("HttpOnly");
733
+ parts.push(`SameSite=${cookie.sameSite.charAt(0).toUpperCase()}${cookie.sameSite.slice(1)}`);
734
+ if (cookie.minutes < 0) parts.push("Max-Age=0", "Expires=Thu, 01 Jan 1970 00:00:00 GMT");
735
+ else if (cookie.minutes > 0) parts.push(`Max-Age=${Math.round(cookie.minutes * 60)}`);
736
+ return parts.join("; ");
737
+ }
738
+ var HttpRedirectResponse = class {
739
+ constructor(targetUrl, status = 302, headers) {
740
+ this.targetUrl = targetUrl;
741
+ this.status = status;
742
+ this.#headers = new Headers(headers);
743
+ }
744
+ targetUrl;
745
+ status;
746
+ #headers;
747
+ with(key, value) {
748
+ const session = optionalSession();
749
+ if (session === void 0) return this;
750
+ if (typeof key === "string") session.flash(key, value);
751
+ else for (const [name, item] of Object.entries(key)) session.flash(name, item);
752
+ return this;
753
+ }
754
+ /** Flashes `errors` as { [bag]: { field: string[] } }, keeping the other bags already flashed. */
755
+ withErrors(errors, bag = "default") {
756
+ const session = optionalSession();
757
+ if (session === void 0) return this;
758
+ const source = typeof errors.toObject === "function" ? errors.toObject() : errors;
759
+ const normalized = Object.fromEntries(
760
+ Object.entries(source).map(([field, messages]) => [field, Array.isArray(messages) ? [...messages] : [messages]])
761
+ );
762
+ const existing = session.get("errors", {});
763
+ session.flash("errors", { ...existing, [bag]: normalized });
764
+ return this;
765
+ }
766
+ /** Flashes `input`, or the current request's input (files are never part of input()). */
767
+ withInput(input) {
768
+ const session = optionalSession();
769
+ if (session === void 0) return this;
770
+ session.flashInput(input ?? currentRequest()?.input() ?? {});
771
+ return this;
772
+ }
773
+ /**
774
+ * Always a Set-Cookie header on this response (Laravel's RedirectResponse::withCookie). A bound CookieJar only
775
+ * supplies the defaults (path, domain, secure, same-site); queueing would lose the cookie outside the web group.
776
+ */
777
+ withCookie(name, value, minutes = 0) {
778
+ const jar = optionalCookieJar();
779
+ const cookie = jar !== void 0 ? jar.make(name, value, minutes) : { name, value, minutes, path: "/", secure: false, httpOnly: true, sameSite: "lax" };
780
+ this.#headers.append("Set-Cookie", serializeCookie(cookie));
781
+ return this;
782
+ }
783
+ header(name, value) {
784
+ this.#headers.set(name, value);
785
+ return this;
786
+ }
787
+ toResponse() {
788
+ const headers = new Headers(this.#headers);
789
+ headers.set("Location", this.targetUrl);
790
+ return new Response(null, { status: this.status, headers });
791
+ }
792
+ };
793
+ function pathOf(url2) {
794
+ let parsed;
795
+ try {
796
+ parsed = new URL(url2, "http://localhost");
797
+ } catch {
798
+ return "/";
799
+ }
800
+ return `${parsed.pathname.replace(/^\/+/, "/")}${parsed.search}${parsed.hash}`;
801
+ }
802
+ var HttpRedirector = class {
803
+ to(path, status = 302, headers) {
804
+ return new HttpRedirectResponse(inject(UrlGenerator).to(path), status, headers);
805
+ }
806
+ route(name, parameters, status = 302) {
807
+ return new HttpRedirectResponse(inject(UrlGenerator).route(name, parameters), status);
808
+ }
809
+ back(fallback, status = 302) {
810
+ return new HttpRedirectResponse(inject(UrlGenerator).previous(fallback), status);
811
+ }
812
+ /** Pulls url.intended from the session (put there by guest()), else goes to `fallback`. */
813
+ intended(fallback = "/", status = 302) {
814
+ const path = optionalSession()?.pull("url.intended") ?? fallback;
815
+ return this.to(path, status);
816
+ }
817
+ away(url2, status = 302) {
818
+ return new HttpRedirectResponse(url2, status);
819
+ }
820
+ /**
821
+ * Remembers where a guest was going (a GET page, else the previous URL), then redirects to `path`. Only the path
822
+ * is stored, so intended() goes back on whichever host serves the app, and never to another site's Referer.
823
+ */
824
+ guest(path, status = 302) {
825
+ const session = optionalSession();
826
+ const request2 = currentRequest();
827
+ if (session !== void 0) {
828
+ const intended = request2 !== void 0 && request2.method() === "GET" && !request2.expectsJson() ? request2.fullUrl() : inject(UrlGenerator).previous();
829
+ session.put("url.intended", pathOf(intended));
830
+ }
831
+ return this.to(path, status);
832
+ }
833
+ setIntendedUrl(url2) {
834
+ optionalSession()?.put("url.intended", url2);
835
+ }
836
+ refresh(status = 302) {
837
+ return this.to(currentRequest()?.fullUrl() ?? inject(UrlGenerator).current(), status);
838
+ }
839
+ };
840
+ var KEY = /* @__PURE__ */ Symbol.for("oriel:http.client-address");
841
+ var store = globalThis[KEY] ??= /* @__PURE__ */ new WeakMap();
842
+ function setClientAddress(request2, address) {
843
+ if (address !== void 0 && address !== "") store.set(request2, address);
844
+ }
845
+ function clientAddress(request2) {
846
+ return store.get(request2);
847
+ }
848
+ var TYPES = {
849
+ avif: "image/avif",
850
+ css: "text/css",
851
+ csv: "text/csv",
852
+ doc: "application/msword",
853
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
854
+ gif: "image/gif",
855
+ gz: "application/gzip",
856
+ html: "text/html",
857
+ ico: "image/vnd.microsoft.icon",
858
+ jpg: "image/jpeg",
859
+ js: "text/javascript",
860
+ map: "application/json",
861
+ json: "application/json",
862
+ md: "text/markdown",
863
+ mp3: "audio/mpeg",
864
+ mp4: "video/mp4",
865
+ otf: "font/otf",
866
+ pdf: "application/pdf",
867
+ png: "image/png",
868
+ svg: "image/svg+xml",
869
+ ttf: "font/ttf",
870
+ txt: "text/plain",
871
+ wasm: "application/wasm",
872
+ webm: "video/webm",
873
+ webp: "image/webp",
874
+ woff: "font/woff",
875
+ woff2: "font/woff2",
876
+ xls: "application/vnd.ms-excel",
877
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
878
+ xml: "application/xml",
879
+ zip: "application/zip"
880
+ };
881
+ var ALIASES = { jpeg: "jpg", htm: "html", mjs: "js", text: "txt" };
882
+ var EXTENSIONS = Object.fromEntries(Object.entries(TYPES).map(([extension, type]) => [type, extension]));
883
+ function mimeFromExtension(pathOrExtension) {
884
+ const extension = pathOrExtension.slice(pathOrExtension.lastIndexOf(".") + 1).toLowerCase();
885
+ return TYPES[ALIASES[extension] ?? extension] ?? "application/octet-stream";
886
+ }
887
+ function extensionFromMime(type) {
888
+ return EXTENSIONS[type.split(";")[0].trim().toLowerCase()];
889
+ }
890
+ var NEVER_CLAIMED = /* @__PURE__ */ new Set([
891
+ ...fileType_exports.BINARY_AND_MARKUP_TYPES.flatMap(({ extensions }) => extensions),
892
+ "jpe",
893
+ "svgz",
894
+ "xhtml",
895
+ "xht",
896
+ "shtml",
897
+ "xsl",
898
+ "xslt",
899
+ "mht",
900
+ "mhtml",
901
+ "js",
902
+ "mjs",
903
+ "rss",
904
+ "atom",
905
+ "rdf",
906
+ "mathml",
907
+ "mml",
908
+ "xul"
909
+ ]);
910
+ var PLAIN_EXTENSION = /^[a-z0-9]+$/;
911
+ var FormUploadedFile = class _FormUploadedFile {
912
+ constructor(file, detected) {
913
+ this.file = file;
914
+ this.detected = detected;
915
+ this.clientName = file.name ?? "";
916
+ this.mimeType = file.type === "" ? "application/octet-stream" : file.type;
917
+ this.size = file.size;
918
+ }
919
+ file;
920
+ detected;
921
+ clientName;
922
+ mimeType;
923
+ size;
924
+ /**
925
+ * Reads the leading bytes first, so extension(), hashName() and store() name the file by its content. The request
926
+ * builds its uploads this way.
927
+ */
928
+ static async fromFile(file) {
929
+ return new _FormUploadedFile(file, fileType_exports.detectFileType(new Uint8Array(await file.slice(0, fileType_exports.SNIFF_BYTES).arrayBuffer())));
930
+ }
931
+ /**
932
+ * Laravel's extension(): the type the bytes show (guessExtension()), when they were read and match a known format.
933
+ * Text and bytes no format matches keep the client's claim (its MIME type, else its file name) when it is a plain
934
+ * extension, unless it names a format the bytes would have shown or one a browser renders or runs: otherwise 'txt'
935
+ * for text, '' for the rest. Unread (built directly), the client's claim when it is a plain extension, else ''.
936
+ */
937
+ extension() {
938
+ const claimed = extensionFromMime(this.mimeType) ?? this.clientExtension();
939
+ const detected = this.detected;
940
+ if (detected === void 0) return PLAIN_EXTENSION.test(claimed) ? claimed : "";
941
+ if (detected !== fileType_exports.TEXT && detected.extensions.length > 0) return detected.extensions[0];
942
+ if (PLAIN_EXTENSION.test(claimed) && !NEVER_CLAIMED.has(claimed)) return claimed;
943
+ return detected === fileType_exports.TEXT ? "txt" : "";
944
+ }
945
+ clientExtension() {
946
+ const dot = this.clientName.lastIndexOf(".");
947
+ return dot === -1 ? "" : this.clientName.slice(dot + 1).toLowerCase();
948
+ }
949
+ /** A random 40-character name with the guessed extension. */
950
+ hashName() {
951
+ const extension = this.extension();
952
+ return extension === "" ? Str.random(40) : `${Str.random(40)}.${extension}`;
953
+ }
954
+ arrayBuffer() {
955
+ return this.file.arrayBuffer();
956
+ }
957
+ text() {
958
+ return this.file.text();
959
+ }
960
+ stream() {
961
+ return this.file.stream();
962
+ }
963
+ store(directory, disk) {
964
+ return inject(FilesystemManager).disk(disk).putFile(directory, this);
965
+ }
966
+ storeAs(directory, name, disk) {
967
+ return inject(FilesystemManager).disk(disk).putFileAs(directory, this, name);
968
+ }
969
+ };
970
+ var FORBIDDEN = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
971
+ var INTEGER_KEY = /^(?:0|[1-9]\d*)$/;
972
+ var isPlainObject = (value) => {
973
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
974
+ const prototype = Object.getPrototypeOf(value);
975
+ return prototype === Object.prototype || prototype === null;
976
+ };
977
+ function parseBracketKey(name) {
978
+ const open = name.indexOf("[");
979
+ if (open <= 0 || !name.endsWith("]")) return [name];
980
+ const segments = [name.slice(0, open)];
981
+ const rest = name.slice(open);
982
+ const matches2 = rest.match(/\[[^[\]]*\]/g);
983
+ if (matches2 === null || matches2.join("") !== rest) return [name];
984
+ for (const match of matches2) segments.push(match.slice(1, -1));
985
+ return segments;
986
+ }
987
+ function normalizeLists(value) {
988
+ if (Array.isArray(value)) return value.map(normalizeLists);
989
+ if (!isPlainObject(value)) return value;
990
+ const keys = Object.keys(value);
991
+ const out = {};
992
+ for (const key of keys) out[key] = normalizeLists(value[key]);
993
+ if (keys.length > 0 && keys.every((key, index) => key === String(index))) return keys.map((key) => out[key]);
994
+ return out;
995
+ }
996
+ function fromEntries(entries) {
997
+ const root = {};
998
+ const nextIndex = /* @__PURE__ */ new WeakMap();
999
+ for (const [name, value] of entries) {
1000
+ const segments = parseBracketKey(name);
1001
+ if (segments.some((segment) => FORBIDDEN.has(segment))) continue;
1002
+ let node = root;
1003
+ for (let i = 0; i < segments.length; i++) {
1004
+ let segment = segments[i];
1005
+ const next = nextIndex.get(node) ?? 0;
1006
+ if (segment === "") {
1007
+ segment = String(next);
1008
+ nextIndex.set(node, next + 1);
1009
+ } else if (INTEGER_KEY.test(segment) && Number(segment) >= next) {
1010
+ nextIndex.set(node, Number(segment) + 1);
1011
+ }
1012
+ if (i === segments.length - 1) {
1013
+ node[segment] = value;
1014
+ break;
1015
+ }
1016
+ if (!isPlainObject(node[segment])) node[segment] = {};
1017
+ node = node[segment];
1018
+ }
1019
+ }
1020
+ return normalizeLists(root);
1021
+ }
1022
+ function sanitize(value) {
1023
+ if (Array.isArray(value)) return value.map(sanitize);
1024
+ if (!isPlainObject(value)) return value;
1025
+ const out = {};
1026
+ for (const [key, item] of Object.entries(value)) if (!FORBIDDEN.has(key)) out[key] = sanitize(item);
1027
+ return out;
1028
+ }
1029
+ function mergeRecursive(base, override) {
1030
+ const out = { ...base };
1031
+ for (const [key, value] of Object.entries(override)) {
1032
+ if (FORBIDDEN.has(key)) continue;
1033
+ const current = out[key];
1034
+ out[key] = (isPlainObject(current) || Array.isArray(current)) && (isPlainObject(value) || Array.isArray(value)) ? normalizeLists(mergeRecursive({ ...current }, { ...value })) : value;
1035
+ }
1036
+ return out;
1037
+ }
1038
+ function isBlank(value) {
1039
+ if (value === null || value === void 0) return true;
1040
+ if (typeof value === "string") return value.trim() === "";
1041
+ if (Array.isArray(value)) return value.length === 0;
1042
+ if (isPlainObject(value)) return Object.keys(value).length === 0;
1043
+ return false;
1044
+ }
1045
+ function transformLeaves(tree, transform, prefix = "") {
1046
+ const out = {};
1047
+ for (const [key, value] of Object.entries(tree)) out[key] = transformValue(`${prefix}${key}`, value, transform);
1048
+ return out;
1049
+ }
1050
+ function transformValue(path, value, transform) {
1051
+ if (Array.isArray(value)) return value.map((item, index) => transformValue(`${path}.${index}`, item, transform));
1052
+ if (isPlainObject(value)) return transformLeaves(value, transform, `${path}.`);
1053
+ return transform(path, value);
1054
+ }
1055
+ var TRUE_VALUES = /* @__PURE__ */ new Set(["1", "true", "on", "yes"]);
1056
+ var normalizeIp = (ip) => ip.trim().replace(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i, "$1");
1057
+ function ipv4ToNumber(ip) {
1058
+ const parts = ip.split(".");
1059
+ if (parts.length !== 4 || parts.some((part) => !/^\d{1,3}$/.test(part) || Number(part) > 255)) return void 0;
1060
+ return parts.reduce((total, part) => total * 256 + Number(part), 0);
1061
+ }
1062
+ function ipMatches(ip, patterns) {
1063
+ const address = normalizeIp(ip);
1064
+ return patterns.some((pattern) => {
1065
+ if (pattern === "*") return true;
1066
+ if (!pattern.includes("/")) return normalizeIp(pattern) === address;
1067
+ const [range, bits] = pattern.split("/");
1068
+ const base = ipv4ToNumber(range);
1069
+ const value = ipv4ToNumber(address);
1070
+ const size = Number(bits);
1071
+ if (base === void 0 || value === void 0 || !(size >= 0 && size <= 32)) return false;
1072
+ const mask = size === 0 ? 0 : ~0 << 32 - size >>> 0;
1073
+ return (base & mask) >>> 0 === (value & mask) >>> 0;
1074
+ });
1075
+ }
1076
+ var IncomingRequest = class {
1077
+ constructor(raw, options = {}) {
1078
+ this.raw = raw;
1079
+ this.#url = new URL(raw.url);
1080
+ this.#trustedProxies = options.trustedProxies ?? [];
1081
+ this.#maxBodySize = options.maxBodySize === void 0 ? 8 * 1024 * 1024 : options.maxBodySize;
1082
+ this.#maxJsonDepth = options.maxJsonDepth === void 0 ? 512 : options.maxJsonDepth;
1083
+ this.#query = fromEntries(this.#url.searchParams.entries());
1084
+ this.#method = raw.method.toUpperCase();
1085
+ }
1086
+ raw;
1087
+ #url;
1088
+ #trustedProxies;
1089
+ #maxBodySize;
1090
+ #maxJsonDepth;
1091
+ #query;
1092
+ #body = {};
1093
+ #files = {};
1094
+ #content = "";
1095
+ #method;
1096
+ #cookies;
1097
+ #loading;
1098
+ #userResolver;
1099
+ /** Reads and parses the body (JSON, urlencoded or multipart) once, then applies method spoofing. */
1100
+ load() {
1101
+ return this.#loading ??= this.#load();
1102
+ }
1103
+ async #load() {
1104
+ const method = this.raw.method.toUpperCase();
1105
+ if (method !== "GET" && method !== "HEAD" && this.raw.body !== null) {
1106
+ const type = (this.raw.headers.get("content-type") ?? "").toLowerCase();
1107
+ const isJson = type.includes("/json") || type.includes("+json");
1108
+ const isUrlencoded = type.startsWith("application/x-www-form-urlencoded");
1109
+ const isMultipart = type.startsWith("multipart/form-data");
1110
+ if (isJson || isUrlencoded || isMultipart) {
1111
+ const bytes = await readBody(this.raw, this.#maxBodySize);
1112
+ if (isJson && this.#maxJsonDepth !== null && exceedsJsonDepth(new TextDecoder().decode(bytes), this.#maxJsonDepth)) {
1113
+ throw new HttpException(400, "The request body is nested too deeply.");
1114
+ }
1115
+ try {
1116
+ if (isJson) {
1117
+ this.#content = new TextDecoder().decode(bytes);
1118
+ this.#body = this.#parseJson(this.#content);
1119
+ } else if (isUrlencoded) {
1120
+ this.#content = new TextDecoder().decode(bytes);
1121
+ this.#body = fromEntries(new URLSearchParams(this.#content).entries());
1122
+ } else {
1123
+ await this.#parseForm(await new Response(bytes, { headers: { "content-type": this.raw.headers.get("content-type") ?? "" } }).formData());
1124
+ }
1125
+ } catch {
1126
+ }
1127
+ }
1128
+ }
1129
+ if (method === "POST") {
1130
+ const override = this.raw.headers.get("x-http-method-override") ?? this.#string(this.#body._method) ?? this.#string(this.#query._method);
1131
+ const spoofed = override?.toUpperCase();
1132
+ if (spoofed !== void 0 && /^[A-Z]+$/.test(spoofed) && !["GET", "HEAD", "CONNECT", "TRACE"].includes(spoofed)) {
1133
+ this.#method = spoofed;
1134
+ }
1135
+ }
1136
+ return this;
1137
+ }
1138
+ #parseJson(text) {
1139
+ let parsed;
1140
+ try {
1141
+ parsed = JSON.parse(text);
1142
+ } catch {
1143
+ return {};
1144
+ }
1145
+ if (Array.isArray(parsed)) return sanitize({ ...parsed });
1146
+ return isPlainObject(parsed) ? sanitize(parsed) : {};
1147
+ }
1148
+ async #parseForm(form) {
1149
+ const fields = [];
1150
+ const files = [];
1151
+ for (const [name, value] of form.entries()) {
1152
+ if (typeof value === "string") fields.push([name, value]);
1153
+ else if (!((value.name ?? "") === "" && value.size === 0)) files.push([name, await FormUploadedFile.fromFile(value)]);
1154
+ }
1155
+ this.#body = fromEntries(fields);
1156
+ this.#files = fromEntries(files);
1157
+ }
1158
+ #string(value) {
1159
+ return typeof value === "string" && value !== "" ? value : void 0;
1160
+ }
1161
+ method() {
1162
+ return this.#method;
1163
+ }
1164
+ isMethod(method) {
1165
+ return this.#method === method.toUpperCase();
1166
+ }
1167
+ /** The scheme and host, honouring X-Forwarded-Proto/Host from trusted proxies. */
1168
+ root() {
1169
+ const trusted = this.#fromTrustedProxy();
1170
+ const proto = trusted ? this.raw.headers.get("x-forwarded-proto")?.split(",")[0]?.trim() : void 0;
1171
+ const host = trusted ? this.raw.headers.get("x-forwarded-host")?.split(",")[0]?.trim() : void 0;
1172
+ const scheme = proto === "https" || proto === "http" ? proto : this.#url.protocol.slice(0, -1);
1173
+ return `${scheme}://${host !== void 0 && host !== "" ? host : this.#url.host}`;
1174
+ }
1175
+ /** The URL without the query string or a trailing slash. */
1176
+ url() {
1177
+ return `${this.root()}${this.#url.pathname}`.replace(/\/+$/, "");
1178
+ }
1179
+ fullUrl() {
1180
+ return this.#url.search === "" ? this.url() : `${this.url()}${this.#url.search}`;
1181
+ }
1182
+ /** 'posts/1', or '/' for the root. */
1183
+ path() {
1184
+ const trimmed = this.#url.pathname.replace(/^\/+|\/+$/g, "");
1185
+ return trimmed === "" ? "/" : trimmed;
1186
+ }
1187
+ decodedPath() {
1188
+ try {
1189
+ return decodeURIComponent(this.path());
1190
+ } catch {
1191
+ return this.path();
1192
+ }
1193
+ }
1194
+ secure() {
1195
+ return this.root().startsWith("https:");
1196
+ }
1197
+ /** request.is('admin/*'): wildcard match against the decoded path. */
1198
+ is(...patterns) {
1199
+ const path = this.decodedPath();
1200
+ return patterns.some((pattern) => {
1201
+ const trimmed = pattern.replace(/^\/+|\/+$/g, "");
1202
+ return Str.is(trimmed === "" ? "/" : trimmed, path);
1203
+ });
1204
+ }
1205
+ routeIs(...names) {
1206
+ const name = this.#currentRoute()?.route.getName();
1207
+ return name !== void 0 && names.some((pattern) => Str.is(pattern, name));
1208
+ }
1209
+ header(name, defaultValue) {
1210
+ return this.raw.headers.get(name) ?? defaultValue;
1211
+ }
1212
+ bearerToken() {
1213
+ const header = this.header("authorization", "");
1214
+ const position = header.toLowerCase().lastIndexOf("bearer ");
1215
+ if (position === -1) return void 0;
1216
+ const token2 = header.slice(position + 7).split(",")[0].trim();
1217
+ return token2 === "" ? void 0 : token2;
1218
+ }
1219
+ userAgent() {
1220
+ return this.header("user-agent");
1221
+ }
1222
+ /** The socket address, or the right-most untrusted X-Forwarded-For entry when the socket is a trusted proxy. */
1223
+ ip() {
1224
+ const socket = clientAddress(this.raw);
1225
+ if (socket === void 0) return void 0;
1226
+ const address = normalizeIp(socket);
1227
+ if (!this.#fromTrustedProxy()) return address;
1228
+ const chain = (this.raw.headers.get("x-forwarded-for") ?? "").split(",").map((entry) => normalizeIp(entry)).filter((entry) => entry !== "");
1229
+ for (let i = chain.length - 1; i >= 0; i--) if (!ipMatches(chain[i], this.#trustedProxies)) return chain[i];
1230
+ return chain[0] ?? address;
1231
+ }
1232
+ #fromTrustedProxy() {
1233
+ const socket = clientAddress(this.raw);
1234
+ return socket !== void 0 && this.#trustedProxies.length > 0 && ipMatches(socket, this.#trustedProxies);
1235
+ }
1236
+ ajax() {
1237
+ return this.header("x-requested-with") === "XMLHttpRequest";
1238
+ }
1239
+ pjax() {
1240
+ return this.header("x-pjax") === "true";
1241
+ }
1242
+ /** The first Accept type is JSON. */
1243
+ wantsJson() {
1244
+ const first = this.header("accept", "").split(",")[0].trim();
1245
+ return first.includes("/json") || first.includes("+json");
1246
+ }
1247
+ /** Laravel: (ajax and not pjax and accepts anything) or wantsJson. */
1248
+ expectsJson() {
1249
+ const first = this.header("accept", "").split(",")[0].split(";")[0].trim();
1250
+ const acceptsAny = first === "" || first === "*/*" || first === "*";
1251
+ return this.ajax() && !this.pjax() && acceptsAny || this.wantsJson();
1252
+ }
1253
+ /** The raw body text for JSON and urlencoded requests ('' otherwise; the raw stream stays unread for other types). */
1254
+ content() {
1255
+ return this.#content;
1256
+ }
1257
+ query(key, defaultValue) {
1258
+ return Arr.get(this.#query, key, defaultValue);
1259
+ }
1260
+ /** Body wins over the query string (PHP's $body + $query). Files are not input. */
1261
+ input(key, defaultValue) {
1262
+ return Arr.get({ ...this.#query, ...this.#body }, key, defaultValue);
1263
+ }
1264
+ /** Input plus files (array_replace_recursive). */
1265
+ all() {
1266
+ return mergeRecursive({ ...this.#query, ...this.#body }, this.#files);
1267
+ }
1268
+ only(...keys) {
1269
+ const all = this.all();
1270
+ const out = {};
1271
+ for (const key of keys.flat()) if (Arr.has(all, key)) Arr.set(out, key, Arr.get(all, key));
1272
+ return out;
1273
+ }
1274
+ except(...keys) {
1275
+ const all = structuredCloneInput(this.all());
1276
+ for (const key of keys.flat()) Arr.forget(all, key);
1277
+ return all;
1278
+ }
1279
+ has(...keys) {
1280
+ const all = this.all();
1281
+ return keys.flat().every((key) => Arr.has(all, key));
1282
+ }
1283
+ hasAny(...keys) {
1284
+ const all = this.all();
1285
+ return keys.flat().some((key) => Arr.has(all, key));
1286
+ }
1287
+ missing(...keys) {
1288
+ return !this.has(...keys);
1289
+ }
1290
+ filled(key) {
1291
+ return !isBlank(Arr.get(this.all(), key));
1292
+ }
1293
+ string(key, defaultValue = "") {
1294
+ const value = this.input(key);
1295
+ return value === void 0 || value === null ? defaultValue : String(value);
1296
+ }
1297
+ /** PHP's FILTER_VALIDATE_BOOLEAN: '1', 'true', 'on', 'yes' (any case) and true are true. */
1298
+ boolean(key, defaultValue = false) {
1299
+ const value = this.input(key);
1300
+ if (value === void 0) return defaultValue;
1301
+ if (typeof value === "boolean") return value;
1302
+ if (typeof value === "number") return value === 1;
1303
+ return typeof value === "string" && TRUE_VALUES.has(value.trim().toLowerCase());
1304
+ }
1305
+ /** PHP's intval: leading digits, else 0. */
1306
+ integer(key, defaultValue = 0) {
1307
+ const value = this.input(key);
1308
+ if (value === void 0) return defaultValue;
1309
+ if (typeof value === "number") return Number.isFinite(value) ? Math.trunc(value) : 0;
1310
+ if (typeof value === "boolean") return value ? 1 : 0;
1311
+ const parsed = Number.parseInt(String(value).trim(), 10);
1312
+ return Number.isNaN(parsed) ? 0 : parsed;
1313
+ }
1314
+ /** Adds or replaces top-level body values. */
1315
+ merge(values) {
1316
+ this.#body = { ...this.#body, ...sanitize(values) };
1317
+ return this;
1318
+ }
1319
+ /** Rewrites every scalar in the query and the body (TrimStrings and ConvertEmptyStringsToNull use this). */
1320
+ transformInput(transform) {
1321
+ this.#query = transformLeaves(this.#query, transform);
1322
+ this.#body = transformLeaves(this.#body, transform);
1323
+ return this;
1324
+ }
1325
+ file(key) {
1326
+ const value = Arr.get(this.#files, key);
1327
+ return value instanceof FormUploadedFile ? value : void 0;
1328
+ }
1329
+ hasFile(key) {
1330
+ const value = Arr.get(this.#files, key);
1331
+ return value instanceof FormUploadedFile || Array.isArray(value) && value.some((item) => item instanceof FormUploadedFile);
1332
+ }
1333
+ allFiles() {
1334
+ return { ...this.#files };
1335
+ }
1336
+ cookie(name) {
1337
+ return this.cookies()[name];
1338
+ }
1339
+ cookies() {
1340
+ if (this.#cookies === void 0) {
1341
+ const cookies = {};
1342
+ for (const part of (this.raw.headers.get("cookie") ?? "").split(";")) {
1343
+ const index = part.indexOf("=");
1344
+ if (index === -1) continue;
1345
+ const name = part.slice(0, index).trim();
1346
+ if (name === "" || Object.hasOwn(cookies, name)) continue;
1347
+ let value = part.slice(index + 1).trim();
1348
+ if (value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
1349
+ try {
1350
+ cookies[name] = decodeURIComponent(value);
1351
+ } catch {
1352
+ cookies[name] = value;
1353
+ }
1354
+ }
1355
+ this.#cookies = cookies;
1356
+ }
1357
+ return { ...this.#cookies };
1358
+ }
1359
+ /** Replaces the parsed cookies (EncryptCookies calls this after decrypting). */
1360
+ setCookies(cookies) {
1361
+ this.#cookies = { ...cookies };
1362
+ }
1363
+ /** One route parameter (after model binding), or all of them. */
1364
+ route(key) {
1365
+ const parameters = this.#currentRoute()?.parameters ?? {};
1366
+ return key === void 0 ? { ...parameters } : parameters[key];
1367
+ }
1368
+ session() {
1369
+ return inject(Session);
1370
+ }
1371
+ user(guard) {
1372
+ return this.#userResolver?.(guard);
1373
+ }
1374
+ setUserResolver(resolver) {
1375
+ this.#userResolver = resolver;
1376
+ }
1377
+ /** Validates all() through the ValidationFactory; throws its Validation error on failure. */
1378
+ validate(rules, messages, attributes) {
1379
+ return inject(ValidationFactory).make(this.all(), rules, messages, attributes).validated();
1380
+ }
1381
+ #currentRoute() {
1382
+ const container = currentContainer();
1383
+ return container?.bound(CurrentRoute) ? container.make(CurrentRoute) : void 0;
1384
+ }
1385
+ };
1386
+ function structuredCloneInput(tree) {
1387
+ const out = {};
1388
+ for (const [key, value] of Object.entries(tree)) {
1389
+ out[key] = isPlainObject(value) ? structuredCloneInput(value) : Array.isArray(value) ? [...value] : value;
1390
+ }
1391
+ return out;
1392
+ }
1393
+ function request(key, defaultValue) {
1394
+ const current = inject(HttpRequest);
1395
+ return key === void 0 ? current : current.input(key, defaultValue);
1396
+ }
1397
+ function response(content, status, headers) {
1398
+ const factory = inject(ResponseFactory);
1399
+ return content === void 0 ? factory : factory.make(content, status, headers);
1400
+ }
1401
+ function redirect(to, status) {
1402
+ const redirector = inject(Redirector);
1403
+ return to === void 0 ? redirector : redirector.to(to, status);
1404
+ }
1405
+ function back(fallback, status) {
1406
+ return inject(Redirector).back(fallback, status);
1407
+ }
1408
+ function url(path, query, absolute) {
1409
+ const generator = inject(UrlGenerator);
1410
+ return path === void 0 ? generator : generator.to(path, query, absolute);
1411
+ }
1412
+ function route(name, parameters, absolute) {
1413
+ return inject(UrlGenerator).route(name, parameters, absolute);
1414
+ }
1415
+ function abort(status, message = "", headers = {}) {
1416
+ if (status instanceof Response) throw new HttpResponseException(status);
1417
+ throw new HttpException(status, message, headers);
1418
+ }
1419
+ function abortIf(condition, status, message = "", headers = {}) {
1420
+ if (condition) abort(status, message, headers);
1421
+ }
1422
+ function abortUnless(condition, status, message = "", headers = {}) {
1423
+ if (!condition) abort(status, message, headers);
1424
+ }
1425
+ function contentDisposition(type, name) {
1426
+ const fallback = name.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "\\$&");
1427
+ const header = `${type}; filename="${fallback}"`;
1428
+ return /^[\x20-\x7e]*$/.test(name) ? header : `${header}; filename*=UTF-8''${encodeURIComponent(name)}`;
1429
+ }
1430
+ var HttpResponseFactory = class {
1431
+ /** Strings default to text/html; charset=utf-8, as in Laravel. */
1432
+ make(content = null, status = 200, headers) {
1433
+ const merged = new Headers(headers);
1434
+ if (typeof content === "string" && !merged.has("content-type")) merged.set("content-type", "text/html; charset=utf-8");
1435
+ return new Response(content, { status, headers: merged });
1436
+ }
1437
+ json(data, status = 200, headers) {
1438
+ const merged = new Headers(headers);
1439
+ if (!merged.has("content-type")) merged.set("content-type", "application/json");
1440
+ return new Response(JSON.stringify(data === void 0 ? null : data), { status, headers: merged });
1441
+ }
1442
+ noContent(status = 204) {
1443
+ return new Response(null, { status });
1444
+ }
1445
+ /** Renders a view (through the ViewFactory) into an HTML response. */
1446
+ async view(name, data = {}, status = 200, headers) {
1447
+ const html = await inject(ViewFactory).make(name, data).render();
1448
+ return this.make(html, status, headers);
1449
+ }
1450
+ /** Streams a file as an attachment. A missing file is a 404. */
1451
+ download(path, name = basename(path), headers) {
1452
+ return this.#file(path, "attachment", name, headers);
1453
+ }
1454
+ /** Streams a file for the browser to display (Laravel's response()->file()). */
1455
+ file(path, headers) {
1456
+ return this.#file(path, "inline", basename(path), headers);
1457
+ }
1458
+ stream(body, status = 200, headers) {
1459
+ return new Response(body, { status, headers });
1460
+ }
1461
+ async #file(path, disposition, name, headers) {
1462
+ const info = await stat(path).catch(() => void 0);
1463
+ if (info === void 0 || !info.isFile()) throw new HttpException(404);
1464
+ const merged = new Headers(headers);
1465
+ if (!merged.has("content-type")) merged.set("content-type", mimeFromExtension(name));
1466
+ merged.set("content-length", String(info.size));
1467
+ merged.set("content-disposition", contentDisposition(disposition, name));
1468
+ const body = Readable.toWeb(createReadStream(path));
1469
+ return new Response(body, { status: 200, headers: merged });
1470
+ }
1471
+ };
1472
+ var ConvertEmptyStringsToNull = class {
1473
+ async handle(request2, next) {
1474
+ if (request2 instanceof IncomingRequest) request2.transformInput((_key, value) => value === "" ? null : value);
1475
+ return next(request2);
1476
+ }
1477
+ };
1478
+ var TRIM = /^[\s\uFEFF\u200B\u200E]+|[\s\uFEFF\u200B\u200E]+$/g;
1479
+ var TrimStrings = class {
1480
+ /** Dotted input keys left untouched. Override in a subclass to change. */
1481
+ except = ["current_password", "password", "password_confirmation"];
1482
+ async handle(request2, next) {
1483
+ if (request2 instanceof IncomingRequest) {
1484
+ request2.transformInput(
1485
+ (key, value) => typeof value === "string" && !this.except.includes(key) ? value.replace(TRIM, "") : value
1486
+ );
1487
+ }
1488
+ return next(request2);
1489
+ }
1490
+ };
1491
+ var plainServerError = () => new Response("Server Error", { status: 500, headers: { "content-type": "text/plain; charset=utf-8" } });
1492
+ var Kernel = class {
1493
+ constructor(app) {
1494
+ this.app = app;
1495
+ }
1496
+ app;
1497
+ #middleware = [TrimStrings, ConvertEmptyStringsToNull];
1498
+ #requests = /* @__PURE__ */ new WeakMap();
1499
+ getGlobalMiddleware() {
1500
+ return [...this.#middleware];
1501
+ }
1502
+ setGlobalMiddleware(middleware) {
1503
+ this.#middleware = [...middleware];
1504
+ return this;
1505
+ }
1506
+ appendMiddleware(...middleware) {
1507
+ for (const cls of middleware) if (!this.#middleware.includes(cls)) this.#middleware.push(cls);
1508
+ return this;
1509
+ }
1510
+ prependMiddleware(...middleware) {
1511
+ for (const cls of [...middleware].reverse()) if (!this.#middleware.includes(cls)) this.#middleware.unshift(cls);
1512
+ return this;
1513
+ }
1514
+ hasMiddleware(middleware) {
1515
+ return this.#middleware.includes(middleware);
1516
+ }
1517
+ /** Runs in the current request scope (Application.handle() opens one); called directly, it opens its own. */
1518
+ handle(raw) {
1519
+ const current = currentContainer();
1520
+ const scope = current !== void 0 && current !== this.app ? current : this.app.createScope();
1521
+ return scope.run(() => this.#handle(raw, scope));
1522
+ }
1523
+ async #handle(raw, scope) {
1524
+ let request2;
1525
+ let response2;
1526
+ try {
1527
+ request2 = new IncomingRequest(raw, {
1528
+ trustedProxies: this.#trustedProxies(),
1529
+ maxBodySize: this.#maxBodySize(),
1530
+ maxJsonDepth: this.#maxJsonDepth()
1531
+ });
1532
+ scope.instance(HttpRequest, request2);
1533
+ const state = { scope, request: request2, middleware: [] };
1534
+ this.#requests.set(raw, state);
1535
+ await request2.load();
1536
+ response2 = await this.#sendThroughGlobal(state);
1537
+ } catch (error) {
1538
+ request2 ??= this.#bareRequest(scope, raw);
1539
+ if (request2 !== void 0) {
1540
+ response2 = await this.#renderLast(scope, request2, error);
1541
+ } else {
1542
+ await this.#report(scope, error);
1543
+ response2 = plainServerError();
1544
+ }
1545
+ }
1546
+ if (raw.method.toUpperCase() === "HEAD" && response2.body !== null) {
1547
+ await response2.body.cancel().catch(() => void 0);
1548
+ response2 = new Response(null, { status: response2.status, statusText: response2.statusText, headers: response2.headers });
1549
+ }
1550
+ return response2;
1551
+ }
1552
+ /** The request without options, for rendering an error raised while the real one was being built. */
1553
+ #bareRequest(scope, raw) {
1554
+ try {
1555
+ const request2 = new IncomingRequest(raw);
1556
+ scope.instance(HttpRequest, request2);
1557
+ return request2;
1558
+ } catch {
1559
+ return void 0;
1560
+ }
1561
+ }
1562
+ /** 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. */
1563
+ async terminate(raw, response2) {
1564
+ const state = this.#requests.get(raw);
1565
+ if (state !== void 0) {
1566
+ this.#requests.delete(raw);
1567
+ await state.scope.run(async () => {
1568
+ const route2 = state.scope.bound(CurrentRoute) ? state.scope.make(CurrentRoute).middleware : [];
1569
+ for (const middleware of [...state.middleware, ...route2]) {
1570
+ if (typeof middleware.terminate !== "function") continue;
1571
+ try {
1572
+ await middleware.terminate(state.request, response2);
1573
+ } catch (error) {
1574
+ await this.#report(state.scope, error);
1575
+ }
1576
+ }
1577
+ await this.app.terminate();
1578
+ });
1579
+ return;
1580
+ }
1581
+ await this.app.terminate();
1582
+ }
1583
+ #sendThroughGlobal(state) {
1584
+ const { scope, request: request2 } = state;
1585
+ const pipes = this.#middleware.map((cls) => {
1586
+ const instance = scope.make(cls);
1587
+ state.middleware.push(instance);
1588
+ return (passable, next) => this.#guard(scope, passable, () => instance.handle(passable, next));
1589
+ });
1590
+ return scope.make(PipelineFactory).make().send(request2).through(pipes).then((passable) => this.#guard(scope, passable, () => this.#dispatch(scope, passable)));
1591
+ }
1592
+ #dispatch(scope, request2) {
1593
+ return scope.make(Router).dispatch(request2);
1594
+ }
1595
+ /** Converts a Responsable result, and renders a thrown error at this stage (Laravel's Routing\Pipeline). */
1596
+ async #guard(scope, request2, run) {
1597
+ try {
1598
+ const result = await run();
1599
+ if (result instanceof Response) return result;
1600
+ const responsable = result;
1601
+ if (typeof responsable?.toResponse === "function") return await responsable.toResponse(request2);
1602
+ throw new HttpServerError("Middleware must return a Response.");
1603
+ } catch (error) {
1604
+ if (!scope.bound(ExceptionHandler)) throw error;
1605
+ const handler = scope.make(ExceptionHandler);
1606
+ await handler.report(error);
1607
+ return handler.render(request2, error);
1608
+ }
1609
+ }
1610
+ /** Last resort: the handler failed or isn't bound. Never throws. */
1611
+ async #renderLast(scope, request2, error) {
1612
+ if (scope.bound(ExceptionHandler)) {
1613
+ try {
1614
+ const handler = scope.make(ExceptionHandler);
1615
+ await handler.report(error);
1616
+ return await handler.render(request2, error);
1617
+ } catch (renderError) {
1618
+ console.error(renderError);
1619
+ }
1620
+ } else {
1621
+ console.error(error);
1622
+ }
1623
+ return plainServerError();
1624
+ }
1625
+ async #report(scope, error) {
1626
+ try {
1627
+ if (scope.bound(ExceptionHandler)) await scope.make(ExceptionHandler).report(error);
1628
+ else console.error(error);
1629
+ } catch (reportError) {
1630
+ console.error(reportError);
1631
+ }
1632
+ }
1633
+ /** config('http.max_body_size'): bytes, or null for no limit. Anything else is a configuration error. */
1634
+ #maxBodySize() {
1635
+ if (!this.app.bound(ConfigRepository)) return 8 * 1024 * 1024;
1636
+ const value = this.app.make(ConfigRepository).get("http.max_body_size", 8 * 1024 * 1024);
1637
+ if (value === null) return null;
1638
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
1639
+ throw new HttpServerError(
1640
+ `Invalid http.max_body_size ${JSON.stringify(value) ?? String(value)}: use a number of bytes (0 or more), or null for no limit.`
1641
+ );
1642
+ }
1643
+ /** config('http.max_json_depth'): how deep a JSON body may nest, or null for no limit. */
1644
+ #maxJsonDepth() {
1645
+ if (!this.app.bound(ConfigRepository)) return 512;
1646
+ const value = this.app.make(ConfigRepository).get("http.max_json_depth", 512);
1647
+ if (value === null) return null;
1648
+ if (typeof value === "number" && Number.isInteger(value) && value >= 1) return value;
1649
+ throw new HttpServerError(
1650
+ `Invalid http.max_json_depth ${JSON.stringify(value) ?? String(value)}: use a whole number (1 or more), or null for no limit.`
1651
+ );
1652
+ }
1653
+ /** config('http.trusted_proxies'): an array, or a comma-separated string such as '*' or '10.0.0.0/8,::1'. */
1654
+ #trustedProxies() {
1655
+ if (!this.app.bound(ConfigRepository)) return [];
1656
+ const value = this.app.make(ConfigRepository).get("http.trusted_proxies", []);
1657
+ if (typeof value === "string") return value.split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
1658
+ return Array.isArray(value) ? value.map(String) : [];
1659
+ }
1660
+ };
1661
+ var MiddlewareConfiguration = class {
1662
+ constructor(kernel, router, config) {
1663
+ this.kernel = kernel;
1664
+ this.router = router;
1665
+ this.config = config;
1666
+ }
1667
+ kernel;
1668
+ router;
1669
+ config;
1670
+ append(...middleware) {
1671
+ this.kernel.appendMiddleware(...middleware);
1672
+ return this;
1673
+ }
1674
+ prepend(...middleware) {
1675
+ this.kernel.prependMiddleware(...middleware);
1676
+ return this;
1677
+ }
1678
+ web(options) {
1679
+ return this.#modifyGroup("web", options);
1680
+ }
1681
+ api(options) {
1682
+ return this.#modifyGroup("api", options);
1683
+ }
1684
+ alias(aliases) {
1685
+ for (const [name, middleware] of Object.entries(aliases)) this.router.aliasMiddleware(name, middleware);
1686
+ return this;
1687
+ }
1688
+ group(name, middleware) {
1689
+ this.router.middlewareGroup(name, middleware);
1690
+ return this;
1691
+ }
1692
+ /** Stored in config('http.csrf_except'), which the Session component's VerifyCsrfToken reads. */
1693
+ validateCsrfTokens(options) {
1694
+ const current = this.config?.get("http.csrf_except", []) ?? [];
1695
+ this.config?.set("http.csrf_except", [...current, ...options.except]);
1696
+ return this;
1697
+ }
1698
+ /** Stored in config('http.redirect_guests_to'), which the Auth middleware and the exception handler read. */
1699
+ redirectGuestsTo(url2) {
1700
+ this.config?.set("http.redirect_guests_to", url2);
1701
+ return this;
1702
+ }
1703
+ /** Laravel 11's redirectUsersTo(): stored in config('http.redirect_users_to') for Auth's RedirectIfAuthenticated. */
1704
+ redirectUsersTo(url2) {
1705
+ this.config?.set("http.redirect_users_to", url2);
1706
+ return this;
1707
+ }
1708
+ /** Laravel's order: prepends, then the group minus removals, then appends. */
1709
+ #modifyGroup(name, { append = [], prepend = [], remove = [] }) {
1710
+ const current = this.router.getMiddlewareGroups()[name] ?? [];
1711
+ const moved = /* @__PURE__ */ new Set([...append, ...prepend, ...remove]);
1712
+ this.router.middlewareGroup(name, [...prepend, ...current.filter((entry) => !moved.has(entry)), ...append]);
1713
+ return this;
1714
+ }
1715
+ };
1716
+ function applyMiddlewareCallbacks(app, kernel) {
1717
+ if (!app.bound(MiddlewareCallbacks)) return;
1718
+ const callbacks = app.make(MiddlewareCallbacks);
1719
+ if (callbacks.length === 0) return;
1720
+ const config = app.bound(ConfigRepository) ? app.make(ConfigRepository) : void 0;
1721
+ const configurator = new MiddlewareConfiguration(kernel, app.make(Router), config);
1722
+ for (const callback of callbacks) callback(configurator);
1723
+ }
1724
+ var STATUS_TEXTS = {
1725
+ 400: "Bad Request",
1726
+ 401: "Unauthorized",
1727
+ 402: "Payment Required",
1728
+ 403: "Forbidden",
1729
+ 404: "Not Found",
1730
+ 405: "Method Not Allowed",
1731
+ 408: "Request Timeout",
1732
+ 409: "Conflict",
1733
+ 410: "Gone",
1734
+ 413: "Content Too Large",
1735
+ 415: "Unsupported Media Type",
1736
+ 419: "Page Expired",
1737
+ 422: "Unprocessable Content",
1738
+ 429: "Too Many Requests",
1739
+ 500: "Server Error",
1740
+ 502: "Bad Gateway",
1741
+ 503: "Service Unavailable",
1742
+ 504: "Gateway Timeout"
1743
+ };
1744
+ function statusText(status) {
1745
+ return STATUS_TEXTS[status] ?? (status >= 500 ? "Server Error" : "Error");
1746
+ }
1747
+ function escapeHtml(value) {
1748
+ return value.replace(/[&<>"']/g, (char) => `&#${char.charCodeAt(0)};`);
1749
+ }
1750
+ function minimalPage(status, message) {
1751
+ const title = escapeHtml(message);
1752
+ return `<!DOCTYPE html>
1753
+ <html lang="en">
1754
+ <head>
1755
+ <meta charset="utf-8">
1756
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1757
+ <title>${title}</title>
1758
+ <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>
1759
+ </head>
1760
+ <body><div class="code">${status}</div><div class="message">${title}</div></body>
1761
+ </html>`;
1762
+ }
1763
+ function stackFrames(error) {
1764
+ const frames = [];
1765
+ for (const raw of (error.stack ?? "").split("\n").slice(1)) {
1766
+ const text = raw.trim();
1767
+ const match = /(?:\(|@|at )((?:file:\/\/)?(?:\/|[A-Za-z]:\\)[^()]*?):(\d+):(\d+)\)?$/.exec(text);
1768
+ if (match === null) continue;
1769
+ const location = match[1];
1770
+ const file = location.startsWith("file://") ? fileURLToPath(location) : location;
1771
+ frames.push({ file, line: Number(match[2]), column: Number(match[3]), text });
1772
+ }
1773
+ return frames;
1774
+ }
1775
+ async function excerpt(frame, radius = 5) {
1776
+ const source = await readFile(frame.file, "utf8").catch(() => void 0);
1777
+ if (source === void 0) return void 0;
1778
+ const lines = source.split("\n");
1779
+ const start = Math.max(1, frame.line - radius);
1780
+ const end = Math.min(lines.length, frame.line + radius);
1781
+ const rows = [];
1782
+ for (let number = start; number <= end; number++) {
1783
+ const current = number === frame.line ? ' class="current"' : "";
1784
+ rows.push(`<tr${current}><td class="n">${number}</td><td><code>${escapeHtml(lines[number - 1] ?? "")}</code></td></tr>`);
1785
+ }
1786
+ return `<table class="source">${rows.join("")}</table>`;
1787
+ }
1788
+ async function debugPage(error, method, url2) {
1789
+ const err = error instanceof Error ? error : new Error(String(error));
1790
+ const frames = stackFrames(err);
1791
+ const appFrame = frames.find((frame) => !frame.file.includes("node_modules") && !frame.file.startsWith("node:")) ?? frames[0];
1792
+ const source = appFrame === void 0 ? void 0 : await excerpt(appFrame);
1793
+ const causes = [];
1794
+ for (let cause = err.cause, depth = 0; cause !== void 0 && depth < 5; cause = cause.cause, depth++) {
1795
+ causes.push(`<li>${escapeHtml(cause instanceof Error ? `${cause.name}: ${cause.message}` : String(cause))}</li>`);
1796
+ }
1797
+ return `<!DOCTYPE html>
1798
+ <html lang="en">
1799
+ <head>
1800
+ <meta charset="utf-8">
1801
+ <title>${escapeHtml(err.message || err.name)}</title>
1802
+ <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>
1803
+ </head>
1804
+ <body><main>
1805
+ <header><div class="class">${escapeHtml(err.name)}</div><div class="message">${escapeHtml(err.message)}</div><div class="request">${escapeHtml(method)} ${escapeHtml(url2)}</div></header>
1806
+ ${appFrame === void 0 ? "" : `<section><div>${escapeHtml(appFrame.file)}:${appFrame.line}</div>${source ?? ""}</section>`}
1807
+ <section><ol>${frames.map((frame) => `<li>${escapeHtml(frame.text)}</li>`).join("")}</ol></section>
1808
+ ${causes.length === 0 ? "" : `<section><div>Caused by</div><ul>${causes.join("")}</ul></section>`}
1809
+ </main></body>
1810
+ </html>`;
1811
+ }
1812
+ var NOT_REPORTED = /* @__PURE__ */ new Set(["Http", "ModelNotFound", "Validation", "Authentication", "Authorization", "TokenMismatch"]);
1813
+ var HTTP_KINDS = /* @__PURE__ */ new Set(["Http", "ModelNotFound", "Authorization", "TokenMismatch"]);
1814
+ var kindOf = (error) => typeof error === "object" && error !== null && typeof error.kind === "string" ? error.kind : void 0;
1815
+ function statusFor(error) {
1816
+ const status = error?.status;
1817
+ switch (kindOf(error)) {
1818
+ case "Http":
1819
+ return status ?? 500;
1820
+ case "ModelNotFound":
1821
+ return 404;
1822
+ case "Authorization":
1823
+ return status ?? 403;
1824
+ case "TokenMismatch":
1825
+ return 419;
1826
+ case "Authentication":
1827
+ return 401;
1828
+ case "Validation":
1829
+ return status ?? 422;
1830
+ case "Server":
1831
+ return status ?? 500;
1832
+ default:
1833
+ return 500;
1834
+ }
1835
+ }
1836
+ function summarizeValidation(errors) {
1837
+ const messages = Object.values(errors).flat();
1838
+ if (messages.length === 0) return "The given data was invalid.";
1839
+ const more = messages.length - 1;
1840
+ return more === 0 ? messages[0] : `${messages[0]} (and ${more} more ${more === 1 ? "error" : "errors"})`;
1841
+ }
1842
+ var isHttpLike = (error) => {
1843
+ const kind = kindOf(error);
1844
+ return kind !== void 0 && HTTP_KINDS.has(kind);
1845
+ };
1846
+ var ErrorHandler = class {
1847
+ constructor(app) {
1848
+ this.app = app;
1849
+ }
1850
+ app;
1851
+ /** Input keys never flashed back on a validation redirect. */
1852
+ dontFlash = ["current_password", "password", "password_confirmation"];
1853
+ #reportCallbacks = [];
1854
+ #renderCallbacks = [];
1855
+ #dontReport = [];
1856
+ #reported = /* @__PURE__ */ new WeakSet();
1857
+ #jsonWhen;
1858
+ reportUsing(callback) {
1859
+ this.#reportCallbacks.push(callback);
1860
+ }
1861
+ renderUsing(callback) {
1862
+ this.#renderCallbacks.push(callback);
1863
+ }
1864
+ ignore(...classes) {
1865
+ this.#dontReport.push(...classes);
1866
+ }
1867
+ /** Laravel's shouldRenderJsonWhen(): the callback decides JSON or HTML instead of the Accept header. */
1868
+ shouldRenderJsonWhen(callback) {
1869
+ this.#jsonWhen = callback;
1870
+ }
1871
+ /** Laravel's shouldReturnJson(): the shouldRenderJsonWhen() callback when set, else request.expectsJson(). */
1872
+ shouldReturnJson(request2, error) {
1873
+ return this.#jsonWhen !== void 0 ? this.#jsonWhen(request2, error) : request2.expectsJson();
1874
+ }
1875
+ shouldReport(error) {
1876
+ const kind = kindOf(error);
1877
+ if (kind !== void 0 && NOT_REPORTED.has(kind)) return false;
1878
+ return !this.#dontReport.some((cls) => error instanceof cls);
1879
+ }
1880
+ /** Report callbacks first (returning false stops the default log), then LogManager.error(), else console.error. Once per error. */
1881
+ async report(error) {
1882
+ if (!this.shouldReport(error)) return;
1883
+ if (typeof error === "object" && error !== null) {
1884
+ if (this.#reported.has(error)) return;
1885
+ this.#reported.add(error);
1886
+ }
1887
+ try {
1888
+ for (const callback of this.#reportCallbacks) if (await callback(error) === false) return;
1889
+ const container = this.#container();
1890
+ if (container.bound(LogManager)) {
1891
+ const message = error instanceof Error ? error.message : String(error);
1892
+ container.make(LogManager).error(message, { exception: error });
1893
+ } else {
1894
+ console.error(error);
1895
+ }
1896
+ } catch (reportError) {
1897
+ console.error(reportError);
1898
+ }
1899
+ }
1900
+ async render(request2, error) {
1901
+ for (const callback of this.#renderCallbacks) {
1902
+ const response3 = await callback(error, request2);
1903
+ if (response3 instanceof Response) return response3;
1904
+ }
1905
+ if (error instanceof HttpResponseException) return error.response;
1906
+ const custom = await this.#renderCustom(request2, error);
1907
+ if (custom !== void 0) return custom;
1908
+ const http = this.#httpRequest(request2);
1909
+ const kind = kindOf(error);
1910
+ let response2;
1911
+ if (kind === "Validation") response2 = await this.#renderValidation(http, error);
1912
+ else if (kind === "Authentication") response2 = await this.#renderAuthentication(http, error);
1913
+ else if (this.shouldReturnJson(http, error)) response2 = this.#renderJson(error);
1914
+ else response2 = await this.#renderHtml(http, error);
1915
+ const headers = error?.headers;
1916
+ if (headers !== void 0) for (const [name, value] of Object.entries(headers)) response2.headers.set(name, value);
1917
+ return response2;
1918
+ }
1919
+ /** An error with its own render(request) or toResponse(request) (Laravel's renderable exceptions / Responsable). */
1920
+ async #renderCustom(request2, error) {
1921
+ if (typeof error !== "object" || error === null) return void 0;
1922
+ const candidate = error;
1923
+ for (const method of [candidate.render, candidate.toResponse]) {
1924
+ if (typeof method !== "function") continue;
1925
+ const response2 = await method.call(error, request2);
1926
+ if (response2 instanceof Response) return response2;
1927
+ }
1928
+ return void 0;
1929
+ }
1930
+ async #renderValidation(request2, error) {
1931
+ if (this.shouldReturnJson(request2, error)) {
1932
+ return this.#json({ message: summarizeValidation(error.errors), errors: error.errors }, statusFor(error));
1933
+ }
1934
+ const input = { ...request2.input() };
1935
+ for (const key of this.dontFlash) delete input[key];
1936
+ const container = this.#container();
1937
+ if (!container.bound(Redirector)) {
1938
+ return this.#redirect(error.redirectTo ?? request2.header("referer") ?? "/");
1939
+ }
1940
+ const redirector = container.make(Redirector);
1941
+ const redirect2 = error.redirectTo !== void 0 ? redirector.to(error.redirectTo) : redirector.back();
1942
+ const response2 = await redirect2.withInput(input).withErrors(error.errors, error.errorBag).toResponse(request2);
1943
+ return response2;
1944
+ }
1945
+ async #renderAuthentication(request2, error) {
1946
+ if (this.shouldReturnJson(request2, error)) return this.#json({ message: error.message || "Unauthenticated." }, 401);
1947
+ const target2 = error.redirectTo ?? this.#guestRedirect(request2);
1948
+ const container = this.#container();
1949
+ if (!container.bound(Redirector)) return this.#redirect(target2);
1950
+ const redirector = container.make(Redirector);
1951
+ const redirect2 = typeof redirector.guest === "function" ? redirector.guest(target2) : redirector.to(target2);
1952
+ return await redirect2.toResponse(request2);
1953
+ }
1954
+ /** config('http.redirect_guests_to'), then route('login'), then '/login'. */
1955
+ #guestRedirect(request2) {
1956
+ const configured = this.#config("http.redirect_guests_to", void 0);
1957
+ if (typeof configured === "function") return String(configured(request2));
1958
+ if (typeof configured === "string" && configured !== "") return configured;
1959
+ const container = this.#container();
1960
+ if (container.bound(UrlGenerator)) {
1961
+ const url2 = container.make(UrlGenerator);
1962
+ if (url2.hasRoute("login")) return url2.route("login");
1963
+ }
1964
+ return "/login";
1965
+ }
1966
+ #renderJson(error) {
1967
+ const status = statusFor(error);
1968
+ if (isHttpLike(error)) {
1969
+ const message = error instanceof Error && error.message !== "" ? error.message : statusText(status);
1970
+ return this.#json({ message }, status);
1971
+ }
1972
+ if (!this.#debug()) return this.#json({ message: "Server Error" }, status);
1973
+ const err = error instanceof Error ? error : new Error(String(error));
1974
+ const [frame] = stackFrames(err);
1975
+ return this.#json(
1976
+ {
1977
+ message: err.message,
1978
+ exception: err.name,
1979
+ file: frame?.file,
1980
+ line: frame?.line,
1981
+ trace: stackFrames(err).map((entry) => entry.text)
1982
+ },
1983
+ status
1984
+ );
1985
+ }
1986
+ async #renderHtml(request2, error) {
1987
+ const status = statusFor(error);
1988
+ if (!isHttpLike(error) && this.#debug()) {
1989
+ return this.#html(await debugPage(error, request2.method(), request2.fullUrl()), status);
1990
+ }
1991
+ const message = status === 403 && error instanceof Error && error.message !== "" ? error.message : statusText(status);
1992
+ const view = await this.#errorView(status, error, message);
1993
+ return this.#html(view ?? minimalPage(status, message), status);
1994
+ }
1995
+ /** resources/views/errors/{status}.tsx, then errors/4xx or errors/5xx, through the ViewFactory. */
1996
+ async #errorView(status, error, message) {
1997
+ const container = this.#container();
1998
+ if (!container.bound(ViewFactory)) return void 0;
1999
+ const views = container.make(ViewFactory);
2000
+ for (const name of [`errors.${status}`, `errors.${Math.floor(status / 100)}xx`]) {
2001
+ if (!views.exists(name)) continue;
2002
+ try {
2003
+ return await views.make(name, { exception: error, status, message }).render();
2004
+ } catch (viewError) {
2005
+ await this.report(viewError);
2006
+ return void 0;
2007
+ }
2008
+ }
2009
+ return void 0;
2010
+ }
2011
+ #httpRequest(request2) {
2012
+ if (typeof request2 === "object" && request2 !== null && typeof request2.expectsJson === "function") {
2013
+ return request2;
2014
+ }
2015
+ return new IncomingRequest(request2 instanceof Request ? request2 : new Request("http://localhost/"));
2016
+ }
2017
+ #json(body, status) {
2018
+ return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
2019
+ }
2020
+ #html(body, status) {
2021
+ return new Response(body, { status, headers: { "content-type": "text/html; charset=utf-8" } });
2022
+ }
2023
+ #redirect(location) {
2024
+ return new Response(null, { status: 302, headers: { Location: location } });
2025
+ }
2026
+ #debug() {
2027
+ return this.#config("app.debug", false) === true;
2028
+ }
2029
+ #config(key, fallback) {
2030
+ return this.app.bound(ConfigRepository) ? this.app.make(ConfigRepository).get(key, fallback) : fallback;
2031
+ }
2032
+ /** The request scope while handling a request (so scoped services resolve), else the application. */
2033
+ #container() {
2034
+ return currentContainer() ?? this.app;
2035
+ }
2036
+ };
2037
+ var ExceptionsConfiguration = class {
2038
+ constructor(handler) {
2039
+ this.handler = handler;
2040
+ }
2041
+ handler;
2042
+ report(callback) {
2043
+ this.handler.reportUsing(callback);
2044
+ return this;
2045
+ }
2046
+ render(callback) {
2047
+ this.handler.renderUsing(callback);
2048
+ return this;
2049
+ }
2050
+ dontReport(...classes) {
2051
+ this.handler.ignore(...classes);
2052
+ return this;
2053
+ }
2054
+ /** shouldRenderJsonWhen((request) => request.is('api/*')): JSON errors for those requests, whatever they accept. */
2055
+ shouldRenderJsonWhen(callback) {
2056
+ this.handler.shouldRenderJsonWhen(callback);
2057
+ return this;
2058
+ }
2059
+ };
2060
+ function applyExceptionCallbacks(app, handler) {
2061
+ if (!app.bound(ExceptionCallbacks)) return;
2062
+ const configurator = new ExceptionsConfiguration(handler);
2063
+ for (const callback of app.make(ExceptionCallbacks)) callback(configurator);
2064
+ }
2065
+ var HIDDEN_SEGMENT = /^\.|\.(ts|tsx|mts|cts)$|[. ]$/i;
2066
+ async function servePublicFile(publicPath, request2) {
2067
+ if (request2.method !== "GET" && request2.method !== "HEAD") return void 0;
2068
+ let pathname;
2069
+ try {
2070
+ pathname = decodeURIComponent(new URL(request2.url).pathname);
2071
+ } catch {
2072
+ return void 0;
2073
+ }
2074
+ if (pathname === "/" || pathname.includes("\0")) return void 0;
2075
+ const root = resolve(publicPath);
2076
+ const file = resolve(root, `.${pathname}`);
2077
+ if (!file.startsWith(root + sep)) return void 0;
2078
+ if (relative(root, file).split(/[\\/]/).some((segment) => HIDDEN_SEGMENT.test(segment))) return void 0;
2079
+ const info = await stat2(file).catch(() => void 0);
2080
+ if (info === void 0 || !info.isFile()) return void 0;
2081
+ const type = mimeFromExtension(file);
2082
+ const headers = new Headers({
2083
+ "content-type": type.startsWith("text/") || type === "application/json" ? `${type}; charset=utf-8` : type,
2084
+ "content-length": String(info.size),
2085
+ "last-modified": info.mtime.toUTCString()
2086
+ });
2087
+ if (pathname.startsWith("/build/assets/")) headers.set("cache-control", "public, max-age=31536000, immutable");
2088
+ if (request2.method === "HEAD") return new Response(null, { status: 200, headers });
2089
+ const body = Readable2.toWeb(createReadStream2(file));
2090
+ return new Response(body, { status: 200, headers });
2091
+ }
2092
+ async function terminateRequest(app, request2, response2) {
2093
+ try {
2094
+ if (app.bound(HttpKernel)) await app.make(HttpKernel).terminate?.(request2, response2);
2095
+ } catch (error) {
2096
+ console.error(error);
2097
+ }
2098
+ }
2099
+ async function handleSafely(app, request2, publicPath) {
2100
+ try {
2101
+ if (publicPath !== void 0) {
2102
+ const file = await servePublicFile(publicPath, request2);
2103
+ if (file !== void 0) return file;
2104
+ }
2105
+ return await app.handle(request2);
2106
+ } catch (error) {
2107
+ console.error(error);
2108
+ return plainServerError();
2109
+ }
2110
+ }
2111
+ function exitOnSignals(close, closed) {
2112
+ const onSignal = () => {
2113
+ void close().then(() => process.exit(0));
2114
+ };
2115
+ process.once("SIGINT", onSignal);
2116
+ process.once("SIGTERM", onSignal);
2117
+ void closed.then(() => {
2118
+ process.off("SIGINT", onSignal);
2119
+ process.off("SIGTERM", onSignal);
2120
+ });
2121
+ }
2122
+ var displayHost = (hostname) => hostname.includes(":") ? `[${hostname}]` : hostname;
2123
+ function bunRuntime() {
2124
+ return globalThis.Bun;
2125
+ }
2126
+ async function serveBun(app, options) {
2127
+ const bun = bunRuntime();
2128
+ if (bun === void 0) throw new HttpServerError("Bun.serve is not available: the process is not running on Bun.");
2129
+ let markClosed = () => void 0;
2130
+ const closed = new Promise((resolve2) => markClosed = resolve2);
2131
+ const server = bun.serve({
2132
+ port: options.port,
2133
+ hostname: options.hostname,
2134
+ async fetch(request2, current) {
2135
+ setClientAddress(request2, current.requestIP(request2)?.address);
2136
+ const response2 = await handleSafely(app, request2, options.publicPath);
2137
+ setTimeout(() => void terminateRequest(app, request2, response2), 0);
2138
+ return response2;
2139
+ },
2140
+ error(error) {
2141
+ console.error(error);
2142
+ return plainServerError();
2143
+ }
2144
+ });
2145
+ let closing;
2146
+ const close = () => closing ??= (async () => {
2147
+ let timer;
2148
+ const forced = new Promise((resolve2) => {
2149
+ timer = setTimeout(() => void Promise.resolve(server.stop(true)).then(resolve2), options.shutdownTimeout);
2150
+ });
2151
+ await Promise.race([Promise.resolve(server.stop(false)), forced]);
2152
+ clearTimeout(timer);
2153
+ markClosed();
2154
+ })();
2155
+ if (options.signals) exitOnSignals(close, closed);
2156
+ return {
2157
+ runtime: "bun",
2158
+ hostname: options.hostname,
2159
+ port: server.port,
2160
+ url: `http://${displayHost(options.hostname)}:${server.port}`,
2161
+ close,
2162
+ closed
2163
+ };
2164
+ }
2165
+ function requestBody(req) {
2166
+ let finished = false;
2167
+ const detach = () => {
2168
+ finished = true;
2169
+ req.off("data", onData);
2170
+ req.off("end", onEnd);
2171
+ req.off("error", onError);
2172
+ req.off("close", onClose);
2173
+ };
2174
+ let controller;
2175
+ const onData = (chunk2) => {
2176
+ if (finished) return;
2177
+ controller.enqueue(new Uint8Array(chunk2.buffer, chunk2.byteOffset, chunk2.byteLength));
2178
+ if ((controller.desiredSize ?? 0) <= 0) req.pause();
2179
+ };
2180
+ const onEnd = () => {
2181
+ if (finished) return;
2182
+ detach();
2183
+ controller.close();
2184
+ };
2185
+ const onError = (error) => {
2186
+ if (finished) return;
2187
+ detach();
2188
+ controller.error(error);
2189
+ };
2190
+ const onClose = () => {
2191
+ if (!finished && !req.complete) onError(new Error("The request was aborted."));
2192
+ };
2193
+ return new ReadableStream(
2194
+ {
2195
+ start(c) {
2196
+ controller = c;
2197
+ req.on("data", onData);
2198
+ req.on("end", onEnd);
2199
+ req.on("error", onError);
2200
+ req.on("close", onClose);
2201
+ req.pause();
2202
+ },
2203
+ pull() {
2204
+ req.resume();
2205
+ },
2206
+ cancel() {
2207
+ detach();
2208
+ }
2209
+ },
2210
+ { highWaterMark: 0 }
2211
+ );
2212
+ }
2213
+ function toWebRequest(req, res, fallbackHost) {
2214
+ const target2 = req.url ?? "/";
2215
+ const url2 = new URL(`http://${req.headers.host ?? fallbackHost}${target2.startsWith("/") ? target2 : `/${target2}`}`);
2216
+ const headers = new Headers();
2217
+ for (let i = 0; i < req.rawHeaders.length; i += 2) {
2218
+ const name = req.rawHeaders[i];
2219
+ if (!name.startsWith(":")) headers.append(name, req.rawHeaders[i + 1]);
2220
+ }
2221
+ const method = (req.method ?? "GET").toUpperCase();
2222
+ const controller = new AbortController();
2223
+ res.once("close", () => {
2224
+ if (!res.writableFinished) controller.abort();
2225
+ });
2226
+ const init = { method, headers, signal: controller.signal };
2227
+ if (method !== "GET" && method !== "HEAD") {
2228
+ init.body = requestBody(req);
2229
+ init.duplex = "half";
2230
+ }
2231
+ return new Request(url2, init);
2232
+ }
2233
+ async function writeResponse(res, response2) {
2234
+ res.statusCode = response2.status;
2235
+ if (response2.statusText !== "") res.statusMessage = response2.statusText;
2236
+ response2.headers.forEach((value, name) => {
2237
+ if (name !== "set-cookie") res.setHeader(name, value);
2238
+ });
2239
+ const cookies = response2.headers.getSetCookie();
2240
+ if (cookies.length > 0) res.setHeader("set-cookie", cookies);
2241
+ if (response2.body === null) {
2242
+ res.end();
2243
+ return;
582
2244
  }
583
- async bootProvider(provider) {
584
- try {
585
- await provider.boot?.();
586
- } catch (error) {
587
- throw new ProviderBootError(provider.constructor.name, error);
2245
+ await pipeline(Readable3.fromWeb(response2.body), res);
2246
+ }
2247
+ async function serveNode(app, options) {
2248
+ const fallbackHost = `${displayHost(options.hostname)}:${options.port}`;
2249
+ const server = createServer((req, res) => {
2250
+ void (async () => {
2251
+ let request2;
2252
+ try {
2253
+ request2 = toWebRequest(req, res, fallbackHost);
2254
+ } catch {
2255
+ res.statusCode = 400;
2256
+ res.end("Bad Request");
2257
+ return;
2258
+ }
2259
+ setClientAddress(request2, req.socket.remoteAddress);
2260
+ const response2 = await handleSafely(app, request2, options.publicPath);
2261
+ try {
2262
+ await writeResponse(res, response2);
2263
+ } catch {
2264
+ res.destroy();
2265
+ }
2266
+ await terminateRequest(app, request2, response2);
2267
+ })();
2268
+ });
2269
+ await new Promise((resolve2, reject) => {
2270
+ server.once("error", reject);
2271
+ server.listen(options.port, options.hostname, () => {
2272
+ server.off("error", reject);
2273
+ resolve2();
2274
+ });
2275
+ });
2276
+ const address = server.address();
2277
+ const closed = new Promise((resolve2) => server.once("close", () => resolve2()));
2278
+ let closing;
2279
+ const close = () => closing ??= (async () => {
2280
+ const done = new Promise((resolve2) => server.close(() => resolve2()));
2281
+ server.closeIdleConnections();
2282
+ const idle = setInterval(() => server.closeIdleConnections(), 50);
2283
+ const force = setTimeout(() => server.closeAllConnections(), options.shutdownTimeout);
2284
+ await done;
2285
+ clearInterval(idle);
2286
+ clearTimeout(force);
2287
+ })();
2288
+ if (options.signals) exitOnSignals(close, closed);
2289
+ return {
2290
+ runtime: "node",
2291
+ hostname: options.hostname,
2292
+ port: address.port,
2293
+ url: `http://${displayHost(options.hostname)}:${address.port}`,
2294
+ close,
2295
+ closed
2296
+ };
2297
+ }
2298
+ function detectRuntime() {
2299
+ return bunRuntime() !== void 0 ? "bun" : "node";
2300
+ }
2301
+ async function serve(app, options = {}) {
2302
+ const resolved = {
2303
+ port: options.port ?? Number(process.env.PORT ?? process.env.SERVER_PORT ?? 8e3),
2304
+ hostname: options.hostname ?? "127.0.0.1",
2305
+ signals: options.signals ?? true,
2306
+ shutdownTimeout: options.shutdownTimeout ?? 1e4,
2307
+ ...options.publicPath !== false && { publicPath: options.publicPath ?? app.publicPath() }
2308
+ };
2309
+ await app.boot();
2310
+ const runtime = options.runtime ?? detectRuntime();
2311
+ const server = runtime === "bun" ? await serveBun(app, resolved) : await serveNode(app, resolved);
2312
+ const onListen = options.onListen ?? ((running) => console.log(`Server running on [${running.url}].`));
2313
+ onListen(server);
2314
+ return server;
2315
+ }
2316
+ function io(command) {
2317
+ return {
2318
+ option: (name) => {
2319
+ const value = command.option(name);
2320
+ return value === void 0 || value === null || value === false || value === "" ? void 0 : String(value);
2321
+ },
2322
+ flag: (name) => {
2323
+ const value = command.option(name);
2324
+ return value === true || value === "true" || value === "1";
2325
+ },
2326
+ info: (message) => command.info(message),
2327
+ line: (message) => command.line(message),
2328
+ warn: (message) => command.warn(message),
2329
+ error: (message) => command.error(message)
2330
+ };
2331
+ }
2332
+ var middlewareName = (reference) => typeof reference === "string" ? reference : reference.name;
2333
+ function routeListRows(routes) {
2334
+ const rows = routes.map((route2) => {
2335
+ const listable = route2;
2336
+ return {
2337
+ method: route2.methods.join("|"),
2338
+ uri: route2.uri,
2339
+ name: route2.getName() ?? null,
2340
+ action: typeof listable.getActionName === "function" ? listable.getActionName() : "Closure",
2341
+ middleware: typeof listable.gatherMiddleware === "function" ? listable.gatherMiddleware().map(middlewareName) : []
2342
+ };
2343
+ });
2344
+ return rows.sort((a, b) => a.uri < b.uri ? -1 : a.uri > b.uri ? 1 : 0);
2345
+ }
2346
+ function filterRouteList(rows, filters) {
2347
+ const excluded = (filters.exceptPath ?? "").split(",").filter((path) => path !== "");
2348
+ return rows.filter((row) => {
2349
+ if (filters.name !== void 0 && !(row.name ?? "").includes(filters.name)) return false;
2350
+ if (filters.path !== void 0 && !row.uri.includes(filters.path)) return false;
2351
+ if (filters.method !== void 0 && !row.method.includes(filters.method.toUpperCase())) return false;
2352
+ return !excluded.some((path) => row.uri.includes(path));
2353
+ });
2354
+ }
2355
+ function formatRouteList(rows, width) {
2356
+ const methodWidth = Math.max(...rows.map((row) => row.method.length));
2357
+ const lines = rows.map((row) => {
2358
+ const spaces = " ".repeat(Math.max(methodWidth + 6 - row.method.length, 0));
2359
+ const action = row.action === "Closure" ? row.name ?? "" : row.name === null ? row.action : `${row.name} \u203A ${row.action}`;
2360
+ const used = row.method.length + spaces.length + row.uri.length + action.length + 6 + (action === "" ? 0 : 1);
2361
+ const dots = ".".repeat(Math.max(width - used, 0));
2362
+ return ` ${row.method} ${spaces}${row.uri}${dots === "" ? "" : ` ${dots}`}${action === "" ? "" : ` ${action}`}`;
2363
+ });
2364
+ const count = `Showing [${rows.length}] ${rows.length === 1 ? "route" : "routes"}`;
2365
+ return [...lines, "", `${" ".repeat(Math.max(width - count.length - 2, 0))}${count}`];
2366
+ }
2367
+ var API_ROUTES_STUB = `import { request, Route } from '@orieljs/framework'
2368
+
2369
+ Route.get('/user', () => request().user()).middleware('auth:token')
2370
+ `;
2371
+ function addApiRouting(source) {
2372
+ const code = source.replace(/^[ \t]*\/\/.*$/gm, "");
2373
+ if (/withRouting\(\s*\{[^}]*\bapi\s*:/.test(code)) return source;
2374
+ const commented = /^([ \t]*)\/\/\s*(api:\s*['"`][^'"`\n]*['"`],?)[ \t]*$/m.exec(source);
2375
+ if (commented !== null) return source.replace(commented[0], () => `${commented[1]}${commented[2]}`);
2376
+ const web = /^([ \t]*)web:\s*(['"`])[^'"`\n]*\2[ \t]*,?[ \t]*$/m.exec(source);
2377
+ if (web !== null) {
2378
+ const line = web[0].trimEnd();
2379
+ return source.replace(web[0], () => `${line.endsWith(",") ? line : `${line},`}
2380
+ ${web[1]}api: 'routes/api.ts',`);
2381
+ }
2382
+ const inline = /withRouting\(\s*\{/.exec(source);
2383
+ if (inline !== null) return source.replace(inline[0], () => `${inline[0]} api: 'routes/api.ts',`);
2384
+ const create = /^([ \t]*)\.create\(\)/m.exec(source);
2385
+ if (create !== null) return source.replace(create[0], () => `${create[1]}.withRouting({ api: 'routes/api.ts' })
2386
+ ${create[0]}`);
2387
+ return void 0;
2388
+ }
2389
+ function registerHttpCommands(kernel, app, options = {}) {
2390
+ const serveApp = options.serve ?? serve;
2391
+ const terminalWidth = options.terminalWidth ?? (() => process.stdout.columns ?? 120);
2392
+ kernel.command(
2393
+ "serve {--host=127.0.0.1 : The host address to serve the application on} {--port= : The port to serve the application on}",
2394
+ async function() {
2395
+ const out = io(this);
2396
+ const port = out.option("port");
2397
+ if (port !== void 0 && !/^\d+$/.test(port)) {
2398
+ out.error("The --port option must be a number.");
2399
+ return 1;
2400
+ }
2401
+ const server = await serveApp(app, {
2402
+ hostname: out.option("host") ?? "127.0.0.1",
2403
+ ...port !== void 0 && { port: Number(port) },
2404
+ onListen: (running) => {
2405
+ out.info(`Server running on [${running.url}].`);
2406
+ out.line("Press Ctrl+C to stop the server");
2407
+ }
2408
+ });
2409
+ await server.closed;
2410
+ return 0;
588
2411
  }
2412
+ ).purpose("Serve the application on the Oriel development server");
2413
+ kernel.command(
2414
+ "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}",
2415
+ function() {
2416
+ const out = io(this);
2417
+ if (!app.bound(Router)) {
2418
+ out.error("The router is not available. Is the Routing service provider registered?");
2419
+ return 1;
2420
+ }
2421
+ const all = routeListRows(app.make(Router).getRoutes());
2422
+ if (all.length === 0) {
2423
+ out.error("Your application doesn't have any routes.");
2424
+ return 0;
2425
+ }
2426
+ const rows = filterRouteList(all, {
2427
+ ...out.option("method") !== void 0 && { method: out.option("method") },
2428
+ ...out.option("name") !== void 0 && { name: out.option("name") },
2429
+ ...out.option("path") !== void 0 && { path: out.option("path") },
2430
+ ...out.option("except-path") !== void 0 && { exceptPath: out.option("except-path") }
2431
+ });
2432
+ if (rows.length === 0) {
2433
+ out.error("Your application doesn't have any routes matching the given criteria.");
2434
+ return 0;
2435
+ }
2436
+ if (out.flag("reverse")) rows.reverse();
2437
+ if (out.flag("json")) {
2438
+ out.line(JSON.stringify(rows));
2439
+ return 0;
2440
+ }
2441
+ for (const line of formatRouteList(rows, terminalWidth())) out.line(line);
2442
+ return 0;
2443
+ }
2444
+ ).purpose("List all registered routes");
2445
+ kernel.command("install:api {--force : Overwrite any existing API routes file}", async function() {
2446
+ const out = io(this);
2447
+ const routesFile = app.basePath("routes/api.ts");
2448
+ if (existsSync4(routesFile) && !out.flag("force")) {
2449
+ out.error("API routes file already exists.");
2450
+ } else {
2451
+ await mkdir(dirname(routesFile), { recursive: true });
2452
+ await writeFile(routesFile, API_ROUTES_STUB);
2453
+ out.info("Published API routes file.");
2454
+ }
2455
+ const bootstrapFile = app.bootstrapPath("app.ts");
2456
+ const source = existsSync4(bootstrapFile) ? await readFile2(bootstrapFile, "utf8") : void 0;
2457
+ const updated = source === void 0 ? void 0 : addApiRouting(source);
2458
+ if (updated === void 0) {
2459
+ out.warn(
2460
+ "Unable to automatically add API route definition to bootstrap file. Add api: 'routes/api.ts' to withRouting() in bootstrap/app.ts."
2461
+ );
2462
+ } else if (updated !== source) {
2463
+ await writeFile(bootstrapFile, updated);
2464
+ }
2465
+ out.info("API scaffolding installed. Run [oriel migrate] to create the personal access tokens table.");
2466
+ return 0;
2467
+ }).purpose("Create an API routes file and prepare the application for API authentication");
2468
+ }
2469
+ var HttpServiceProvider = class extends ServiceProvider {
2470
+ register() {
2471
+ this.mergeConfigFrom(
2472
+ {
2473
+ trusted_proxies: [],
2474
+ max_body_size: 8 * 1024 * 1024,
2475
+ max_json_depth: 512,
2476
+ csrf_except: [],
2477
+ redirect_guests_to: void 0,
2478
+ redirect_users_to: void 0
2479
+ },
2480
+ "http"
2481
+ );
2482
+ this.app.singleton(HttpKernel, () => new Kernel(this.app));
2483
+ this.app.singleton(ExceptionHandler, () => {
2484
+ const handler = new ErrorHandler(this.app);
2485
+ applyExceptionCallbacks(this.app, handler);
2486
+ return handler;
2487
+ });
2488
+ this.app.singleton(ResponseFactory, () => new HttpResponseFactory());
2489
+ this.app.singleton(Redirector, () => new HttpRedirector());
589
2490
  }
590
- };
591
- var ClosureKernel = class {
592
- routes = /* @__PURE__ */ new Map();
593
- on(method, path, handler) {
594
- this.routes.set(`${method.toUpperCase()} ${path}`, handler);
595
- return this;
596
- }
597
- async handle(request) {
598
- const handler = this.routes.get(`${request.method} ${new URL(request.url).pathname}`);
599
- if (!handler) return new Response("Not Found", { status: 404, headers: { "content-type": "text/plain; charset=utf-8" } });
600
- return toResponse(await handler(request));
2491
+ boot() {
2492
+ this.app.booted(() => {
2493
+ const kernel = this.app.make(HttpKernel);
2494
+ if (kernel instanceof Kernel && this.app.bound(Router)) applyMiddlewareCallbacks(this.app, kernel);
2495
+ });
2496
+ if (this.app.bound(ConsoleKernel)) registerHttpCommands(this.app.make(ConsoleKernel), this.app);
601
2497
  }
602
2498
  };
603
- function toResponse(output) {
604
- if (output instanceof Response) return output;
605
- if (output === void 0 || output === null) return new Response(null, { status: 204 });
606
- if (typeof output === "string") return new Response(output, { headers: { "content-type": "text/html; charset=utf-8" } });
607
- return Response.json(output);
608
- }
609
2499
 
610
2500
  // ../Live/dist/index.js
611
2501
  import { AssertionError } from "assert";
612
2502
  import { isDeepStrictEqual } from "util";
613
- import { AsyncLocalStorage } from "async_hooks";
2503
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
614
2504
  import { randomBytes as randomBytes2 } from "crypto";
615
2505
  import { createHmac, timingSafeEqual } from "crypto";
616
- import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
617
- import { join as join5, relative, sep } from "path";
2506
+ import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
2507
+ import { join as join5, relative as relative2, sep as sep2 } from "path";
618
2508
  import { pathToFileURL as pathToFileURL2 } from "url";
2509
+ import { AsyncLocalStorage } from "async_hooks";
619
2510
  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";
2511
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
2512
+ import { dirname as dirname2, join as join23, relative as relative22, sep as sep22 } from "path";
622
2513
  Symbol.metadata ??= /* @__PURE__ */ Symbol.for("Symbol.metadata");
623
2514
  var LISTENERS = /* @__PURE__ */ Symbol.for("oriel:live.listeners");
624
2515
  var LOCKED = /* @__PURE__ */ Symbol.for("oriel:live.locked");
@@ -771,9 +2662,9 @@ var Component = class {
771
2662
  return new PendingEvent(event);
772
2663
  }
773
2664
  /** Sends the browser to `url` after this request; `navigate` uses o:navigate instead of a full page load. */
774
- redirect(url, options = {}) {
2665
+ redirect(url2, options = {}) {
775
2666
  const state = stateOf(this);
776
- state.redirect = url;
2667
+ state.redirect = url2;
777
2668
  if (options.navigate === true) state.redirectUsingNavigate = true;
778
2669
  else delete state.redirectUsingNavigate;
779
2670
  }
@@ -825,6 +2716,20 @@ var Component = class {
825
2716
  return inject(LiveManager).page(this);
826
2717
  }
827
2718
  };
2719
+ function trackFlash(session, { saved = true } = {}) {
2720
+ if (session === void 0) return () => void 0;
2721
+ const before = new Set(session.get("_flash.new", []));
2722
+ return (redirected) => {
2723
+ if (redirected) return;
2724
+ const flashed = session.get("_flash.new", []);
2725
+ const fresh = flashed.filter((key) => !before.has(key));
2726
+ if (fresh.length === 0) return;
2727
+ session.put("_flash.new", flashed.filter((key) => before.has(key)));
2728
+ if (!saved) return session.forget(...fresh);
2729
+ const old = session.get("_flash.old", []);
2730
+ session.put("_flash.old", [...old, ...fresh.filter((key) => !old.includes(key))]);
2731
+ };
2732
+ }
828
2733
  var LiveError = class extends Error {
829
2734
  name = "LiveError";
830
2735
  kind = "Server";
@@ -884,14 +2789,14 @@ function rootTag(html, component) {
884
2789
  let rootEnd = -1;
885
2790
  TOKEN.lastIndex = 0;
886
2791
  for (let match = TOKEN.exec(source); match !== null; match = TOKEN.exec(source)) {
887
- const [token, closing, opening, selfClosing] = match;
2792
+ const [token2, closing, opening, selfClosing] = match;
888
2793
  if (root === void 0 && match.index !== 0) fail22();
889
2794
  if (depth === 0 && root !== void 0) fail22();
890
2795
  if (opening !== void 0) {
891
2796
  const tag = opening.toLowerCase();
892
2797
  root ??= tag;
893
2798
  if (selfClosing === "/" || VOID.has(tag)) {
894
- if (depth === 0) rootEnd = match.index + token.length;
2799
+ if (depth === 0) rootEnd = match.index + token2.length;
895
2800
  continue;
896
2801
  }
897
2802
  if (RAW_TEXT.has(tag)) {
@@ -903,7 +2808,7 @@ function rootTag(html, component) {
903
2808
  depth++;
904
2809
  } else if (closing !== void 0) {
905
2810
  if (--depth < 0) fail22();
906
- if (depth === 0) rootEnd = match.index + token.length;
2811
+ if (depth === 0) rootEnd = match.index + token2.length;
907
2812
  }
908
2813
  }
909
2814
  if (root === void 0 || depth !== 0 || source.slice(rootEnd).trim() !== "") fail22();
@@ -920,23 +2825,40 @@ var fail = (message, actual, expected) => {
920
2825
  throw new AssertionError({ message, actual, expected, operator: "live" });
921
2826
  };
922
2827
  var getPath = (target2, path) => path.split(".").reduce((node, segment) => typeof node === "object" && node !== null ? node[segment] : void 0, target2);
2828
+ var TestRequestScope = token("Testing.RequestScope");
2829
+ function requestScope2() {
2830
+ const container = currentContainer() ?? Container.getInstance();
2831
+ return container?.bound(TestRequestScope) === true ? container.make(TestRequestScope) : (run) => run();
2832
+ }
2833
+ function currentSession() {
2834
+ const container = currentContainer();
2835
+ if (container?.bound(Session) !== true) return void 0;
2836
+ try {
2837
+ return container.make(Session);
2838
+ } catch {
2839
+ return void 0;
2840
+ }
2841
+ }
923
2842
  var Testable = class _Testable {
924
- constructor(manager, result) {
2843
+ constructor(manager, scope, result) {
925
2844
  this.manager = manager;
2845
+ this.scope = scope;
926
2846
  this.#snapshot = JSON.stringify(result.snapshot);
927
2847
  this.#html = result.html ?? "";
928
2848
  this.#effects = result.effects;
929
2849
  this.#instance = result.instance;
930
2850
  }
931
2851
  manager;
2852
+ scope;
932
2853
  #snapshot;
933
2854
  #html;
934
2855
  #effects;
935
2856
  #instance;
936
2857
  /** Mounts `component` with `params`, as `<Live component={…} {...params} />` would. */
937
2858
  static async create(component, params = {}) {
2859
+ const scope = requestScope2();
938
2860
  const manager = inject(LiveManager);
939
- return new _Testable(manager, await manager.mountComponent(component, params));
2861
+ return new _Testable(manager, scope, await scope(() => manager.mountComponent(component, params)));
940
2862
  }
941
2863
  /** The component instance after the last request. */
942
2864
  instance() {
@@ -971,7 +2893,12 @@ var Testable = class _Testable {
971
2893
  return this.#update({}, [{ method: "$refresh", params: [] }]);
972
2894
  }
973
2895
  async #update(updates, calls) {
974
- const result = await this.manager.updateComponent(this.#snapshot, updates, calls);
2896
+ const result = await this.scope(async () => {
2897
+ const flashed = trackFlash(currentSession(), { saved: false });
2898
+ const updated = await this.manager.updateComponent(this.#snapshot, updates, calls);
2899
+ flashed(updated.effects.redirect !== void 0);
2900
+ return updated;
2901
+ });
975
2902
  this.#snapshot = JSON.stringify(result.snapshot);
976
2903
  if (result.html !== void 0) this.#html = result.html;
977
2904
  this.#effects = result.effects;
@@ -1029,10 +2956,10 @@ var Testable = class _Testable {
1029
2956
  if (found.length > 0) fail(`Component has errors: "${found.join('", "')}"`, errors);
1030
2957
  return this;
1031
2958
  }
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);
2959
+ assertRedirect(url2) {
2960
+ const redirect2 = this.#effects.redirect;
2961
+ if (redirect2 === void 0) fail("Component did not perform a redirect.");
2962
+ if (url2 !== void 0 && redirect2 !== url2) fail("Component redirected to an unexpected URL.", redirect2, url2);
1036
2963
  return this;
1037
2964
  }
1038
2965
  assertNoRedirect() {
@@ -1088,8 +3015,8 @@ function defaultName(cls) {
1088
3015
  return typeof name === "string" && name !== "" ? Str.kebab(name) : void 0;
1089
3016
  }
1090
3017
  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();
3018
+ if (!existsSync5(directory)) return [];
3019
+ 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
3020
  }
1094
3021
  async function discover(directory) {
1095
3022
  const found = [];
@@ -1098,16 +3025,132 @@ async function discover(directory) {
1098
3025
  /* @vite-ignore */
1099
3026
  pathToFileURL2(join5(directory, file)).href
1100
3027
  );
1101
- found.push([componentName(relative(directory, join5(directory, file))), module.default]);
3028
+ found.push([componentName(relative2(directory, join5(directory, file))), module.default]);
1102
3029
  }
1103
3030
  return found;
1104
3031
  }
3032
+ var PERSISTENT_MIDDLEWARE = ["auth", "verified", "guest", "can"];
3033
+ var ROUTER_METHODS = ["getRoutesByMatchOrder", "getPatterns", "getPatternVersion", "getMiddleware", "getMiddlewareGroups", "substituteBindings"];
3034
+ function canMatch(router) {
3035
+ const methods = router;
3036
+ return ROUTER_METHODS.every((name) => typeof methods[name] === "function");
3037
+ }
3038
+ function isMatchingRoute(route2) {
3039
+ const candidate = route2;
3040
+ return typeof candidate === "object" && candidate !== null && Array.isArray(candidate.methods) && ["match", "gatherMiddleware", "excludedMiddleware"].every((name) => typeof candidate[name] === "function");
3041
+ }
3042
+ function routePath(path) {
3043
+ const trimmed = path.replace(/^\/+|\/+$/g, "").replace(/\/{2,}/g, "/");
3044
+ return trimmed === "" ? "" : `/${trimmed}`;
3045
+ }
3046
+ function expand(references, aliases, groups, out = [], visiting = /* @__PURE__ */ new Set()) {
3047
+ for (const reference of references) {
3048
+ if (typeof reference === "function") {
3049
+ out.push({ cls: reference, parameters: [] });
3050
+ continue;
3051
+ }
3052
+ const index = reference.indexOf(":");
3053
+ const name = index === -1 ? reference : reference.slice(0, index);
3054
+ if (index === -1 && Object.hasOwn(groups, name)) {
3055
+ if (visiting.has(name)) continue;
3056
+ visiting.add(name);
3057
+ expand(groups[name], aliases, groups, out, visiting);
3058
+ visiting.delete(name);
3059
+ } else if (Object.hasOwn(aliases, name)) {
3060
+ out.push({ cls: aliases[name], parameters: index === -1 ? [] : reference.slice(index + 1).split(",") });
3061
+ }
3062
+ }
3063
+ return out;
3064
+ }
3065
+ function persistentPass(router, persistent, method, path) {
3066
+ if (!canMatch(router)) throw new LiveError("Live re-applies the page route's middleware to updates, which needs Oriel's router.");
3067
+ const routes = router.getRoutesByMatchOrder();
3068
+ 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.");
3069
+ const verb = method.toUpperCase();
3070
+ const target2 = routePath(path);
3071
+ const patterns = router.getPatterns();
3072
+ const version = router.getPatternVersion();
3073
+ let matched;
3074
+ for (const candidate of routes) {
3075
+ const values2 = candidate.methods.includes(verb) ? candidate.match(target2, patterns, version) : void 0;
3076
+ if (values2 !== void 0) {
3077
+ matched = { route: candidate, values: values2 };
3078
+ break;
3079
+ }
3080
+ }
3081
+ if (matched === void 0) return void 0;
3082
+ const { route: route2, values } = matched;
3083
+ const aliases = router.getMiddleware();
3084
+ const groups = router.getMiddlewareGroups();
3085
+ const wanted = new Set(persistent.flatMap((name) => Object.hasOwn(aliases, name) ? [aliases[name]] : []));
3086
+ const excluded = new Set(expand(route2.excludedMiddleware(), aliases, groups).map((entry) => entry.cls));
3087
+ const ids = /* @__PURE__ */ new Map();
3088
+ const seen = /* @__PURE__ */ new Set();
3089
+ const middleware = expand(route2.gatherMiddleware(), aliases, groups).filter((entry) => {
3090
+ if (!ids.has(entry.cls)) ids.set(entry.cls, ids.size);
3091
+ const key = `${ids.get(entry.cls)}:${entry.parameters.join(",")}`;
3092
+ if (!wanted.has(entry.cls) || excluded.has(entry.cls) || seen.has(key)) return false;
3093
+ seen.add(key);
3094
+ return true;
3095
+ });
3096
+ const early = new Set(expand(["web", "api"], aliases, groups).map((entry) => entry.cls));
3097
+ for (const name of ["auth", "guest"]) if (Object.hasOwn(aliases, name)) early.add(aliases[name]);
3098
+ const before = middleware.filter((entry) => early.has(entry.cls));
3099
+ const after = middleware.filter((entry) => !early.has(entry.cls));
3100
+ return { router, route: route2, values, middleware: [...before, ...after], at: before.length };
3101
+ }
3102
+ function persistentMiddleware(router, persistent, method, path) {
3103
+ return [...persistentPass(router, persistent, method, path)?.middleware ?? []];
3104
+ }
3105
+ function currentRoute(container) {
3106
+ if (!container.bound(CurrentRoute)) return void 0;
3107
+ try {
3108
+ return container.make(CurrentRoute);
3109
+ } catch {
3110
+ return void 0;
3111
+ }
3112
+ }
3113
+ async function throughMiddleware(container, request2, pass, destination) {
3114
+ const original = currentRoute(container);
3115
+ const page = { route: pass.route, parameters: { ...pass.values }, middleware: [] };
3116
+ let swapped = false;
3117
+ const show = () => {
3118
+ container.instance(CurrentRoute, page);
3119
+ swapped = true;
3120
+ };
3121
+ const restore = () => {
3122
+ if (!swapped) return;
3123
+ if (original === void 0) container.forgetInstance(CurrentRoute);
3124
+ else container.instance(CurrentRoute, original);
3125
+ swapped = false;
3126
+ };
3127
+ let ran;
3128
+ const run = async (index) => {
3129
+ if (index === pass.at) Object.assign(page.parameters, await pass.router.substituteBindings(pass.route, pass.values, container));
3130
+ if (index === pass.middleware.length) {
3131
+ restore();
3132
+ ran = { result: await destination() };
3133
+ return new Response(null, { status: 204 });
3134
+ }
3135
+ const { cls, parameters } = pass.middleware[index];
3136
+ const instance = container.make(cls);
3137
+ const next = () => run(index + 1);
3138
+ return instance.handle(request2, next, ...parameters);
3139
+ };
3140
+ show();
3141
+ try {
3142
+ const response2 = await run(0);
3143
+ return ran ? ran.result : response2;
3144
+ } finally {
3145
+ restore();
3146
+ }
3147
+ }
1105
3148
  function isModel(value) {
1106
3149
  if (typeof value !== "object" || value === null) return false;
1107
3150
  const candidate = value;
1108
3151
  return typeof candidate.getKey === "function" && typeof candidate.getKeyName === "function" && typeof candidate.getTable === "function" && typeof candidate.exists === "boolean";
1109
3152
  }
1110
- var isPlainObject = (value) => {
3153
+ var isPlainObject2 = (value) => {
1111
3154
  const proto = Object.getPrototypeOf(value);
1112
3155
  return proto === Object.prototype || proto === null;
1113
3156
  };
@@ -1154,7 +3197,7 @@ function dehydrate(value, registry, path, seen = /* @__PURE__ */ new Set()) {
1154
3197
  }
1155
3198
  return [items.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), { s: "clctn" }];
1156
3199
  }
1157
- if (isPlainObject(object)) {
3200
+ if (isPlainObject2(object)) {
1158
3201
  const out = {};
1159
3202
  for (const [key, item] of Object.entries(object)) out[key] = dehydrate(item, registry, `${path}.${key}`, seen);
1160
3203
  return [out, { s: "obj" }];
@@ -1240,11 +3283,68 @@ function isUpdatable(instance, path) {
1240
3283
  }
1241
3284
  return !isModel(node) && !(node instanceof Collection);
1242
3285
  }
1243
- var renderContext = new AsyncLocalStorage();
1244
- function optional(container, token) {
1245
- if (!container.bound(token)) return void 0;
3286
+ var PAGINATED = /* @__PURE__ */ Symbol.for("oriel:live.pagination");
3287
+ function toPage(value) {
3288
+ const page = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : Number.NaN;
3289
+ return Number.isFinite(page) && page >= 1 ? Math.min(Math.floor(page), Number.MAX_SAFE_INTEGER) : 1;
3290
+ }
3291
+ async function hook(instance, name, ...args) {
3292
+ const method = instance[name];
3293
+ if (typeof method === "function") await method.apply(instance, args);
3294
+ }
3295
+ function WithPagination(Base) {
3296
+ class WithPaginationMixin extends Base {
3297
+ page = 1;
3298
+ nextPage() {
3299
+ return this.setPage(toPage(this.page) + 1);
3300
+ }
3301
+ previousPage() {
3302
+ return this.setPage(Math.max(toPage(this.page) - 1, 1));
3303
+ }
3304
+ gotoPage(page) {
3305
+ return this.setPage(page);
3306
+ }
3307
+ resetPage() {
3308
+ return this.setPage(1);
3309
+ }
3310
+ async setPage(page) {
3311
+ const next = toPage(page);
3312
+ await hook(this, "updatingPage", next);
3313
+ this.page = next;
3314
+ await hook(this, "updatedPage", next);
3315
+ }
3316
+ }
3317
+ Object.defineProperty(WithPaginationMixin.prototype, PAGINATED, { value: true });
3318
+ return WithPaginationMixin;
3319
+ }
3320
+ function isPaginated(component) {
3321
+ return component[PAGINATED] === true;
3322
+ }
3323
+ var rendering = new AsyncLocalStorage();
3324
+ function renderPaginated(component, url2, run) {
3325
+ return rendering.run(isPaginated(component) ? { component, url: url2 } : void 0, run);
3326
+ }
3327
+ function outsidePagination(run) {
3328
+ return rendering.run(void 0, run);
3329
+ }
3330
+ var livePaginationState = {
3331
+ currentPage(pageName) {
3332
+ const current = rendering.getStore();
3333
+ if (current === void 0 || pageName !== "page") return void 0;
3334
+ const page = toPage(current.component.page);
3335
+ current.component.page = page;
3336
+ return page;
3337
+ },
3338
+ currentPath() {
3339
+ return rendering.getStore()?.url;
3340
+ }
3341
+ };
3342
+ var renderContext = new AsyncLocalStorage2();
3343
+ var pageRequest = new AsyncLocalStorage2();
3344
+ function optional(container, token2) {
3345
+ if (!container.bound(token2)) return void 0;
1246
3346
  try {
1247
- return container.make(token);
3347
+ return container.make(token2);
1248
3348
  } catch {
1249
3349
  return void 0;
1250
3350
  }
@@ -1253,7 +3353,7 @@ var isRecord = (value) => typeof value === "object" && value !== null && !Array.
1253
3353
  function setOwn(object, key, value) {
1254
3354
  Object.defineProperty(object, key, { value, enumerable: true, writable: true, configurable: true });
1255
3355
  }
1256
- async function hook(instance, name, ...args) {
3356
+ async function hook2(instance, name, ...args) {
1257
3357
  const method = instance[name];
1258
3358
  if (typeof method === "function") await method.apply(instance, args);
1259
3359
  }
@@ -1291,8 +3391,8 @@ var LiveComponentManager = class {
1291
3391
  if (!isComponentClass(cls)) throw new LiveComponentNotFoundError(name);
1292
3392
  return cls;
1293
3393
  }
1294
- isLiveRequest(request) {
1295
- return request.header("X-Oriel-Live") === "true";
3394
+ isLiveRequest(request2) {
3395
+ return request2.header("X-Oriel-Live") === "true";
1296
3396
  }
1297
3397
  /** The first render: HTML with o:id, o:snapshot and o:effects on the root element. */
1298
3398
  async mount(component, params = {}, key) {
@@ -1304,7 +3404,7 @@ var LiveComponentManager = class {
1304
3404
  setOwn(context.children, childKey, existing);
1305
3405
  return `<${existing[0]} o:id="${escapeAttribute2(existing[1])}"></${existing[0]}>`;
1306
3406
  }
1307
- const result = await this.mountComponent(component, params);
3407
+ const result = await outsidePagination(() => this.mountComponent(component, params));
1308
3408
  setOwn(context.children, childKey, [result.tag, result.snapshot.memo.id]);
1309
3409
  return result.html;
1310
3410
  }
@@ -1316,14 +3416,15 @@ var LiveComponentManager = class {
1316
3416
  const state = stateOf(instance);
1317
3417
  state.id = randomBytes2(15).toString("base64url");
1318
3418
  state.name = name;
1319
- await hook(instance, "boot");
3419
+ await hook2(instance, "boot");
1320
3420
  for (const [property, value] of Object.entries(params)) {
1321
3421
  if (Object.hasOwn(instance, property)) instance[property] = value;
1322
3422
  }
1323
3423
  this.prepareComputed(instance);
1324
- 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: {} };
3424
+ const request2 = optional(this.container(), HttpRequest);
3425
+ const page = pageRequest.getStore() ?? { path: request2?.path() ?? "", method: request2?.method() ?? "GET" };
3426
+ await renderPaginated(instance, this.pageUrl(page.path), () => hook2(instance, "mount", params));
3427
+ const memo = { id: state.id, name, path: page.path, method: page.method, children: {}, errors: {} };
1327
3428
  const { html, tag } = await this.render(instance, memo, {});
1328
3429
  const effects = this.effects(instance);
1329
3430
  const listeners = Object.keys(listenersOf(cls));
@@ -1333,10 +3434,11 @@ var LiveComponentManager = class {
1333
3434
  return { instance, snapshot, effects, html: insertAttributesIntoRoot(html, attributes), tag };
1334
3435
  }
1335
3436
  /** Applies each component's updates and calls, re-renders it and returns its new snapshot and effects. */
1336
- async update(request) {
3437
+ async update(request2) {
1337
3438
  const components = [];
1338
- for (const component of request.components) {
1339
- const result = await this.updateComponent(component.snapshot, component.updates, component.calls);
3439
+ for (const component of request2.components) {
3440
+ const { memo } = this.verify(component.snapshot);
3441
+ const result = await pageRequest.run({ path: memo.path, method: memo.method }, () => this.updateComponent(component.snapshot, component.updates, component.calls));
1340
3442
  components.push({ snapshot: JSON.stringify(result.snapshot), effects: result.effects });
1341
3443
  }
1342
3444
  return { components };
@@ -1349,22 +3451,24 @@ var LiveComponentManager = class {
1349
3451
  state.id = snapshot.memo.id;
1350
3452
  state.name = snapshot.memo.name;
1351
3453
  state.errors = { ...snapshot.memo.errors };
1352
- await hook(instance, "boot");
3454
+ await hook2(instance, "boot");
1353
3455
  const registry = this.registry();
1354
3456
  for (const [property, value] of Object.entries(snapshot.data)) {
1355
3457
  Object.defineProperty(instance, property, { value: await hydrate(value, registry), enumerable: true, writable: true, configurable: true });
1356
3458
  }
1357
3459
  this.prepareComputed(instance);
1358
- await hook(instance, "hydrate");
1359
3460
  const returns = [];
1360
- try {
1361
- for (const [path, value] of Object.entries(updates)) await this.applyUpdate(instance, path, value);
1362
- for (const call of calls) returns.push(await this.call(instance, call.method, call.params));
1363
- } catch (error) {
1364
- const errors = error.errors;
1365
- if (error.kind !== "Validation" || !isRecord(errors)) throw error;
1366
- state.errors = { ...errors };
1367
- }
3461
+ await renderPaginated(instance, this.pageUrl(snapshot.memo.path), async () => {
3462
+ await hook2(instance, "hydrate");
3463
+ try {
3464
+ for (const [path, value] of Object.entries(updates)) await this.applyUpdate(instance, path, value);
3465
+ for (const call of calls) returns.push(await this.call(instance, call.method, call.params));
3466
+ } catch (error) {
3467
+ const errors = error.errors;
3468
+ if (error.kind !== "Validation" || !isRecord(errors)) throw error;
3469
+ state.errors = { ...errors };
3470
+ }
3471
+ });
1368
3472
  const memo = { ...snapshot.memo, children: { ...snapshot.memo.children } };
1369
3473
  const effects = this.effects(instance);
1370
3474
  let html;
@@ -1378,10 +3482,32 @@ var LiveComponentManager = class {
1378
3482
  if (calls.length > 0) effects.returns = returns;
1379
3483
  return { instance, snapshot: await this.snapshot(instance, memo), effects, html, tag };
1380
3484
  }
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));
3485
+ /**
3486
+ * The route action of POST /oriel/live/update. Each component's update runs behind the persistent middleware of the
3487
+ * route that rendered its page (Livewire's): `auth`, `verified`, `guest` and `can`, with the page route's parameters
3488
+ * bound again. A signed-out tab gets a 401, a revoked ability a 403 and a deleted model a 404, before the component
3489
+ * hydrates. When a middleware answers instead (a redirect), that is the response. A key first flashed during the
3490
+ * update shows in this response only, unless the update redirects.
3491
+ */
3492
+ async handle(request2) {
3493
+ if (!this.isLiveRequest(request2)) throw new InvalidLiveRequestError("Live update requests need the X-Oriel-Live header.");
3494
+ const container = this.container();
3495
+ const persistent = optional(container, ConfigRepository)?.get("live.persistent_middleware", PERSISTENT_MIDDLEWARE) ?? PERSISTENT_MIDDLEWARE;
3496
+ const components = [];
3497
+ const flashed = trackFlash(optional(container, Session));
3498
+ for (const component of this.payload(request2).components) {
3499
+ const { memo } = this.verify(component.snapshot);
3500
+ const pass = persistentPass(container.make(Router), persistent, memo.method, memo.path);
3501
+ const update = () => this.updateComponent(component.snapshot, component.updates, component.calls);
3502
+ const result = await pageRequest.run(
3503
+ { path: memo.path, method: memo.method },
3504
+ () => pass === void 0 || pass.middleware.length === 0 ? update() : throughMiddleware(container, request2, pass, update)
3505
+ );
3506
+ if (result instanceof Response) return result;
3507
+ components.push({ snapshot: JSON.stringify(result.snapshot), effects: result.effects });
3508
+ }
3509
+ flashed(components.some((component) => component.effects.redirect !== void 0));
3510
+ return { components };
1385
3511
  }
1386
3512
  /** A full-page component (Route.get('/counter', Counter)): mounted with the route parameters, inside the layout. */
1387
3513
  async page(component) {
@@ -1389,9 +3515,9 @@ var LiveComponentManager = class {
1389
3515
  const params = { ...optional(container, CurrentRoute)?.parameters ?? {} };
1390
3516
  const cls = component.constructor;
1391
3517
  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 } });
3518
+ const redirect2 = result.effects.redirect;
3519
+ if (redirect2 !== void 0) {
3520
+ return optional(container, Redirector)?.to(redirect2) ?? new Response(null, { status: 302, headers: { Location: redirect2 } });
1395
3521
  }
1396
3522
  const layout = cls.layout ?? optional(container, ConfigRepository)?.get("live.layout") ?? "components.layouts.app";
1397
3523
  const html = result.html;
@@ -1424,11 +3550,11 @@ var LiveComponentManager = class {
1424
3550
  return this.container().make(Encrypter).getKey();
1425
3551
  }
1426
3552
  /** The request body, read from the raw JSON when the request has it, so TrimStrings can't alter typed values. */
1427
- payload(request) {
3553
+ payload(request2) {
1428
3554
  let body;
1429
3555
  try {
1430
- const content = request.content;
1431
- body = typeof content === "function" ? JSON.parse(content.call(request)) : request.all();
3556
+ const content = request2.content;
3557
+ body = typeof content === "function" ? JSON.parse(content.call(request2)) : request2.all();
1432
3558
  } catch {
1433
3559
  throw new InvalidLiveRequestError("The Live update request body is not valid JSON.");
1434
3560
  }
@@ -1455,8 +3581,8 @@ var LiveComponentManager = class {
1455
3581
  const [root, ...rest] = path.split(".");
1456
3582
  const key = rest.length > 0 ? rest.join(".") : void 0;
1457
3583
  const studly = Str.studly(root);
1458
- await hook(instance, "updating", path, value);
1459
- await hook(instance, `updating${studly}`, value, key);
3584
+ await hook2(instance, "updating", path, value);
3585
+ await hook2(instance, `updating${studly}`, value, key);
1460
3586
  const target2 = instance;
1461
3587
  if (rest.length === 0) target2[root] = value;
1462
3588
  else {
@@ -1474,8 +3600,8 @@ var LiveComponentManager = class {
1474
3600
  assertSlot(node, last);
1475
3601
  Object.defineProperty(node, last, { value, enumerable: true, writable: true, configurable: true });
1476
3602
  }
1477
- await hook(instance, "updated", path, value);
1478
- await hook(instance, `updated${studly}`, value, key);
3603
+ await hook2(instance, "updated", path, value);
3604
+ await hook2(instance, `updated${studly}`, value, key);
1479
3605
  }
1480
3606
  /** Runs one call from the browser: a magic action, an event, or a public method. Returns its return value. */
1481
3607
  async call(instance, method, params) {
@@ -1503,18 +3629,35 @@ var LiveComponentManager = class {
1503
3629
  if (!isCallable(instance, method)) throw new MethodNotCallableError(method, name);
1504
3630
  return instance[method](...params);
1505
3631
  }
1506
- /** Renders the component (rendering/rendered hooks, nested children tracked) and checks for a single root. */
3632
+ /**
3633
+ * Renders the component (rendering/rendered hooks, nested children tracked) and checks for a single root. A
3634
+ * WithPagination component's page and page URL are what paginate() reads while it renders, as in its mount() and in
3635
+ * its update's hooks and actions.
3636
+ */
1507
3637
  async render(instance, memo, previous) {
1508
- await hook(instance, "rendering");
3638
+ await hook2(instance, "rendering");
1509
3639
  const context = { previous, children: {}, index: 0 };
1510
- const html = await renderContext.run(context, async () => {
1511
- const output = await instance.render();
1512
- return typeof output === "string" ? output : this.container().make(ViewFactory).renderToString(output);
1513
- });
1514
- await hook(instance, "rendered", html);
3640
+ const html = await renderContext.run(
3641
+ context,
3642
+ () => renderPaginated(instance, this.pageUrl(memo.path), async () => {
3643
+ const output = await instance.render();
3644
+ return typeof output === "string" ? output : this.container().make(ViewFactory).renderToString(output);
3645
+ })
3646
+ );
3647
+ await hook2(instance, "rendered", html);
1515
3648
  memo.children = context.children;
1516
3649
  return { html: html.trim(), tag: rootTag(html, memo.name) };
1517
3650
  }
3651
+ /** The absolute URL of the page a memo path belongs to, on the current request's origin ('/' + path without one). */
3652
+ pageUrl(path) {
3653
+ const relative3 = `/${path.replace(/^\/+/, "")}`;
3654
+ const url2 = optional(this.container(), HttpRequest)?.url();
3655
+ try {
3656
+ return url2 === void 0 ? relative3 : new URL(relative3, url2).href;
3657
+ } catch {
3658
+ return relative3;
3659
+ }
3660
+ }
1518
3661
  /** Dispatches, redirects: the effects every response carries. */
1519
3662
  effects(instance) {
1520
3663
  const state = stateOf(instance);
@@ -1533,7 +3676,7 @@ var LiveComponentManager = class {
1533
3676
  }
1534
3677
  /** dehydrate() hook, then the state as snapshot data, the error bag, and the checksum over both. */
1535
3678
  async snapshot(instance, memo) {
1536
- await hook(instance, "dehydrate");
3679
+ await hook2(instance, "dehydrate");
1537
3680
  const registry = this.registry();
1538
3681
  const data = {};
1539
3682
  for (const [property, value] of Object.entries(instance)) data[property] = dehydrate(value, registry, `${memo.name}.${property}`);
@@ -1563,7 +3706,7 @@ test('{{ name }} renders', async () => {
1563
3706
  `;
1564
3707
  var CLASS_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
1565
3708
  function importPath(from, to) {
1566
- const path = relative2(dirname(from), to).split(sep2).join("/").replace(/\.tsx?$/, "");
3709
+ const path = relative22(dirname2(from), to).split(sep22).join("/").replace(/\.tsx?$/, "");
1567
3710
  return path.startsWith(".") ? path : `./${path}`;
1568
3711
  }
1569
3712
  function registerLiveCommands(kernel, app) {
@@ -1579,23 +3722,23 @@ function registerLiveCommands(kernel, app) {
1579
3722
  const file = join23(directory, ...segments.slice(0, -1), `${className}.tsx`);
1580
3723
  const force = this.option("force") === true;
1581
3724
  if (existsSync22(file) && !force) {
1582
- this.error(`Component [${relative2(app.basePath(), file)}] already exists.`);
3725
+ this.error(`Component [${relative22(app.basePath(), file)}] already exists.`);
1583
3726
  return 1;
1584
3727
  }
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.`);
3728
+ await mkdir2(dirname2(file), { recursive: true });
3729
+ await writeFile2(file, COMPONENT_STUB.replace("{{ class }}", () => className));
3730
+ this.info(`Component [${relative22(app.basePath(), file)}] created successfully.`);
1588
3731
  if (this.option("test") === true) {
1589
3732
  const testFile = app.basePath(join23("tests", "Feature", "Live", ...segments.slice(0, -1), `${className}.test.ts`));
1590
3733
  if (existsSync22(testFile) && !force) {
1591
- this.error(`Test [${relative2(app.basePath(), testFile)}] already exists.`);
3734
+ this.error(`Test [${relative22(app.basePath(), testFile)}] already exists.`);
1592
3735
  return 1;
1593
3736
  }
1594
3737
  const name = segments.map((segment) => Str.kebab(segment)).join(".");
1595
3738
  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.`);
3739
+ await mkdir2(dirname2(testFile), { recursive: true });
3740
+ await writeFile2(testFile, test);
3741
+ this.info(`Test [${relative22(app.basePath(), testFile)}] created successfully.`);
1599
3742
  }
1600
3743
  return 0;
1601
3744
  }).purpose("Create a new Live component");
@@ -1603,9 +3746,10 @@ function registerLiveCommands(kernel, app) {
1603
3746
  var UPDATE_URI = "/oriel/live/update";
1604
3747
  var LiveServiceProvider = class extends ServiceProvider {
1605
3748
  register() {
1606
- this.mergeConfigFrom({ class_path: this.app.appPath("Live"), layout: "components.layouts.app" }, "live");
3749
+ this.mergeConfigFrom({ class_path: this.app.appPath("Live"), layout: "components.layouts.app", persistent_middleware: [...PERSISTENT_MIDDLEWARE] }, "live");
1607
3750
  this.app.singleton(LiveComponentManager, () => new LiveComponentManager(this.app));
1608
3751
  this.app.singleton(LiveManager, (container) => container.make(LiveComponentManager));
3752
+ this.app.singleton(PaginationState, () => livePaginationState);
1609
3753
  }
1610
3754
  async boot() {
1611
3755
  const manager = this.app.make(LiveComponentManager);
@@ -1619,17 +3763,52 @@ var LiveServiceProvider = class extends ServiceProvider {
1619
3763
  }
1620
3764
  }
1621
3765
  };
3766
+ function urlWindow(current, last, onEachSide) {
3767
+ const range = (from, to) => Array.from({ length: Math.max(to - from + 1, 0) }, (_, index) => from + index);
3768
+ if (last < onEachSide * 2 + 8) return range(1, last);
3769
+ const size = onEachSide + 4;
3770
+ if (current <= size) return [...range(1, size + onEachSide), "...", ...range(last - 1, last)];
3771
+ if (current > last - size) return [...range(1, 2), "...", ...range(last - (size + (onEachSide - 1)), last)];
3772
+ return [...range(1, 2), "...", ...range(current - onEachSide, current + onEachSide), "...", ...range(last - 1, last)];
3773
+ }
3774
+ function button(label, action, attributes = "") {
3775
+ const live2 = action === void 0 ? " disabled" : ` o:click="${escapeAttribute2(action)}"`;
3776
+ return `<button type="button" class="btn btn-secondary"${attributes}${live2}>${escapeAttribute2(label)}</button>`;
3777
+ }
3778
+ function Pagination({ paginator, class: className }) {
3779
+ if (typeof paginator.nextCursor === "function") {
3780
+ throw new LiveError("<Pagination> needs paginate() or simplePaginate(): a cursor paginator has no page numbers.");
3781
+ }
3782
+ const current = paginator.currentPage();
3783
+ const onFirst = paginator.onFirstPage();
3784
+ const hasMore = paginator.hasMorePages();
3785
+ if (onFirst && !hasMore) return { toHtml: () => "" };
3786
+ const parts = [button("\xAB Previous", onFirst ? void 0 : "previousPage")];
3787
+ if (typeof paginator.lastPage === "function") {
3788
+ const onEachSide = Number.isInteger(paginator.onEachSideCount) && paginator.onEachSideCount >= 0 ? paginator.onEachSideCount : 3;
3789
+ for (const page of urlWindow(current, paginator.lastPage(), onEachSide)) {
3790
+ if (page === "...") parts.push('<span aria-hidden="true">...</span>');
3791
+ else if (page === current) parts.push(`<button type="button" class="btn" aria-current="page" disabled>${page}</button>`);
3792
+ else parts.push(button(String(page), `gotoPage(${page})`, ` aria-label="Go to page ${page}"`));
3793
+ }
3794
+ }
3795
+ parts.push(button("Next \xBB", hasMore ? "nextPage" : void 0));
3796
+ const classes = className === void 0 || className === "" ? "" : ` class="${escapeAttribute2(className)}"`;
3797
+ const html = `<nav aria-label="Pagination"${classes}>${parts.join("")}</nav>`;
3798
+ return { toHtml: () => html };
3799
+ }
1622
3800
 
1623
3801
  // ../Testing/dist/index.js
1624
3802
  import { AssertionError as AssertionError2 } from "assert";
1625
3803
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
1626
- import { existsSync as existsSync5 } from "fs";
3804
+ import { existsSync as existsSync6 } from "fs";
1627
3805
  import { join as join6 } from "path";
1628
3806
  import { createHmac as createHmac2 } from "crypto";
1629
3807
  import { randomUUID } from "crypto";
1630
3808
  import { randomUUID as randomUUID2 } from "crypto";
1631
3809
  import { randomUUID as randomUUID3 } from "crypto";
1632
3810
  import { randomBytes as randomBytes3 } from "crypto";
3811
+ import { posix } from "path";
1633
3812
  import { deflateSync } from "zlib";
1634
3813
  function fail2(message, details = {}) {
1635
3814
  const error = new AssertionError2({ message, actual: details.actual, expected: details.expected, operator: "fail" });
@@ -1649,7 +3828,7 @@ function format(value) {
1649
3828
  return String(value);
1650
3829
  }
1651
3830
  }
1652
- function escapeHtml(value) {
3831
+ function escapeHtml2(value) {
1653
3832
  return value.replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#039;" })[char]);
1654
3833
  }
1655
3834
  function stripTags(html) {
@@ -2162,12 +4341,12 @@ function sizeOf(value) {
2162
4341
  function toPlain(value) {
2163
4342
  return value === void 0 ? void 0 : JSON.parse(JSON.stringify(value));
2164
4343
  }
2165
- function inertiaPageOf(response) {
4344
+ function inertiaPageOf(response2) {
2166
4345
  let page;
2167
4346
  try {
2168
- if (response.headers.has("X-Inertia")) page = JSON.parse(response.content);
4347
+ if (response2.headers.has("X-Inertia")) page = JSON.parse(response2.content);
2169
4348
  else {
2170
- const match = /<script data-page="[^"]*" type="application\/json">([\s\S]*?)<\/script>/.exec(response.content);
4349
+ const match = /<script data-page="[^"]*" type="application\/json">([\s\S]*?)<\/script>/.exec(response2.content);
2171
4350
  page = match === null ? void 0 : JSON.parse(match[1]);
2172
4351
  }
2173
4352
  } catch {
@@ -2180,11 +4359,11 @@ function inertiaPageOf(response) {
2180
4359
  var AssertableInertia = class _AssertableInertia extends AssertableJson {
2181
4360
  #page;
2182
4361
  #response;
2183
- static fromTestResponse(response) {
2184
- const page = inertiaPageOf(response);
4362
+ static fromTestResponse(response2) {
4363
+ const page = inertiaPageOf(response2);
2185
4364
  const instance = new _AssertableInertia(page.props);
2186
4365
  instance.#page = page;
2187
- instance.#response = response;
4366
+ instance.#response = response2;
2188
4367
  return instance;
2189
4368
  }
2190
4369
  /** Scopes are plain AssertableJson: the page-level methods only make sense at the root. */
@@ -2204,7 +4383,7 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
2204
4383
  if (shouldExist === true || shouldExist === void 0 && config?.get("inertia.testing.ensure_pages_exist", true) !== false) {
2205
4384
  const paths = config?.get("inertia.pages.paths", []) ?? [];
2206
4385
  const extensions = config?.get("inertia.pages.extensions", []) ?? [];
2207
- const exists = paths.some((path) => extensions.some((extension) => existsSync5(join6(path, `${value}.${extension}`))));
4386
+ const exists = paths.some((path) => extensions.some((extension) => existsSync6(join6(path, `${value}.${extension}`))));
2208
4387
  if (!exists) fail2(`Inertia page component file [${value}] does not exist.`);
2209
4388
  }
2210
4389
  return this;
@@ -2253,8 +4432,8 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
2253
4432
  if (onlyList !== void 0 || exceptList !== void 0) headers["X-Inertia-Partial-Component"] = this.page.component;
2254
4433
  if (onlyList !== void 0) headers["X-Inertia-Partial-Data"] = onlyList;
2255
4434
  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);
4435
+ const response2 = await client.request("GET", this.page.url, { headers });
4436
+ const reloaded = _AssertableInertia.fromTestResponse(response2);
2258
4437
  reloaded.component(this.page.component, false).url(this.page.url).version(this.page.version);
2259
4438
  await callback?.(reloaded);
2260
4439
  return this;
@@ -2349,8 +4528,8 @@ function isExpired(cookie, now = Date.now()) {
2349
4528
  if (cookie.maxAge !== void 0 && !Number.isNaN(cookie.maxAge)) return cookie.maxAge <= 0;
2350
4529
  return cookie.expires !== void 0 && cookie.expires.getTime() <= now;
2351
4530
  }
2352
- function responseCookies(response) {
2353
- return response.headers.getSetCookie().map(parseSetCookie);
4531
+ function responseCookies(response2) {
4532
+ return response2.headers.getSetCookie().map(parseSetCookie);
2354
4533
  }
2355
4534
  function cookieHeader(cookies) {
2356
4535
  return [...cookies].map(([name, value]) => `${encodeURIComponent(name)}=${encodeURIComponent(value)}`).join("; ");
@@ -2367,8 +4546,8 @@ var TestResponse = class _TestResponse {
2367
4546
  context;
2368
4547
  decoded;
2369
4548
  /** Reads the body once, so every assertion is synchronous. */
2370
- static async from(response, context = {}) {
2371
- return new _TestResponse(response, await response.text(), context);
4549
+ static async from(response2, context = {}) {
4550
+ return new _TestResponse(response2, await response2.text(), context);
2372
4551
  }
2373
4552
  get status() {
2374
4553
  return this.baseResponse.status;
@@ -2860,11 +5039,11 @@ ${JSON.stringify(errors, null, 2)}`, { actual: errors, expected: {} });
2860
5039
  return Arr.get(this.viewDataOf(this.ensureView()), key);
2861
5040
  }
2862
5041
  // Internals ---------------------------------------------------------------------------------------------
2863
- absolute(url) {
5042
+ absolute(url2) {
2864
5043
  try {
2865
- return new URL(url, this.context.request?.url ?? "http://localhost").href;
5044
+ return new URL(url2, this.context.request?.url ?? "http://localhost").href;
2866
5045
  } catch {
2867
- return url;
5046
+ return url2;
2868
5047
  }
2869
5048
  }
2870
5049
  excerpt(text = this.content) {
@@ -2919,7 +5098,7 @@ ${this.excerpt()}`;
2919
5098
  return decryptCookie(app.make(Encrypter), cookie.name, cookie.value);
2920
5099
  }
2921
5100
  needles(value, escape) {
2922
- return [value].flat().map((needle) => escape ? escapeHtml(needle) : needle);
5101
+ return [value].flat().map((needle) => escape ? escapeHtml2(needle) : needle);
2923
5102
  }
2924
5103
  inOrder(haystack, needles) {
2925
5104
  let position = 0;
@@ -2964,9 +5143,9 @@ function recordingViewFactory(factory, views) {
2964
5143
  }
2965
5144
  function recordingExceptionHandler(handler, record) {
2966
5145
  return forward(handler, {
2967
- async render(request, error) {
5146
+ async render(request2, error) {
2968
5147
  record(error);
2969
- return handler.render(request, error);
5148
+ return handler.render(request2, error);
2970
5149
  }
2971
5150
  });
2972
5151
  }
@@ -3004,13 +5183,13 @@ var HttpClient = class {
3004
5183
  /** Requests whose actingAs users reached the guards (the AuthFactory they went through). */
3005
5184
  actingAsApplied = /* @__PURE__ */ new WeakMap();
3006
5185
  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") ?? "/", {}));
5186
+ let response2 = await this.send(this.buildRequest(method, uri, options));
5187
+ if (!this.followRedirects) return response2;
5188
+ for (let hops = 0; isRedirect(response2.status); hops++) {
5189
+ if (hops >= MAX_REDIRECTS) fail2(`Too many redirects (more than ${MAX_REDIRECTS}), last location [${response2.headers.get("Location")}].`);
5190
+ response2 = await this.send(this.buildRequest("GET", response2.headers.get("Location") ?? "/", {}));
3012
5191
  }
3013
- return response;
5192
+ return response2;
3014
5193
  }
3015
5194
  /** Runs `fn` in a scope prepared like a request's (carried session, actingAs users), e.g. for assertAuthenticated(). */
3016
5195
  async inScope(fn) {
@@ -3021,23 +5200,48 @@ var HttpClient = class {
3021
5200
  return fn(scope);
3022
5201
  });
3023
5202
  }
5203
+ /**
5204
+ * Runs `run` in a request scope like this client's requests have (a request for `method uri` bound, the carried
5205
+ * session, actingAs users), then carries the session and the users on, as after a request. Live.test() runs its
5206
+ * mount and updates through it.
5207
+ */
5208
+ async withinRequest(run, method = "GET", uri = "/") {
5209
+ await this.app.boot();
5210
+ const request2 = this.buildRequest(method, uri, {});
5211
+ setClientAddress2(request2, "127.0.0.1");
5212
+ const scope = this.app.createScope();
5213
+ const context = { request: request2, app: this.app, client: this };
5214
+ const result = await scope.run(async () => {
5215
+ this.beforeRequest(scope, context);
5216
+ scope.instance(HttpRequest, new IncomingRequest(request2));
5217
+ if (scope.bound(AuthFactory)) {
5218
+ try {
5219
+ await scope.make(AuthFactory).guard().check();
5220
+ } catch {
5221
+ }
5222
+ }
5223
+ return run();
5224
+ });
5225
+ await scope.run(() => this.afterRequest(scope, context));
5226
+ return result;
5227
+ }
3024
5228
  /** Runs the request through the app's real HttpKernel, in a request scope this client owns. */
3025
- async send(request) {
5229
+ async send(request2) {
3026
5230
  const app = this.app;
3027
5231
  await app.boot();
3028
- setClientAddress(request, "127.0.0.1");
5232
+ setClientAddress2(request2, "127.0.0.1");
3029
5233
  const scope = app.createScope();
3030
- const context = { request, app, client: this };
3031
- const response = await scope.run(async () => {
5234
+ const context = { request: request2, app, client: this };
5235
+ const response2 = await scope.run(async () => {
3032
5236
  await this.beforeRequest(scope, context);
3033
5237
  this.previousUrl = void 0;
3034
- return scope.make(HttpKernel).handle(request);
5238
+ return scope.make(HttpKernel).handle(request2);
3035
5239
  });
3036
5240
  await scope.run(() => this.afterRequest(scope, context));
3037
- this.rememberCookies(response);
3038
- await app.make(HttpKernel).terminate?.(request, response);
5241
+ this.rememberCookies(response2);
5242
+ await app.make(HttpKernel).terminate?.(request2, response2);
3039
5243
  if (!this.handleExceptions && context.exception !== void 0) throw context.exception;
3040
- return TestResponse.from(response, context);
5244
+ return TestResponse.from(response2, context);
3041
5245
  }
3042
5246
  /** Prepares the request scope: recorders, the carried session and actingAs users. */
3043
5247
  beforeRequest(scope, context) {
@@ -3092,12 +5296,12 @@ var HttpClient = class {
3092
5296
  }
3093
5297
  buildRequest(method, uri, options) {
3094
5298
  const verb = method.toUpperCase();
3095
- const url = new URL(uri, this.baseUrl());
5299
+ const url2 = new URL(uri, this.baseUrl());
3096
5300
  const headers = new Headers({ ...this.headers, ...options.headers });
3097
5301
  let body;
3098
5302
  const data = options.data ?? {};
3099
5303
  if (verb === "GET" || verb === "HEAD") {
3100
- for (const [key, value] of flatten(data)) if (typeof value === "string") url.searchParams.append(key, value);
5304
+ for (const [key, value] of flatten(data)) if (typeof value === "string") url2.searchParams.append(key, value);
3101
5305
  } else if (options.json) {
3102
5306
  body = JSON.stringify(data);
3103
5307
  } else if (Object.keys(data).length > 0) {
@@ -3116,7 +5320,7 @@ var HttpClient = class {
3116
5320
  }
3117
5321
  const cookies = this.cookieHeader();
3118
5322
  if (cookies !== "") headers.set("Cookie", cookies);
3119
- return new Request(url, { method: verb, headers, body });
5323
+ return new Request(url2, { method: verb, headers, body });
3120
5324
  }
3121
5325
  baseUrl() {
3122
5326
  const configured = this.app.bound(ConfigRepository) ? this.app.make(ConfigRepository).get("app.url") : void 0;
@@ -3131,8 +5335,8 @@ var HttpClient = class {
3131
5335
  return cookieHeader(values);
3132
5336
  }
3133
5337
  /** 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)) {
5338
+ rememberCookies(response2) {
5339
+ for (const cookie of responseCookies(response2)) {
3136
5340
  if (isExpired(cookie)) this.cookies.delete(cookie.name);
3137
5341
  else this.cookies.set(cookie.name, { value: cookie.value, encrypt: false });
3138
5342
  }
@@ -3141,9 +5345,9 @@ var HttpClient = class {
3141
5345
  function isRedirect(status) {
3142
5346
  return status >= 300 && status < 400 && status !== 304;
3143
5347
  }
3144
- function setClientAddress(request, address) {
5348
+ function setClientAddress2(request2, address) {
3145
5349
  const g = globalThis;
3146
- (g[CLIENT_ADDRESS] ??= /* @__PURE__ */ new WeakMap()).set(request, address);
5350
+ (g[CLIENT_ADDRESS] ??= /* @__PURE__ */ new WeakMap()).set(request2, address);
3147
5351
  }
3148
5352
  function flatten(data, prefix = "") {
3149
5353
  const out = [];
@@ -3239,11 +5443,43 @@ var InteractsWithApplication = class {
3239
5443
  if (global === app) Container.setInstance(void 0);
3240
5444
  }
3241
5445
  }
5446
+ /**
5447
+ * Laravel's withoutVite(): in this test's requests vite([...]) and viteReactRefresh() render nothing, so feature
5448
+ * tests of pages pass before `vite build` has written public/build/manifest.json.
5449
+ */
5450
+ withoutVite() {
5451
+ const app = this.app;
5452
+ app.scoped(Vite, () => new WithoutVite(app));
5453
+ return this;
5454
+ }
3242
5455
  /** Runs in tearDown(), before the application is thrown away (in registration order). */
3243
5456
  beforeApplicationDestroyed(callback) {
3244
5457
  this.destroying.push(callback);
3245
5458
  }
3246
5459
  };
5460
+ var WithoutVite = class extends Vite {
5461
+ tags() {
5462
+ return new ViteHtml("");
5463
+ }
5464
+ reactRefresh() {
5465
+ return void 0;
5466
+ }
5467
+ asset() {
5468
+ return "";
5469
+ }
5470
+ content() {
5471
+ return "";
5472
+ }
5473
+ manifestHash() {
5474
+ return void 0;
5475
+ }
5476
+ preloadedAssets() {
5477
+ return {};
5478
+ }
5479
+ toHtml() {
5480
+ return "";
5481
+ }
5482
+ };
3247
5483
  async function disconnectAll(db) {
3248
5484
  const manager = db;
3249
5485
  if (typeof manager.disconnectAll === "function") await manager.disconnectAll();
@@ -3258,6 +5494,11 @@ var MakesHttpRequests = class extends InteractsWithApplication {
3258
5494
  createHttpClient() {
3259
5495
  return new HttpClient(this.app);
3260
5496
  }
5497
+ /** Live.test() runs in this test's request scope (Live reads the same token: no import between the packages). */
5498
+ async setUp() {
5499
+ await super.setUp();
5500
+ this.app.instance(token("Testing.RequestScope"), (run) => this.http.withinRequest(run));
5501
+ }
3261
5502
  async tearDown() {
3262
5503
  try {
3263
5504
  await super.tearDown();
@@ -3273,8 +5514,8 @@ var MakesHttpRequests = class extends InteractsWithApplication {
3273
5514
  this.http.headers[name] = value;
3274
5515
  return this;
3275
5516
  }
3276
- withToken(token, type = "Bearer") {
3277
- return this.withHeader("Authorization", `${type} ${token}`);
5517
+ withToken(token2, type = "Bearer") {
5518
+ return this.withHeader("Authorization", `${type} ${token2}`);
3278
5519
  }
3279
5520
  withBasicAuth(username, password) {
3280
5521
  return this.withToken(Buffer.from(`${username}:${password}`).toString("base64"), "Basic");
@@ -3305,9 +5546,9 @@ var MakesHttpRequests = class extends InteractsWithApplication {
3305
5546
  return this;
3306
5547
  }
3307
5548
  /** 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);
5549
+ from(url2) {
5550
+ this.http.previousUrl = url2;
5551
+ return this.withHeader("Referer", url2);
3311
5552
  }
3312
5553
  /** Puts data in the session for the following requests (Laravel's withSession()). */
3313
5554
  withSession(data) {
@@ -3457,13 +5698,13 @@ var InteractsWithConsole = class extends InteractsWithDatabase {
3457
5698
  };
3458
5699
  var TestCase = class extends InteractsWithConsole {
3459
5700
  };
3460
- function install(token, fake) {
3461
- facade(token).swap(fake);
5701
+ function install(token2, fake) {
5702
+ facade(token2).swap(fake);
3462
5703
  return fake;
3463
5704
  }
3464
- function bound(token) {
5705
+ function bound(token2) {
3465
5706
  const container = currentContainer();
3466
- return container !== void 0 && container.bound(token) ? container.make(token) : void 0;
5707
+ return container !== void 0 && container.bound(token2) ? container.make(token2) : void 0;
3467
5708
  }
3468
5709
  function nameOf(target2) {
3469
5710
  if (typeof target2 === "string") return target2;
@@ -3486,7 +5727,7 @@ var QUEUE_OPTIONS = ["connection", "queue", "delay", "tries", "backoff", "timeou
3486
5727
  function isQueueable(job) {
3487
5728
  if (typeof job !== "object" || job === null) return false;
3488
5729
  const members = job;
3489
- if (members[SHOULD_QUEUE] === true) return true;
5730
+ if (members.shouldQueue === true || members[SHOULD_QUEUE] === true) return true;
3490
5731
  if (QUEUE_OPTIONS.some((option) => members[option] !== void 0)) return true;
3491
5732
  return typeof members.uniqueId === "function" || typeof members.retryUntil === "function";
3492
5733
  }
@@ -4159,8 +6400,8 @@ var AnonymousNotifiable = class {
4159
6400
  }
4160
6401
  dispatcher;
4161
6402
  routes = /* @__PURE__ */ new Map();
4162
- route(channel, route) {
4163
- this.routes.set(channel, route);
6403
+ route(channel, route2) {
6404
+ this.routes.set(channel, route2);
4164
6405
  return this;
4165
6406
  }
4166
6407
  routeNotificationFor(channel) {
@@ -4204,8 +6445,8 @@ var NotificationFake = class _NotificationFake {
4204
6445
  async sendNow(notifiables, notification, channels) {
4205
6446
  this.record(notifiables, notification, channels);
4206
6447
  }
4207
- route(channel, route) {
4208
- return new AnonymousNotifiable(this).route(channel, route);
6448
+ route(channel, route2) {
6449
+ return new AnonymousNotifiable(this).route(channel, route2);
4209
6450
  }
4210
6451
  /** The Notifications provider calls this in boot(); a fake has no channels to register. */
4211
6452
  extend(_channel, _factory) {
@@ -4304,8 +6545,74 @@ var MIME = {
4304
6545
  mp4: "video/mp4"
4305
6546
  };
4306
6547
  function guessMimeType(path) {
4307
- const extension = path.split(".").pop()?.toLowerCase() ?? "";
4308
- return MIME[extension];
6548
+ const base = path.slice(path.lastIndexOf("/") + 1);
6549
+ const dot = base.lastIndexOf(".");
6550
+ const extension = dot === -1 ? "" : base.slice(dot + 1).toLowerCase();
6551
+ return Object.hasOwn(MIME, extension) ? MIME[extension] : void 0;
6552
+ }
6553
+ var DISK_TYPES = {
6554
+ txt: "text/plain",
6555
+ html: "text/html",
6556
+ htm: "text/html",
6557
+ css: "text/css",
6558
+ csv: "text/csv",
6559
+ md: "text/markdown",
6560
+ js: "text/javascript",
6561
+ mjs: "text/javascript",
6562
+ json: "application/json",
6563
+ xml: "application/xml",
6564
+ pdf: "application/pdf",
6565
+ zip: "application/zip",
6566
+ gz: "application/gzip",
6567
+ tar: "application/x-tar",
6568
+ "7z": "application/x-7z-compressed",
6569
+ rar: "application/vnd.rar",
6570
+ epub: "application/epub+zip",
6571
+ rtf: "application/rtf",
6572
+ doc: "application/msword",
6573
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
6574
+ xls: "application/vnd.ms-excel",
6575
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
6576
+ ppt: "application/vnd.ms-powerpoint",
6577
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
6578
+ odt: "application/vnd.oasis.opendocument.text",
6579
+ ods: "application/vnd.oasis.opendocument.spreadsheet",
6580
+ odp: "application/vnd.oasis.opendocument.presentation",
6581
+ png: "image/png",
6582
+ jpg: "image/jpeg",
6583
+ jpeg: "image/jpeg",
6584
+ gif: "image/gif",
6585
+ webp: "image/webp",
6586
+ avif: "image/avif",
6587
+ svg: "image/svg+xml",
6588
+ ico: "image/x-icon",
6589
+ bmp: "image/bmp",
6590
+ tif: "image/tiff",
6591
+ tiff: "image/tiff",
6592
+ heic: "image/heic",
6593
+ heif: "image/heif",
6594
+ mp3: "audio/mpeg",
6595
+ m4a: "audio/mp4",
6596
+ wav: "audio/wav",
6597
+ ogg: "audio/ogg",
6598
+ oga: "audio/ogg",
6599
+ opus: "audio/ogg",
6600
+ flac: "audio/flac",
6601
+ aac: "audio/aac",
6602
+ mp4: "video/mp4",
6603
+ mov: "video/quicktime",
6604
+ webm: "video/webm",
6605
+ ogv: "video/ogg",
6606
+ avi: "video/x-msvideo",
6607
+ woff: "font/woff",
6608
+ woff2: "font/woff2",
6609
+ ttf: "font/ttf",
6610
+ otf: "font/otf",
6611
+ wasm: "application/wasm"
6612
+ };
6613
+ function diskType(extension) {
6614
+ const key = extension.replace(/^\./, "").toLowerCase();
6615
+ return Object.hasOwn(DISK_TYPES, key) ? DISK_TYPES[key] : void 0;
4309
6616
  }
4310
6617
  function hashName(file) {
4311
6618
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -4361,17 +6668,24 @@ var FakeDisk = class {
4361
6668
  async put(path, contents, options = {}) {
4362
6669
  const key = normalize(path);
4363
6670
  const bytes = typeof contents === "string" ? new TextEncoder().encode(contents) : contents instanceof Uint8Array ? contents.slice() : await collect(contents);
4364
- this.stored.set(key, { bytes, visibility: options.visibility ?? "private", mimeType: options.mimeType ?? guessMimeType(key), modified: /* @__PURE__ */ new Date() });
6671
+ this.write(key, bytes, options.mimeType ?? diskType(posix.extname(key)), options);
4365
6672
  return true;
4366
6673
  }
4367
6674
  putFile(directory, file, options = {}) {
4368
6675
  return this.putFileAs(directory, file, hashName(file), options);
4369
6676
  }
6677
+ /**
6678
+ * The upload's own type, as the S3 disk sends it: its extension(), never the MIME type the client sent as such nor
6679
+ * the stored name's type.
6680
+ */
4370
6681
  async putFileAs(directory, file, name, options = {}) {
4371
6682
  const path = normalize(`${directory}/${name}`);
4372
- await this.put(path, new Uint8Array(await file.arrayBuffer()), { ...options, mimeType: options.mimeType ?? file.mimeType });
6683
+ this.write(path, new Uint8Array(await file.arrayBuffer()), options.mimeType ?? diskType(file.extension()), options);
4373
6684
  return path;
4374
6685
  }
6686
+ write(key, bytes, mimeType, options) {
6687
+ this.stored.set(key, { bytes, visibility: options.visibility ?? "private", mimeType, modified: /* @__PURE__ */ new Date() });
6688
+ }
4375
6689
  async delete(paths) {
4376
6690
  for (const path of [paths].flat()) this.stored.delete(normalize(path));
4377
6691
  return true;
@@ -4565,6 +6879,37 @@ var StorageFake = class _StorageFake {
4565
6879
  return this.disk().path(path);
4566
6880
  }
4567
6881
  };
6882
+ var ascii = (text) => [...text].map((char) => char.charCodeAt(0));
6883
+ var u16be = (n) => [n >>> 8 & 255, n & 255];
6884
+ var u16le = (n) => [n & 255, n >>> 8 & 255];
6885
+ var u32le = (n) => [n & 255, n >>> 8 & 255, n >>> 16 & 255, n >>> 24 & 255];
6886
+ function concat(...parts) {
6887
+ const out = new Uint8Array(parts.reduce((n, part) => n + part.length, 0));
6888
+ let offset = 0;
6889
+ for (const part of parts) {
6890
+ out.set(part, offset);
6891
+ offset += part.length;
6892
+ }
6893
+ return out;
6894
+ }
6895
+ var BitWriter = class {
6896
+ bytes = [];
6897
+ buffer = 0;
6898
+ count = 0;
6899
+ write(value, bits) {
6900
+ this.buffer |= value << this.count;
6901
+ this.count += bits;
6902
+ while (this.count >= 8) {
6903
+ this.bytes.push(this.buffer & 255);
6904
+ this.buffer >>>= 8;
6905
+ this.count -= 8;
6906
+ }
6907
+ }
6908
+ finish() {
6909
+ if (this.count > 0) this.bytes.push(this.buffer & 255);
6910
+ return Uint8Array.from(this.bytes);
6911
+ }
6912
+ };
4568
6913
  var CRC_TABLE = Array.from({ length: 256 }, (_, n) => {
4569
6914
  let c = n;
4570
6915
  for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
@@ -4592,23 +6937,175 @@ function png(width, height) {
4592
6937
  header.set([8, 2, 0, 0, 0], 8);
4593
6938
  const rows = new Uint8Array((width * 3 + 1) * height).fill(255);
4594
6939
  for (let y = 0; y < height; y++) rows[y * (width * 3 + 1)] = 0;
4595
- const parts = [new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]), chunk("IHDR", header), chunk("IDAT", deflateSync(rows)), chunk("IEND", new Uint8Array())];
4596
- const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
4597
- let offset = 0;
4598
- for (const part of parts) {
4599
- out.set(part, offset);
4600
- offset += part.length;
6940
+ return concat([137, 80, 78, 71, 13, 10, 26, 10], chunk("IHDR", header), chunk("IDAT", deflateSync(rows)), chunk("IEND", new Uint8Array()));
6941
+ }
6942
+ var oneCodeTable = (tableClass) => [255, 196, ...u16be(20), tableClass, 1, ...new Array(15).fill(0), 0];
6943
+ function jpeg(width, height) {
6944
+ const bits = Math.ceil(width / 8) * Math.ceil(height / 8) * 2;
6945
+ const scan = new Uint8Array(Math.ceil(bits / 8));
6946
+ const padding = scan.length * 8 - bits;
6947
+ if (padding > 0) scan[scan.length - 1] = (1 << padding) - 1;
6948
+ return concat(
6949
+ [255, 216],
6950
+ // SOI
6951
+ [255, 224, ...u16be(16), ...ascii("JFIF"), 0, 1, 1, 0, 0, 1, 0, 1, 0, 0],
6952
+ // APP0
6953
+ [255, 219, ...u16be(67), 0, ...new Array(64).fill(1)],
6954
+ // DQT: table 0, all ones
6955
+ [255, 192, ...u16be(11), 8, ...u16be(height), ...u16be(width), 1, 1, 17, 0],
6956
+ // SOF0: one component
6957
+ oneCodeTable(0),
6958
+ // DHT: DC table 0
6959
+ oneCodeTable(16),
6960
+ // DHT: AC table 0
6961
+ [255, 218, ...u16be(8), 1, 1, 0, 0, 63, 0],
6962
+ // SOS
6963
+ scan,
6964
+ [255, 217]
6965
+ // EOI
6966
+ );
6967
+ }
6968
+ function lzwZeros(pixels, minCodeSize) {
6969
+ const clear = 1 << minCodeSize;
6970
+ const end = clear + 1;
6971
+ const out = new BitWriter();
6972
+ const table = /* @__PURE__ */ new Map();
6973
+ let codeSize = minCodeSize + 1;
6974
+ let next = end + 1;
6975
+ let current = 0;
6976
+ out.write(clear, codeSize);
6977
+ for (let i = 1; i < pixels; i++) {
6978
+ const key = current << 8;
6979
+ const known = table.get(key);
6980
+ if (known !== void 0) {
6981
+ current = known;
6982
+ continue;
6983
+ }
6984
+ out.write(current, codeSize);
6985
+ if (next === 4096) {
6986
+ out.write(clear, codeSize);
6987
+ table.clear();
6988
+ codeSize = minCodeSize + 1;
6989
+ next = end + 1;
6990
+ } else {
6991
+ if (next >= 1 << codeSize) codeSize++;
6992
+ table.set(key, next++);
6993
+ }
6994
+ current = 0;
4601
6995
  }
6996
+ out.write(current, codeSize);
6997
+ out.write(end, codeSize);
6998
+ return out.finish();
6999
+ }
7000
+ function gif(width, height) {
7001
+ const data = lzwZeros(width * height, 2);
7002
+ const blocks = [];
7003
+ for (let offset = 0; offset < data.length; offset += 255) {
7004
+ const block = data.subarray(offset, offset + 255);
7005
+ blocks.push(block.length, ...block);
7006
+ }
7007
+ return concat(
7008
+ ascii("GIF89a"),
7009
+ [...u16le(width), ...u16le(height), 128, 0, 0],
7010
+ // a global palette of two colours
7011
+ [255, 255, 255, 0, 0, 0],
7012
+ [44, 0, 0, 0, 0, ...u16le(width), ...u16le(height), 0],
7013
+ // the image descriptor
7014
+ [2],
7015
+ blocks,
7016
+ [0, 59]
7017
+ // the end of the data, the trailer
7018
+ );
7019
+ }
7020
+ function webp(width, height) {
7021
+ const bits = new BitWriter();
7022
+ bits.write(47, 8);
7023
+ bits.write(width - 1, 14);
7024
+ bits.write(height - 1, 14);
7025
+ bits.write(0, 4);
7026
+ bits.write(0, 3);
7027
+ for (const symbol of [255, 255, 255, 255, 0]) {
7028
+ bits.write(1, 1);
7029
+ bits.write(0, 1);
7030
+ bits.write(1, 1);
7031
+ bits.write(symbol, 8);
7032
+ }
7033
+ const data = bits.finish();
7034
+ const padded = data.length % 2 === 0 ? data : concat(data, [0]);
7035
+ return concat(ascii("RIFF"), u32le(12 + padded.length), ascii("WEBP"), ascii("VP8L"), u32le(data.length), padded);
7036
+ }
7037
+ function bmp(width, height) {
7038
+ const row = Math.ceil(width * 3 / 4) * 4;
7039
+ const out = new Uint8Array(54 + row * height);
7040
+ const view = new DataView(out.buffer);
7041
+ out.set(ascii("BM"));
7042
+ view.setUint32(2, out.length, true);
7043
+ view.setUint32(10, 54, true);
7044
+ view.setUint32(14, 40, true);
7045
+ view.setInt32(18, width, true);
7046
+ view.setInt32(22, height, true);
7047
+ view.setUint16(26, 1, true);
7048
+ view.setUint16(28, 24, true);
7049
+ view.setUint32(34, row * height, true);
7050
+ for (let y = 0; y < height; y++) out.fill(255, 54 + y * row, 54 + y * row + width * 3);
7051
+ return out;
7052
+ }
7053
+ var IMAGE_FORMATS = {
7054
+ png: { type: "image/png", encode: png },
7055
+ jpg: { type: "image/jpeg", encode: jpeg },
7056
+ jpeg: { type: "image/jpeg", encode: jpeg },
7057
+ gif: { type: "image/gif", encode: gif },
7058
+ webp: { type: "image/webp", encode: webp },
7059
+ bmp: { type: "image/bmp", encode: bmp }
7060
+ };
7061
+ var extensionOf = (name) => name.includes(".") ? name.slice(name.lastIndexOf(".") + 1).toLowerCase() : "";
7062
+ var MORE_MIME_TYPES = {
7063
+ bmp: "image/bmp",
7064
+ avif: "image/avif",
7065
+ xml: "text/xml",
7066
+ htm: "text/html",
7067
+ md: "text/markdown",
7068
+ tsv: "text/tab-separated-values",
7069
+ text: "text/plain"
7070
+ };
7071
+ var LEADING_BYTES = {
7072
+ "image/jpeg": { bytes: [255, 216, 255, 224, ...u16be(16), ...ascii("JFIF"), 0, 1, 1, 0, 0, 1, 0, 1, 0, 0] },
7073
+ "image/png": { bytes: [137, 80, 78, 71, 13, 10, 26, 10] },
7074
+ "image/gif": { bytes: "GIF89a" },
7075
+ "image/webp": { bytes: "RIFF\0\0\0\0WEBP" },
7076
+ "image/avif": { bytes: [0, 0, 0, 20, ...ascii("ftypavif"), 0, 0, 0, 0, ...ascii("avifmif1")] },
7077
+ "image/bmp": { bytes: [...ascii("BM"), 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 40, 0, 0, 0] },
7078
+ "application/pdf": { bytes: "%PDF-1.4\n" },
7079
+ "image/svg+xml": { bytes: '<svg xmlns="http://www.w3.org/2000/svg"/>\n', text: true },
7080
+ "text/html": { bytes: "<!doctype html>\n", text: true },
7081
+ "text/xml": { bytes: '<?xml version="1.0"?>\n', text: true }
7082
+ };
7083
+ function contentFor(type, size) {
7084
+ const text = type.startsWith("text/") || type === "application/json";
7085
+ const leading = LEADING_BYTES[type] ?? { bytes: text ? "\n" : [], text };
7086
+ const head = typeof leading.bytes === "string" ? new TextEncoder().encode(leading.bytes) : Uint8Array.from(leading.bytes);
7087
+ const out = new Uint8Array(Math.max(size, head.length, 1)).fill(leading.text ? 32 : 0);
7088
+ out.set(head);
4602
7089
  return out;
4603
7090
  }
4604
7091
  var FakeFile = {
4605
- /** A file of `kilobytes` KiB (zero bytes), typed by `mimeType` or the extension. */
7092
+ /**
7093
+ * A file of `kilobytes` KiB typed by `mimeType` or the name's extension. Validation reads a file's bytes, so the
7094
+ * file starts like its type (a PDF's `%PDF-`, a PNG's signature, text for text types) and passes `mimes` for its
7095
+ * name, as Laravel's fakes do. It is at least its type's leading bytes and never empty, whatever the size asked for.
7096
+ * An image made this way has no readable size: use image() for `dimensions`.
7097
+ */
4606
7098
  create(name, kilobytes = 0, mimeType) {
4607
- return new File([new Uint8Array(Math.round(kilobytes * 1024))], name, { type: mimeType ?? guessMimeType(name) ?? "application/octet-stream" });
7099
+ const type = mimeType ?? guessMimeType(name) ?? MORE_MIME_TYPES[extensionOf(name)] ?? "application/octet-stream";
7100
+ return new File([contentFor(type, Math.round(kilobytes * 1024))], name, { type });
4608
7101
  },
4609
- /** A real PNG image of the given size (typed by the name's extension, like Laravel's image()). */
7102
+ /**
7103
+ * A real white image of the given size in the format the name's extension asks for: png, jpg/jpeg, gif, webp or
7104
+ * bmp (any other name gets a JPEG, as Laravel's fakes do), so image, mimes and dimensions pass on it.
7105
+ */
4610
7106
  image(name, width = 10, height = 10) {
4611
- return new File([png(width, height)], name, { type: guessMimeType(name) ?? "image/png" });
7107
+ const format2 = IMAGE_FORMATS[extensionOf(name)] ?? IMAGE_FORMATS.jpeg;
7108
+ return new File([format2.encode(width, height)], name, { type: guessMimeType(name) ?? format2.type });
4612
7109
  }
4613
7110
  };
4614
7111
 
@@ -4626,6 +7123,70 @@ export {
4626
7123
  viteReactRefresh,
4627
7124
  Application2 as Application,
4628
7125
  ClosureKernel,
7126
+ HttpException,
7127
+ HttpResponseException,
7128
+ HttpServerError,
7129
+ exceedsJsonDepth,
7130
+ requestScope,
7131
+ currentRequest,
7132
+ optionalSession,
7133
+ optionalCookieJar,
7134
+ HttpRedirectResponse,
7135
+ HttpRedirector,
7136
+ setClientAddress,
7137
+ clientAddress,
7138
+ extensionFromMime,
7139
+ FormUploadedFile,
7140
+ parseBracketKey,
7141
+ normalizeLists,
7142
+ fromEntries,
7143
+ sanitize,
7144
+ mergeRecursive,
7145
+ isBlank,
7146
+ transformLeaves,
7147
+ ipMatches,
7148
+ IncomingRequest,
7149
+ request,
7150
+ response,
7151
+ redirect,
7152
+ back,
7153
+ url,
7154
+ route,
7155
+ abort,
7156
+ abortIf,
7157
+ abortUnless,
7158
+ contentDisposition,
7159
+ HttpResponseFactory,
7160
+ ConvertEmptyStringsToNull,
7161
+ TrimStrings,
7162
+ Kernel,
7163
+ MiddlewareConfiguration,
7164
+ applyMiddlewareCallbacks,
7165
+ statusText,
7166
+ escapeHtml,
7167
+ minimalPage,
7168
+ stackFrames,
7169
+ debugPage,
7170
+ statusFor,
7171
+ summarizeValidation,
7172
+ ErrorHandler,
7173
+ ExceptionsConfiguration,
7174
+ applyExceptionCallbacks,
7175
+ servePublicFile,
7176
+ bunRuntime,
7177
+ serveBun,
7178
+ toWebRequest,
7179
+ writeResponse,
7180
+ serveNode,
7181
+ detectRuntime,
7182
+ serve,
7183
+ routeListRows,
7184
+ filterRouteList,
7185
+ formatRouteList,
7186
+ API_ROUTES_STUB,
7187
+ addApiRouting,
7188
+ registerHttpCommands,
7189
+ HttpServiceProvider,
4629
7190
  On,
4630
7191
  Locked,
4631
7192
  Computed,
@@ -4638,13 +7199,18 @@ export {
4638
7199
  PropertyNotUpdatableError,
4639
7200
  LiveComponentNotFoundError,
4640
7201
  InvalidLiveRequestError,
7202
+ TestRequestScope,
4641
7203
  Testable,
4642
7204
  live,
4643
7205
  Live,
4644
7206
  componentName,
7207
+ PERSISTENT_MIDDLEWARE,
7208
+ persistentMiddleware,
7209
+ WithPagination,
4645
7210
  LiveComponentManager,
4646
7211
  UPDATE_URI,
4647
7212
  LiveServiceProvider,
7213
+ Pagination,
4648
7214
  PendingCommand,
4649
7215
  RefreshDatabaseState,
4650
7216
  runCommand,
@@ -4690,4 +7256,4 @@ export {
4690
7256
  StorageFake,
4691
7257
  FakeFile
4692
7258
  };
4693
- //# sourceMappingURL=chunk-I2OMKIQZ.js.map
7259
+ //# sourceMappingURL=chunk-WNJP46B7.js.map