@orieljs/framework 0.1.0-alpha.6 → 0.1.0-alpha.7

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.
Files changed (86) hide show
  1. package/NOTICE +4 -0
  2. package/README.md +10 -1
  3. package/TRADEMARKS.md +33 -0
  4. package/dist/{chunk-ZOEPKBVF.js → chunk-4MWG3B7G.js} +1347 -428
  5. package/dist/chunk-4MWG3B7G.js.map +1 -0
  6. package/dist/{chunk-POEP4B2M.js → chunk-7ZQZYDOC.js} +745 -105
  7. package/dist/chunk-7ZQZYDOC.js.map +1 -0
  8. package/dist/{chunk-I63ZFMX6.js → chunk-GVNLZZTN.js} +1 -1
  9. package/dist/chunk-GVNLZZTN.js.map +1 -0
  10. package/dist/{chunk-MFTZQMZM.js → chunk-VOYW3R5J.js} +4 -4
  11. package/dist/chunk-VOYW3R5J.js.map +1 -0
  12. package/dist/contracts.d.ts +515 -17
  13. package/dist/contracts.js +1 -1
  14. package/dist/index.d-WQpfdIbG.d.ts +630 -0
  15. package/dist/index.d.ts +964 -152
  16. package/dist/index.js +4279 -866
  17. package/dist/index.js.map +1 -1
  18. package/dist/{jsx-runtime-CBZQYYDS.d.ts → jsx-runtime-6um4Mwfb.d.ts} +96 -5
  19. package/dist/jsx-runtime.d.ts +2 -2
  20. package/dist/jsx-runtime.js +3 -3
  21. package/dist/{testing-BM481Hdz.d.ts → testing-CKwfWJGn.d.ts} +180 -15
  22. package/dist/testing.d.ts +2 -2
  23. package/dist/testing.js +3 -3
  24. package/docs/authentication.md +487 -0
  25. package/docs/authorization.md +435 -0
  26. package/docs/cache.md +647 -0
  27. package/docs/collections.md +2270 -0
  28. package/docs/configuration.md +334 -0
  29. package/docs/console.md +651 -0
  30. package/docs/container.md +430 -0
  31. package/docs/controllers.md +406 -0
  32. package/docs/csrf.md +126 -0
  33. package/docs/database-testing.md +144 -0
  34. package/docs/database.md +236 -0
  35. package/docs/deployment.md +276 -0
  36. package/docs/eloquent-collections.md +218 -0
  37. package/docs/eloquent-factories.md +255 -0
  38. package/docs/eloquent-mutators.md +254 -0
  39. package/docs/eloquent-observers.md +145 -0
  40. package/docs/eloquent-relationships.md +557 -0
  41. package/docs/eloquent.md +494 -0
  42. package/docs/encryption.md +104 -0
  43. package/docs/errors.md +318 -0
  44. package/docs/events.md +336 -0
  45. package/docs/facades.md +278 -0
  46. package/docs/hashing.md +118 -0
  47. package/docs/helpers.md +1281 -0
  48. package/docs/http-tests.md +386 -0
  49. package/docs/inertia.md +485 -0
  50. package/docs/installation.md +209 -0
  51. package/docs/lifecycle.md +234 -0
  52. package/docs/live-tests.md +226 -0
  53. package/docs/live.md +910 -0
  54. package/docs/logging.md +255 -0
  55. package/docs/mail.md +619 -0
  56. package/docs/middleware.md +384 -0
  57. package/docs/migrations.md +416 -0
  58. package/docs/mocking.md +264 -0
  59. package/docs/navigation.json +273 -0
  60. package/docs/notifications.md +225 -0
  61. package/docs/pagination.md +247 -0
  62. package/docs/passwords.md +207 -0
  63. package/docs/providers.md +187 -0
  64. package/docs/queries.md +600 -0
  65. package/docs/queues.md +330 -0
  66. package/docs/requests.md +533 -0
  67. package/docs/responses.md +444 -0
  68. package/docs/routing.md +738 -0
  69. package/docs/scheduling.md +277 -0
  70. package/docs/seeding.md +122 -0
  71. package/docs/session.md +409 -0
  72. package/docs/starter-kits.md +396 -0
  73. package/docs/storage.md +192 -0
  74. package/docs/structure.md +264 -0
  75. package/docs/testing.md +165 -0
  76. package/docs/time-travel.md +138 -0
  77. package/docs/validation.md +1792 -0
  78. package/docs/verification.md +152 -0
  79. package/docs/views.md +566 -0
  80. package/docs/vite.md +322 -0
  81. package/package.json +34 -32
  82. package/dist/chunk-I63ZFMX6.js.map +0 -1
  83. package/dist/chunk-MFTZQMZM.js.map +0 -1
  84. package/dist/chunk-POEP4B2M.js.map +0 -1
  85. package/dist/chunk-ZOEPKBVF.js.map +0 -1
  86. package/dist/index.d-BJyJx6U1.d.ts +0 -319
@@ -10,7 +10,7 @@ import {
10
10
  facade,
11
11
  fileType_exports,
12
12
  inject
13
- } from "./chunk-POEP4B2M.js";
13
+ } from "./chunk-7ZQZYDOC.js";
14
14
  import {
15
15
  Application,
16
16
  AuthFactory,
@@ -45,7 +45,7 @@ import {
45
45
  ValidationFactory,
46
46
  ViewFactory,
47
47
  token
48
- } from "./chunk-I63ZFMX6.js";
48
+ } from "./chunk-GVNLZZTN.js";
49
49
 
50
50
  // ../Config/dist/index.js
51
51
  import { existsSync, readFileSync } from "fs";
@@ -54,24 +54,62 @@ import { parse } from "dotenv";
54
54
  import { existsSync as existsSync2, readdirSync } from "fs";
55
55
  import { join as join2 } from "path";
56
56
  import { pathToFileURL } from "url";
57
+ var kind = (value) => value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
58
+ var isPlainObject = (value) => {
59
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
60
+ const proto = Object.getPrototypeOf(value);
61
+ return proto === Object.prototype || proto === null;
62
+ };
63
+ function typed(repository, key2, defaultValue, expected, check) {
64
+ const value = repository.get(key2, defaultValue);
65
+ if (!check(value)) throw new TypeError(`Configuration value for key [${key2}] must be ${expected}, ${kind(value)} given.`);
66
+ return value;
67
+ }
68
+ var Config = facade(ConfigRepository, {
69
+ /** Several keys at once: a list of keys, or an object of keys and their defaults. */
70
+ getMany(keys) {
71
+ const entries = Array.isArray(keys) ? keys.map((key2) => [key2, void 0]) : Object.entries(keys);
72
+ return Object.fromEntries(entries.map(([key2, fallback]) => [key2, this.get(key2, fallback)]));
73
+ },
74
+ /** Puts a value first in the array at `key` (creating it when missing). */
75
+ prepend(key2, value) {
76
+ const current = this.get(key2, []);
77
+ this.set(key2, [value, ...Array.isArray(current) ? current : [current]]);
78
+ },
79
+ string(key2, defaultValue) {
80
+ return typed(this, key2, defaultValue, "a string", (value) => typeof value === "string");
81
+ },
82
+ integer(key2, defaultValue) {
83
+ return typed(this, key2, defaultValue, "an integer", (value) => Number.isInteger(value));
84
+ },
85
+ float(key2, defaultValue) {
86
+ return typed(this, key2, defaultValue, "a float", (value) => typeof value === "number");
87
+ },
88
+ boolean(key2, defaultValue) {
89
+ return typed(this, key2, defaultValue, "a boolean", (value) => typeof value === "boolean");
90
+ },
91
+ array(key2, defaultValue) {
92
+ return typed(this, key2, defaultValue, "an array", (value) => Array.isArray(value) || isPlainObject(value));
93
+ }
94
+ });
57
95
  var Repository = class {
58
96
  constructor(items = {}) {
59
97
  this.items = items;
60
98
  }
61
99
  items;
62
- has(key) {
63
- return Arr.has(this.items, key);
100
+ has(key2) {
101
+ return Arr.has(this.items, key2);
64
102
  }
65
- get(key, defaultValue) {
66
- return Arr.get(this.items, key, defaultValue);
103
+ get(key2, defaultValue) {
104
+ return Arr.get(this.items, key2, defaultValue);
67
105
  }
68
106
  set(keyOrValues, value) {
69
107
  const entries = typeof keyOrValues === "string" ? [[keyOrValues, value]] : Object.entries(keyOrValues);
70
108
  for (const [k, v] of entries) Arr.set(this.items, k, v);
71
109
  }
72
- push(key, value) {
73
- const current = this.get(key, []);
74
- this.set(key, [...Array.isArray(current) ? current : [current], value]);
110
+ push(key2, value) {
111
+ const current = this.get(key2, []);
112
+ this.set(key2, [...Array.isArray(current) ? current : [current], value]);
75
113
  }
76
114
  all() {
77
115
  return this.items;
@@ -87,8 +125,8 @@ var CASTS = {
87
125
  "empty": "",
88
126
  "(empty)": ""
89
127
  };
90
- function env(key, defaultValue) {
91
- const raw = process.env[key];
128
+ function env(key2, defaultValue) {
129
+ const raw = process.env[key2];
92
130
  if (raw === void 0) return typeof defaultValue === "function" ? defaultValue() : defaultValue;
93
131
  const lower = raw.toLowerCase();
94
132
  if (Object.hasOwn(CASTS, lower)) return CASTS[lower];
@@ -107,9 +145,9 @@ function loadEnvironment(basePath) {
107
145
  if (!existsSync(file)) return void 0;
108
146
  const source = readFileSync(file, "utf8");
109
147
  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);
112
- if (process.env[key] === void 0) process.env[key] = value;
148
+ for (const [key2, raw] of Object.entries(parse(source))) {
149
+ const value = literal.has(key2) ? raw : raw.replace(/\$\{([A-Za-z_][A-Za-z0-9_.]*)\}/g, (match, name) => process.env[name] ?? match);
150
+ if (process.env[key2] === void 0) process.env[key2] = value;
113
151
  }
114
152
  return file;
115
153
  }
@@ -119,7 +157,7 @@ function clonePlain(value) {
119
157
  if (typeof value === "object" && value !== null) {
120
158
  const proto = Object.getPrototypeOf(value);
121
159
  if (proto !== Object.prototype && proto !== null) return value;
122
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, clonePlain(item)]));
160
+ return Object.fromEntries(Object.entries(value).map(([key2, item]) => [key2, clonePlain(item)]));
123
161
  }
124
162
  return value;
125
163
  }
@@ -227,6 +265,7 @@ var ViteHtml = class {
227
265
  return this.html;
228
266
  }
229
267
  };
268
+ var TRACKED = { "data-navigate-track": true };
230
269
  var CSS_PATH = /\.(css|less|sass|scss|styl|stylus|pcss|postcss)(\?[^.]*)?$/;
231
270
  var manifests = /* @__PURE__ */ new Map();
232
271
  var escapeAttribute = (value) => value.replace(/&/g, "&").replace(/"/g, """);
@@ -254,8 +293,8 @@ var Vite = class {
254
293
  cspNonce() {
255
294
  return this.#nonce;
256
295
  }
257
- useIntegrityKey(key) {
258
- this.#integrityKey = key;
296
+ useIntegrityKey(key2) {
297
+ this.#integrityKey = key2;
259
298
  return this;
260
299
  }
261
300
  withEntryPoints(entryPoints) {
@@ -323,16 +362,16 @@ var Vite = class {
323
362
  const imported = manifest[name];
324
363
  preloads.push([name, this.#assetPath(`${buildDirectory}/${imported.file}`), imported]);
325
364
  for (const css of imported.css ?? []) {
326
- const [key, partial] = cssChunk(css);
327
- preloads.push([key, this.#assetPath(`${buildDirectory}/${css}`), partial]);
328
- tags.push(this.#tagForChunk(key, this.#assetPath(`${buildDirectory}/${css}`), partial, manifest));
365
+ const [key2, partial] = cssChunk(css);
366
+ preloads.push([key2, this.#assetPath(`${buildDirectory}/${css}`), partial]);
367
+ tags.push(this.#tagForChunk(key2, this.#assetPath(`${buildDirectory}/${css}`), partial, manifest));
329
368
  }
330
369
  }
331
370
  tags.push(this.#tagForChunk(entry, this.#assetPath(`${buildDirectory}/${chunk2.file}`), chunk2, manifest));
332
371
  for (const css of chunk2.css ?? []) {
333
- const [key, partial] = cssChunk(css);
334
- preloads.push([key, this.#assetPath(`${buildDirectory}/${css}`), partial]);
335
- tags.push(this.#tagForChunk(key, this.#assetPath(`${buildDirectory}/${css}`), partial, manifest));
372
+ const [key2, partial] = cssChunk(css);
373
+ preloads.push([key2, this.#assetPath(`${buildDirectory}/${css}`), partial]);
374
+ tags.push(this.#tagForChunk(key2, this.#assetPath(`${buildDirectory}/${css}`), partial, manifest));
336
375
  }
337
376
  }
338
377
  const uniqueTags = [...new Set(tags)];
@@ -428,10 +467,10 @@ var Vite = class {
428
467
  }
429
468
  #tagForChunk(src, url2, chunk2, manifest) {
430
469
  if (CSS_PATH.test(url2)) {
431
- const attributes2 = this.#parseAttributes({ rel: "stylesheet", href: url2, nonce: this.#nonce ?? false, ...this.#styleAttributes(src, url2, chunk2, manifest) });
470
+ const attributes2 = this.#parseAttributes({ rel: "stylesheet", href: url2, nonce: this.#nonce ?? false, ...TRACKED, ...this.#styleAttributes(src, url2, chunk2, manifest) });
432
471
  return `<link ${attributes2.join(" ")} />`;
433
472
  }
434
- const attributes = this.#parseAttributes({ type: "module", src: url2, nonce: this.#nonce ?? false, ...this.#scriptAttributes(src, url2, chunk2, manifest) });
473
+ const attributes = this.#parseAttributes({ type: "module", src: url2, nonce: this.#nonce ?? false, ...TRACKED, ...this.#scriptAttributes(src, url2, chunk2, manifest) });
435
474
  return `<script ${attributes.join(" ")}></script>`;
436
475
  }
437
476
  #preloadTag(src, url2, chunk2, manifest) {
@@ -469,10 +508,10 @@ var Vite = class {
469
508
  }
470
509
  };
471
510
  function currentVite() {
472
- const container = currentContainer();
473
- if (container === void 0) throw new ContainerNotAvailableError("Vite");
474
- if (container.bound(HttpRequest)) return container.make(Vite);
475
- return new Vite(container.make(Application));
511
+ const container2 = currentContainer();
512
+ if (container2 === void 0) throw new ContainerNotAvailableError("Vite");
513
+ if (container2.bound(HttpRequest)) return container2.make(Vite);
514
+ return new Vite(container2.make(Application));
476
515
  }
477
516
  function vite(entryPoints, buildDirectory) {
478
517
  const instance = currentVite();
@@ -481,6 +520,7 @@ function vite(entryPoints, buildDirectory) {
481
520
  function viteReactRefresh() {
482
521
  return currentVite().reactRefresh();
483
522
  }
523
+ var SHUTDOWN_TIMEOUT_MS = 1e4;
484
524
  var Application2 = class _Application extends Container {
485
525
  constructor(base) {
486
526
  super();
@@ -492,11 +532,13 @@ var Application2 = class _Application extends Container {
492
532
  this.scoped(Vite, () => new Vite(this));
493
533
  }
494
534
  base;
495
- version = "0.1.0-alpha.6";
535
+ version = "0.1.0-alpha.7";
496
536
  providers = [];
497
537
  bootingCallbacks = [];
498
538
  bootedCallbacks = [];
499
539
  terminatingCallbacks = [];
540
+ shutdownCallbacks = [];
541
+ shutdownPromise;
500
542
  bootPromise;
501
543
  hasBooted = false;
502
544
  inConsole = false;
@@ -590,6 +632,44 @@ var Application2 = class _Application extends Container {
590
632
  async terminate() {
591
633
  for (const callback of this.terminatingCallbacks) await callback();
592
634
  }
635
+ /**
636
+ * A callback for the end of the process (the CLI after its command, the server on SIGTERM): close DB pools, Redis
637
+ * clients and log files. Not in Laravel, whose PHP processes end with each request.
638
+ */
639
+ onShutdown(callback) {
640
+ this.shutdownCallbacks.push(callback);
641
+ }
642
+ /**
643
+ * Runs the shutdown callbacks once (a second call returns the same promise), newest first: an app's own callbacks
644
+ * run before the framework closes what they use, then the framework's in the reverse of its providers' order (the
645
+ * queue, database and cache connections close, then the log is written, then the session store closes). A failing
646
+ * callback is printed with console.error (the log may be closed already) and the others still run; after 10 seconds
647
+ * the rest are given up on, so the process can still exit. Never rejects.
648
+ */
649
+ shutdown() {
650
+ return this.shutdownPromise ??= this.runShutdown();
651
+ }
652
+ async runShutdown() {
653
+ if (this.shutdownCallbacks.length === 0) return;
654
+ const callbacks = [...this.shutdownCallbacks].reverse();
655
+ const run = (async () => {
656
+ for (const callback of callbacks) {
657
+ try {
658
+ await callback();
659
+ } catch (error) {
660
+ console.error(error);
661
+ }
662
+ }
663
+ return "done";
664
+ })();
665
+ let timer;
666
+ const deadline = new Promise((resolve2) => timer = setTimeout(() => resolve2("timeout"), SHUTDOWN_TIMEOUT_MS));
667
+ const outcome = await Promise.race([run, deadline]);
668
+ clearTimeout(timer);
669
+ if (outcome === "timeout") {
670
+ console.error(`The shutdown callbacks did not finish within ${SHUTDOWN_TIMEOUT_MS / 1e3} seconds; carrying on with the exit.`);
671
+ }
672
+ }
593
673
  async runBoot() {
594
674
  for (const callback of this.bootingCallbacks) await callback(this);
595
675
  for (let i = 0; i < this.providers.length; i++) await this.bootProvider(this.providers[i]);
@@ -637,9 +717,16 @@ import { relative, resolve, sep } from "path";
637
717
  import { createServer } from "http";
638
718
  import { Readable as Readable3 } from "stream";
639
719
  import { pipeline } from "stream/promises";
640
- import { existsSync as existsSync4 } from "fs";
720
+ import { existsSync as existsSync32 } from "fs";
641
721
  import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
642
722
  import { dirname } from "path";
723
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
724
+ import { join as join5 } from "path";
725
+ import * as util from "util";
726
+ import { existsSync as existsSync22, statSync as statSync2, watch as watchPath } from "fs";
727
+ import { join as join23 } from "path";
728
+ import { createServer as createServer2 } from "net";
729
+ import { spawn as spawnProcess } from "child_process";
643
730
  var HttpException = class extends Error {
644
731
  constructor(status, message = "", headers = {}) {
645
732
  super(message);
@@ -711,8 +798,8 @@ async function readBody(raw, limit) {
711
798
  return out;
712
799
  }
713
800
  function requestScope() {
714
- const container = currentContainer();
715
- return container !== void 0 && container.bound(HttpRequest) ? container : void 0;
801
+ const container2 = currentContainer();
802
+ return container2 !== void 0 && container2.bound(HttpRequest) ? container2 : void 0;
716
803
  }
717
804
  function currentRequest() {
718
805
  return requestScope()?.make(HttpRequest);
@@ -744,11 +831,11 @@ var HttpRedirectResponse = class {
744
831
  targetUrl;
745
832
  status;
746
833
  #headers;
747
- with(key, value) {
834
+ with(key2, value) {
748
835
  const session = optionalSession();
749
836
  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);
837
+ if (typeof key2 === "string") session.flash(key2, value);
838
+ else for (const [name, item] of Object.entries(key2)) session.flash(name, item);
752
839
  return this;
753
840
  }
754
841
  /** Flashes `errors` as { [bag]: { field: string[] } }, keeping the other bags already flashed. */
@@ -969,7 +1056,7 @@ var FormUploadedFile = class _FormUploadedFile {
969
1056
  };
970
1057
  var FORBIDDEN = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
971
1058
  var INTEGER_KEY = /^(?:0|[1-9]\d*)$/;
972
- var isPlainObject = (value) => {
1059
+ var isPlainObject2 = (value) => {
973
1060
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
974
1061
  const prototype = Object.getPrototypeOf(value);
975
1062
  return prototype === Object.prototype || prototype === null;
@@ -986,11 +1073,11 @@ function parseBracketKey(name) {
986
1073
  }
987
1074
  function normalizeLists(value) {
988
1075
  if (Array.isArray(value)) return value.map(normalizeLists);
989
- if (!isPlainObject(value)) return value;
1076
+ if (!isPlainObject2(value)) return value;
990
1077
  const keys = Object.keys(value);
991
1078
  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]);
1079
+ for (const key2 of keys) out[key2] = normalizeLists(value[key2]);
1080
+ if (keys.length > 0 && keys.every((key2, index) => key2 === String(index))) return keys.map((key2) => out[key2]);
994
1081
  return out;
995
1082
  }
996
1083
  function fromEntries(entries) {
@@ -1013,7 +1100,7 @@ function fromEntries(entries) {
1013
1100
  node[segment] = value;
1014
1101
  break;
1015
1102
  }
1016
- if (!isPlainObject(node[segment])) node[segment] = {};
1103
+ if (!isPlainObject2(node[segment])) node[segment] = {};
1017
1104
  node = node[segment];
1018
1105
  }
1019
1106
  }
@@ -1021,17 +1108,17 @@ function fromEntries(entries) {
1021
1108
  }
1022
1109
  function sanitize(value) {
1023
1110
  if (Array.isArray(value)) return value.map(sanitize);
1024
- if (!isPlainObject(value)) return value;
1111
+ if (!isPlainObject2(value)) return value;
1025
1112
  const out = {};
1026
- for (const [key, item] of Object.entries(value)) if (!FORBIDDEN.has(key)) out[key] = sanitize(item);
1113
+ for (const [key2, item] of Object.entries(value)) if (!FORBIDDEN.has(key2)) out[key2] = sanitize(item);
1027
1114
  return out;
1028
1115
  }
1029
1116
  function mergeRecursive(base, override) {
1030
1117
  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;
1118
+ for (const [key2, value] of Object.entries(override)) {
1119
+ if (FORBIDDEN.has(key2)) continue;
1120
+ const current = out[key2];
1121
+ out[key2] = (isPlainObject2(current) || Array.isArray(current)) && (isPlainObject2(value) || Array.isArray(value)) ? normalizeLists(mergeRecursive({ ...current }, { ...value })) : value;
1035
1122
  }
1036
1123
  return out;
1037
1124
  }
@@ -1039,17 +1126,17 @@ function isBlank(value) {
1039
1126
  if (value === null || value === void 0) return true;
1040
1127
  if (typeof value === "string") return value.trim() === "";
1041
1128
  if (Array.isArray(value)) return value.length === 0;
1042
- if (isPlainObject(value)) return Object.keys(value).length === 0;
1129
+ if (isPlainObject2(value)) return Object.keys(value).length === 0;
1043
1130
  return false;
1044
1131
  }
1045
1132
  function transformLeaves(tree, transform, prefix = "") {
1046
1133
  const out = {};
1047
- for (const [key, value] of Object.entries(tree)) out[key] = transformValue(`${prefix}${key}`, value, transform);
1134
+ for (const [key2, value] of Object.entries(tree)) out[key2] = transformValue(`${prefix}${key2}`, value, transform);
1048
1135
  return out;
1049
1136
  }
1050
1137
  function transformValue(path, value, transform) {
1051
1138
  if (Array.isArray(value)) return value.map((item, index) => transformValue(`${path}.${index}`, item, transform));
1052
- if (isPlainObject(value)) return transformLeaves(value, transform, `${path}.`);
1139
+ if (isPlainObject2(value)) return transformLeaves(value, transform, `${path}.`);
1053
1140
  return transform(path, value);
1054
1141
  }
1055
1142
  var TRUE_VALUES = /* @__PURE__ */ new Set(["1", "true", "on", "yes"]);
@@ -1143,7 +1230,7 @@ var IncomingRequest = class {
1143
1230
  return {};
1144
1231
  }
1145
1232
  if (Array.isArray(parsed)) return sanitize({ ...parsed });
1146
- return isPlainObject(parsed) ? sanitize(parsed) : {};
1233
+ return isPlainObject2(parsed) ? sanitize(parsed) : {};
1147
1234
  }
1148
1235
  async #parseForm(form) {
1149
1236
  const fields = [];
@@ -1254,12 +1341,12 @@ var IncomingRequest = class {
1254
1341
  content() {
1255
1342
  return this.#content;
1256
1343
  }
1257
- query(key, defaultValue) {
1258
- return Arr.get(this.#query, key, defaultValue);
1344
+ query(key2, defaultValue) {
1345
+ return Arr.get(this.#query, key2, defaultValue);
1259
1346
  }
1260
1347
  /** 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);
1348
+ input(key2, defaultValue) {
1349
+ return Arr.get({ ...this.#query, ...this.#body }, key2, defaultValue);
1263
1350
  }
1264
1351
  /** Input plus files (array_replace_recursive). */
1265
1352
  all() {
@@ -1268,48 +1355,48 @@ var IncomingRequest = class {
1268
1355
  only(...keys) {
1269
1356
  const all = this.all();
1270
1357
  const out = {};
1271
- for (const key of keys.flat()) if (Arr.has(all, key)) Arr.set(out, key, Arr.get(all, key));
1358
+ for (const key2 of keys.flat()) if (Arr.has(all, key2)) Arr.set(out, key2, Arr.get(all, key2));
1272
1359
  return out;
1273
1360
  }
1274
1361
  except(...keys) {
1275
1362
  const all = structuredCloneInput(this.all());
1276
- for (const key of keys.flat()) Arr.forget(all, key);
1363
+ for (const key2 of keys.flat()) Arr.forget(all, key2);
1277
1364
  return all;
1278
1365
  }
1279
1366
  has(...keys) {
1280
1367
  const all = this.all();
1281
- return keys.flat().every((key) => Arr.has(all, key));
1368
+ return keys.flat().every((key2) => Arr.has(all, key2));
1282
1369
  }
1283
1370
  hasAny(...keys) {
1284
1371
  const all = this.all();
1285
- return keys.flat().some((key) => Arr.has(all, key));
1372
+ return keys.flat().some((key2) => Arr.has(all, key2));
1286
1373
  }
1287
1374
  missing(...keys) {
1288
1375
  return !this.has(...keys);
1289
1376
  }
1290
- filled(key) {
1291
- return !isBlank(Arr.get(this.all(), key));
1377
+ filled(key2) {
1378
+ return !isBlank(Arr.get(this.all(), key2));
1292
1379
  }
1293
1380
  /** Laravel's string(): the input as a Stringable (the default when it is missing or null): request().string('q').trim(). */
1294
- string(key, defaultValue = "") {
1295
- const value = this.input(key);
1381
+ string(key2, defaultValue = "") {
1382
+ const value = this.input(key2);
1296
1383
  return Str.of(value === void 0 || value === null ? defaultValue : value);
1297
1384
  }
1298
1385
  /** Laravel's str(): string() under its short name. */
1299
- str(key, defaultValue = "") {
1300
- return this.string(key, defaultValue);
1386
+ str(key2, defaultValue = "") {
1387
+ return this.string(key2, defaultValue);
1301
1388
  }
1302
1389
  /** PHP's FILTER_VALIDATE_BOOLEAN: '1', 'true', 'on', 'yes' (any case) and true are true. */
1303
- boolean(key, defaultValue = false) {
1304
- const value = this.input(key);
1390
+ boolean(key2, defaultValue = false) {
1391
+ const value = this.input(key2);
1305
1392
  if (value === void 0) return defaultValue;
1306
1393
  if (typeof value === "boolean") return value;
1307
1394
  if (typeof value === "number") return value === 1;
1308
1395
  return typeof value === "string" && TRUE_VALUES.has(value.trim().toLowerCase());
1309
1396
  }
1310
1397
  /** PHP's intval: leading digits, else 0. */
1311
- integer(key, defaultValue = 0) {
1312
- const value = this.input(key);
1398
+ integer(key2, defaultValue = 0) {
1399
+ const value = this.input(key2);
1313
1400
  if (value === void 0) return defaultValue;
1314
1401
  if (typeof value === "number") return Number.isFinite(value) ? Math.trunc(value) : 0;
1315
1402
  if (typeof value === "boolean") return value ? 1 : 0;
@@ -1327,12 +1414,12 @@ var IncomingRequest = class {
1327
1414
  this.#body = transformLeaves(this.#body, transform);
1328
1415
  return this;
1329
1416
  }
1330
- file(key) {
1331
- const value = Arr.get(this.#files, key);
1417
+ file(key2) {
1418
+ const value = Arr.get(this.#files, key2);
1332
1419
  return value instanceof FormUploadedFile ? value : void 0;
1333
1420
  }
1334
- hasFile(key) {
1335
- const value = Arr.get(this.#files, key);
1421
+ hasFile(key2) {
1422
+ const value = Arr.get(this.#files, key2);
1336
1423
  return value instanceof FormUploadedFile || Array.isArray(value) && value.some((item) => item instanceof FormUploadedFile);
1337
1424
  }
1338
1425
  allFiles() {
@@ -1366,38 +1453,48 @@ var IncomingRequest = class {
1366
1453
  this.#cookies = { ...cookies };
1367
1454
  }
1368
1455
  /** One route parameter (after model binding), or all of them. */
1369
- route(key) {
1456
+ route(key2) {
1370
1457
  const parameters = this.#currentRoute()?.parameters ?? {};
1371
- return key === void 0 ? { ...parameters } : parameters[key];
1458
+ return key2 === void 0 ? { ...parameters } : parameters[key2];
1372
1459
  }
1373
1460
  session() {
1374
1461
  return inject(Session);
1375
1462
  }
1376
- user(guard) {
1377
- return this.#userResolver?.(guard);
1463
+ /**
1464
+ * The signed-in user, through the resolver Auth sets, else the current scope's guard (the default, or `guard`).
1465
+ * The guard keeps the user it found, so it is looked up once per request. Undefined for a guest or without auth.
1466
+ */
1467
+ async user(guard) {
1468
+ if (this.#userResolver !== void 0) return await this.#userResolver(guard);
1469
+ const container2 = currentContainer();
1470
+ if (container2 === void 0 || !container2.bound(AuthFactory)) return void 0;
1471
+ return await container2.make(AuthFactory).guard(guard).user();
1378
1472
  }
1379
1473
  setUserResolver(resolver) {
1380
1474
  this.#userResolver = resolver;
1381
1475
  }
1382
- /** Validates all() through the ValidationFactory; throws its Validation error on failure. */
1476
+ /**
1477
+ * Validates all() through the ValidationFactory; throws its Validation error on failure. The data is typed from the
1478
+ * rules: `validate({ title: 'required|string' })` is `{ title: string }`.
1479
+ */
1383
1480
  validate(rules, messages, attributes) {
1384
1481
  return inject(ValidationFactory).make(this.all(), rules, messages, attributes).validated();
1385
1482
  }
1386
1483
  #currentRoute() {
1387
- const container = currentContainer();
1388
- return container?.bound(CurrentRoute) ? container.make(CurrentRoute) : void 0;
1484
+ const container2 = currentContainer();
1485
+ return container2?.bound(CurrentRoute) ? container2.make(CurrentRoute) : void 0;
1389
1486
  }
1390
1487
  };
1391
1488
  function structuredCloneInput(tree) {
1392
1489
  const out = {};
1393
- for (const [key, value] of Object.entries(tree)) {
1394
- out[key] = isPlainObject(value) ? structuredCloneInput(value) : Array.isArray(value) ? [...value] : value;
1490
+ for (const [key2, value] of Object.entries(tree)) {
1491
+ out[key2] = isPlainObject2(value) ? structuredCloneInput(value) : Array.isArray(value) ? [...value] : value;
1395
1492
  }
1396
1493
  return out;
1397
1494
  }
1398
- function request(key, defaultValue) {
1495
+ function request(key2, defaultValue) {
1399
1496
  const current = inject(HttpRequest);
1400
- return key === void 0 ? current : current.input(key, defaultValue);
1497
+ return key2 === void 0 ? current : current.input(key2, defaultValue);
1401
1498
  }
1402
1499
  function response(content, status, headers) {
1403
1500
  const factory = inject(ResponseFactory);
@@ -1414,7 +1511,8 @@ function url(path, query, absolute) {
1414
1511
  const generator = inject(UrlGenerator);
1415
1512
  return path === void 0 ? generator : generator.to(path, query, absolute);
1416
1513
  }
1417
- function route(name, parameters, absolute) {
1514
+ function route(name, ...args) {
1515
+ const [parameters, absolute] = args;
1418
1516
  return inject(UrlGenerator).route(name, parameters, absolute);
1419
1517
  }
1420
1518
  function abort(status, message = "", headers = {}) {
@@ -1487,7 +1585,7 @@ var TrimStrings = class {
1487
1585
  async handle(request2, next) {
1488
1586
  if (request2 instanceof IncomingRequest) {
1489
1587
  request2.transformInput(
1490
- (key, value) => typeof value === "string" && !this.except.includes(key) ? value.replace(TRIM, "") : value
1588
+ (key2, value) => typeof value === "string" && !this.except.includes(key2) ? value.replace(TRIM, "") : value
1491
1589
  );
1492
1590
  }
1493
1591
  return next(request2);
@@ -1733,18 +1831,41 @@ var STATUS_TEXTS = {
1733
1831
  403: "Forbidden",
1734
1832
  404: "Not Found",
1735
1833
  405: "Method Not Allowed",
1834
+ 406: "Not Acceptable",
1835
+ 407: "Proxy Authentication Required",
1736
1836
  408: "Request Timeout",
1737
1837
  409: "Conflict",
1738
1838
  410: "Gone",
1839
+ 411: "Length Required",
1840
+ 412: "Precondition Failed",
1739
1841
  413: "Content Too Large",
1842
+ 414: "URI Too Long",
1740
1843
  415: "Unsupported Media Type",
1844
+ 416: "Range Not Satisfiable",
1845
+ 417: "Expectation Failed",
1846
+ 418: "I'm a teapot",
1741
1847
  419: "Page Expired",
1848
+ 421: "Misdirected Request",
1742
1849
  422: "Unprocessable Content",
1850
+ 423: "Locked",
1851
+ 424: "Failed Dependency",
1852
+ 425: "Too Early",
1853
+ 426: "Upgrade Required",
1854
+ 428: "Precondition Required",
1743
1855
  429: "Too Many Requests",
1856
+ 431: "Request Header Fields Too Large",
1857
+ 451: "Unavailable For Legal Reasons",
1744
1858
  500: "Server Error",
1859
+ 501: "Not Implemented",
1745
1860
  502: "Bad Gateway",
1746
1861
  503: "Service Unavailable",
1747
- 504: "Gateway Timeout"
1862
+ 504: "Gateway Timeout",
1863
+ 505: "HTTP Version Not Supported",
1864
+ 506: "Variant Also Negotiates",
1865
+ 507: "Insufficient Storage",
1866
+ 508: "Loop Detected",
1867
+ 510: "Not Extended",
1868
+ 511: "Network Authentication Required"
1748
1869
  };
1749
1870
  function statusText(status) {
1750
1871
  return STATUS_TEXTS[status] ?? (status >= 500 ? "Server Error" : "Error");
@@ -1845,8 +1966,8 @@ function summarizeValidation(errors) {
1845
1966
  return more === 0 ? messages[0] : `${messages[0]} (and ${more} more ${more === 1 ? "error" : "errors"})`;
1846
1967
  }
1847
1968
  var isHttpLike = (error) => {
1848
- const kind = kindOf(error);
1849
- return kind !== void 0 && HTTP_KINDS.has(kind);
1969
+ const kind2 = kindOf(error);
1970
+ return kind2 !== void 0 && HTTP_KINDS.has(kind2);
1850
1971
  };
1851
1972
  var ErrorHandler = class {
1852
1973
  constructor(app) {
@@ -1878,8 +1999,8 @@ var ErrorHandler = class {
1878
1999
  return this.#jsonWhen !== void 0 ? this.#jsonWhen(request2, error) : request2.expectsJson();
1879
2000
  }
1880
2001
  shouldReport(error) {
1881
- const kind = kindOf(error);
1882
- if (kind !== void 0 && NOT_REPORTED.has(kind)) return false;
2002
+ const kind2 = kindOf(error);
2003
+ if (kind2 !== void 0 && NOT_REPORTED.has(kind2)) return false;
1883
2004
  return !this.#dontReport.some((cls) => error instanceof cls);
1884
2005
  }
1885
2006
  /** Report callbacks first (returning false stops the default log), then LogManager.error(), else console.error. Once per error. */
@@ -1891,10 +2012,10 @@ var ErrorHandler = class {
1891
2012
  }
1892
2013
  try {
1893
2014
  for (const callback of this.#reportCallbacks) if (await callback(error) === false) return;
1894
- const container = this.#container();
1895
- if (container.bound(LogManager)) {
2015
+ const container2 = this.#container();
2016
+ if (container2.bound(LogManager)) {
1896
2017
  const message = error instanceof Error ? error.message : String(error);
1897
- container.make(LogManager).error(message, { exception: error });
2018
+ container2.make(LogManager).error(message, { exception: error });
1898
2019
  } else {
1899
2020
  console.error(error);
1900
2021
  }
@@ -1911,15 +2032,14 @@ var ErrorHandler = class {
1911
2032
  const custom = await this.#renderCustom(request2, error);
1912
2033
  if (custom !== void 0) return custom;
1913
2034
  const http = this.#httpRequest(request2);
1914
- const kind = kindOf(error);
2035
+ const kind2 = kindOf(error);
1915
2036
  let response2;
1916
- if (kind === "Validation") response2 = await this.#renderValidation(http, error);
1917
- else if (kind === "Authentication") response2 = await this.#renderAuthentication(http, error);
2037
+ if (kind2 === "Validation") response2 = await this.#renderValidation(http, error);
2038
+ else if (kind2 === "Authentication") response2 = await this.#renderAuthentication(http, error);
1918
2039
  else if (this.shouldReturnJson(http, error)) response2 = this.#renderJson(error);
1919
2040
  else response2 = await this.#renderHtml(http, error);
1920
2041
  const headers = error?.headers;
1921
- if (headers !== void 0) for (const [name, value] of Object.entries(headers)) response2.headers.set(name, value);
1922
- return response2;
2042
+ return headers === void 0 ? response2 : withHeaders(response2, headers);
1923
2043
  }
1924
2044
  /** An error with its own render(request) or toResponse(request) (Laravel's renderable exceptions / Responsable). */
1925
2045
  async #renderCustom(request2, error) {
@@ -1937,12 +2057,12 @@ var ErrorHandler = class {
1937
2057
  return this.#json({ message: summarizeValidation(error.errors), errors: error.errors }, statusFor(error));
1938
2058
  }
1939
2059
  const input = { ...request2.input() };
1940
- for (const key of this.dontFlash) delete input[key];
1941
- const container = this.#container();
1942
- if (!container.bound(Redirector)) {
2060
+ for (const key2 of this.dontFlash) delete input[key2];
2061
+ const container2 = this.#container();
2062
+ if (!container2.bound(Redirector)) {
1943
2063
  return this.#redirect(error.redirectTo ?? request2.header("referer") ?? "/");
1944
2064
  }
1945
- const redirector = container.make(Redirector);
2065
+ const redirector = container2.make(Redirector);
1946
2066
  const redirect2 = error.redirectTo !== void 0 ? redirector.to(error.redirectTo) : redirector.back();
1947
2067
  const response2 = await redirect2.withInput(input).withErrors(error.errors, error.errorBag).toResponse(request2);
1948
2068
  return response2;
@@ -1950,9 +2070,9 @@ var ErrorHandler = class {
1950
2070
  async #renderAuthentication(request2, error) {
1951
2071
  if (this.shouldReturnJson(request2, error)) return this.#json({ message: error.message || "Unauthenticated." }, 401);
1952
2072
  const target2 = error.redirectTo ?? this.#guestRedirect(request2);
1953
- const container = this.#container();
1954
- if (!container.bound(Redirector)) return this.#redirect(target2);
1955
- const redirector = container.make(Redirector);
2073
+ const container2 = this.#container();
2074
+ if (!container2.bound(Redirector)) return this.#redirect(target2);
2075
+ const redirector = container2.make(Redirector);
1956
2076
  const redirect2 = typeof redirector.guest === "function" ? redirector.guest(target2) : redirector.to(target2);
1957
2077
  return await redirect2.toResponse(request2);
1958
2078
  }
@@ -1961,9 +2081,9 @@ var ErrorHandler = class {
1961
2081
  const configured = this.#config("http.redirect_guests_to", void 0);
1962
2082
  if (typeof configured === "function") return String(configured(request2));
1963
2083
  if (typeof configured === "string" && configured !== "") return configured;
1964
- const container = this.#container();
1965
- if (container.bound(UrlGenerator)) {
1966
- const url2 = container.make(UrlGenerator);
2084
+ const container2 = this.#container();
2085
+ if (container2.bound(UrlGenerator)) {
2086
+ const url2 = container2.make(UrlGenerator);
1967
2087
  if (url2.hasRoute("login")) return url2.route("login");
1968
2088
  }
1969
2089
  return "/login";
@@ -1999,9 +2119,9 @@ var ErrorHandler = class {
1999
2119
  }
2000
2120
  /** resources/views/errors/{status}.tsx, then errors/4xx or errors/5xx, through the ViewFactory. */
2001
2121
  async #errorView(status, error, message) {
2002
- const container = this.#container();
2003
- if (!container.bound(ViewFactory)) return void 0;
2004
- const views = container.make(ViewFactory);
2122
+ const container2 = this.#container();
2123
+ if (!container2.bound(ViewFactory)) return void 0;
2124
+ const views = container2.make(ViewFactory);
2005
2125
  for (const name of [`errors.${status}`, `errors.${Math.floor(status / 100)}xx`]) {
2006
2126
  if (!views.exists(name)) continue;
2007
2127
  try {
@@ -2031,14 +2151,19 @@ var ErrorHandler = class {
2031
2151
  #debug() {
2032
2152
  return this.#config("app.debug", false) === true;
2033
2153
  }
2034
- #config(key, fallback) {
2035
- return this.app.bound(ConfigRepository) ? this.app.make(ConfigRepository).get(key, fallback) : fallback;
2154
+ #config(key2, fallback) {
2155
+ return this.app.bound(ConfigRepository) ? this.app.make(ConfigRepository).get(key2, fallback) : fallback;
2036
2156
  }
2037
2157
  /** The request scope while handling a request (so scoped services resolve), else the application. */
2038
2158
  #container() {
2039
2159
  return currentContainer() ?? this.app;
2040
2160
  }
2041
2161
  };
2162
+ function withHeaders(response2, extra) {
2163
+ const headers = new Headers(response2.headers);
2164
+ for (const [name, value] of Object.entries(extra)) headers.set(name, value);
2165
+ return new Response(response2.body, { status: response2.status, statusText: response2.statusText, headers });
2166
+ }
2042
2167
  var ExceptionsConfiguration = class {
2043
2168
  constructor(handler) {
2044
2169
  this.handler = handler;
@@ -2113,9 +2238,9 @@ async function handleSafely(app, request2, publicPath) {
2113
2238
  return plainServerError();
2114
2239
  }
2115
2240
  }
2116
- function exitOnSignals(close, closed) {
2241
+ function exitOnSignals(close, closed, app) {
2117
2242
  const onSignal = () => {
2118
- void close().then(() => process.exit(0));
2243
+ void close().then(() => app.shutdown()).then(() => process.exit(0));
2119
2244
  };
2120
2245
  process.once("SIGINT", onSignal);
2121
2246
  process.once("SIGTERM", onSignal);
@@ -2157,7 +2282,7 @@ async function serveBun(app, options) {
2157
2282
  clearTimeout(timer);
2158
2283
  markClosed();
2159
2284
  })();
2160
- if (options.signals) exitOnSignals(close, closed);
2285
+ if (options.signals) exitOnSignals(close, closed, app);
2161
2286
  return {
2162
2287
  runtime: "bun",
2163
2288
  hostname: options.hostname,
@@ -2290,7 +2415,7 @@ async function serveNode(app, options) {
2290
2415
  clearInterval(idle);
2291
2416
  clearTimeout(force);
2292
2417
  })();
2293
- if (options.signals) exitOnSignals(close, closed);
2418
+ if (options.signals) exitOnSignals(close, closed, app);
2294
2419
  return {
2295
2420
  runtime: "node",
2296
2421
  hostname: options.hostname,
@@ -2318,6 +2443,232 @@ async function serve(app, options = {}) {
2318
2443
  onListen(server);
2319
2444
  return server;
2320
2445
  }
2446
+ var launchEnvironment = Object.freeze({ ...process.env });
2447
+ var BUN_ENV_FILES = [
2448
+ ".env",
2449
+ ".env.development",
2450
+ ".env.production",
2451
+ ".env.test",
2452
+ ".env.local",
2453
+ ".env.development.local",
2454
+ ".env.production.local",
2455
+ ".env.test.local"
2456
+ ];
2457
+ var expand = (value, env2) => value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (_match, braced, bare) => env2[braced ?? bare] ?? "");
2458
+ function serverEnvironment(options = {}) {
2459
+ const launched = options.launched ?? launchEnvironment;
2460
+ const env2 = { ...launched };
2461
+ if (!(options.bun ?? process.versions.bun !== void 0)) return env2;
2462
+ const parseEnv2 = "parseEnv" in options ? options.parseEnv : util.parseEnv;
2463
+ if (typeof parseEnv2 !== "function") return env2;
2464
+ const cwd = options.cwd ?? process.cwd();
2465
+ for (const name of BUN_ENV_FILES) {
2466
+ const file = join5(cwd, name);
2467
+ if (!existsSync4(file)) continue;
2468
+ let values;
2469
+ try {
2470
+ values = parseEnv2(readFileSync3(file, "utf8"));
2471
+ } catch {
2472
+ continue;
2473
+ }
2474
+ for (const [key2, raw] of Object.entries(values)) {
2475
+ const value = launched[key2];
2476
+ if (raw !== void 0 && value !== void 0 && (value === raw || value === expand(raw, launched))) delete env2[key2];
2477
+ }
2478
+ }
2479
+ return env2;
2480
+ }
2481
+ var WATCHED_FOLDERS = ["app", "bootstrap", "config", "lang", "resources/lang", "resources/views", "routes"];
2482
+ var WATCHED_ROOT_FILES = ["package.json", "tsconfig.json"];
2483
+ var IGNORED_FOLDERS = ["bootstrap/ssr", "bootstrap/cache"];
2484
+ var CODE_FILE = /\.(?:[cm]?[jt]sx?|json)$/;
2485
+ var DECLARATION_FILE = /\.d\.[cm]?ts$/;
2486
+ var EDITOR_FILE = /(?:\.sw[a-px]|~|___jb_(?:tmp|old)___)$/;
2487
+ var isEnvironmentFile = (name) => (name === ".env" || name.startsWith(".env.")) && name !== ".env.example" && !EDITOR_FILE.test(name);
2488
+ function isWatchedPath(path) {
2489
+ const segments = path.split("/");
2490
+ if (segments.length === 1) return isEnvironmentFile(path) || WATCHED_ROOT_FILES.includes(path);
2491
+ if (!WATCHED_FOLDERS.some((folder) => path.startsWith(`${folder}/`))) return false;
2492
+ if (IGNORED_FOLDERS.some((folder) => path === folder || path.startsWith(`${folder}/`))) return false;
2493
+ if (segments.some((segment) => segment.startsWith(".") || segment === "node_modules")) return false;
2494
+ const name = segments.at(-1);
2495
+ if (DECLARATION_FILE.test(name)) return false;
2496
+ return CODE_FILE.test(name) || !name.includes(".");
2497
+ }
2498
+ var toSlashes = (file) => file.split("\\").join("/");
2499
+ function watchFiles(basePath, onChange, options = {}) {
2500
+ const watch = options.watch ?? ((path, watchOptions, listener) => watchPath(path, watchOptions, listener));
2501
+ const debounce = options.debounce ?? 200;
2502
+ const onError = options.onError ?? (() => {
2503
+ });
2504
+ const changed = /* @__PURE__ */ new Set();
2505
+ let timer;
2506
+ const settle = options.settle ?? 1e3;
2507
+ const startedAt = Date.now();
2508
+ const reportedLate = (path) => {
2509
+ if (Date.now() - startedAt >= settle) return false;
2510
+ try {
2511
+ const modified = statSync2(join23(basePath, path), { throwIfNoEntry: false })?.mtimeMs;
2512
+ return modified === void 0 || modified < startedAt + 1;
2513
+ } catch {
2514
+ return false;
2515
+ }
2516
+ };
2517
+ const record = (path) => {
2518
+ if (!isWatchedPath(path) || reportedLate(path)) return;
2519
+ changed.add(path);
2520
+ clearTimeout(timer);
2521
+ timer = setTimeout(() => {
2522
+ const files = [...changed];
2523
+ changed.clear();
2524
+ onChange(files);
2525
+ }, debounce);
2526
+ };
2527
+ const handles = [];
2528
+ const start = (folder, recursive, toPath) => {
2529
+ try {
2530
+ const handle = watch(folder === "." ? basePath : join23(basePath, folder), { recursive }, (_event, file) => {
2531
+ const path = toPath(file);
2532
+ if (path !== void 0) record(path);
2533
+ });
2534
+ handle.on("error", (error) => onError(folder, error));
2535
+ handles.push(handle);
2536
+ } catch (error) {
2537
+ onError(folder, error);
2538
+ }
2539
+ };
2540
+ start(".", false, (file) => file === null ? void 0 : toSlashes(file));
2541
+ for (const folder of WATCHED_FOLDERS) {
2542
+ if (existsSync22(join23(basePath, folder))) start(folder, true, (file) => `${folder}/${file === null ? "" : toSlashes(file)}`);
2543
+ }
2544
+ return {
2545
+ close() {
2546
+ clearTimeout(timer);
2547
+ changed.clear();
2548
+ for (const handle of handles) handle.close();
2549
+ }
2550
+ };
2551
+ }
2552
+ function claimPort(hostname, port) {
2553
+ return new Promise((resolve2, reject) => {
2554
+ const server = createServer2();
2555
+ server.once("error", reject);
2556
+ try {
2557
+ server.listen({ port, host: hostname, exclusive: true }, () => {
2558
+ const { port: claimed } = server.address();
2559
+ server.close(() => resolve2(claimed));
2560
+ });
2561
+ } catch (error) {
2562
+ reject(error);
2563
+ }
2564
+ });
2565
+ }
2566
+ var REASONS = {
2567
+ EADDRINUSE: "Address already in use",
2568
+ EACCES: "Permission denied",
2569
+ EADDRNOTAVAIL: "Can't assign requested address"
2570
+ };
2571
+ function listenFailure(hostname, port, error) {
2572
+ const { code, message } = error;
2573
+ return `Failed to listen on ${displayHost(hostname)}:${port} (reason: ${code !== void 0 && REASONS[code] || message})`;
2574
+ }
2575
+ var WatchedServer = class {
2576
+ constructor(options) {
2577
+ this.options = options;
2578
+ }
2579
+ options;
2580
+ running;
2581
+ restarting = false;
2582
+ stopping = false;
2583
+ finish = () => {
2584
+ };
2585
+ /** Starts the server and keeps it current until stopped. Resolves 0 once the last server has exited. */
2586
+ run() {
2587
+ const done = new Promise((resolve2) => this.finish = resolve2);
2588
+ const stopListening = this.listenForSignals();
2589
+ const watcher = this.options.watch((files) => void this.changed(files));
2590
+ this.start();
2591
+ return done.finally(() => {
2592
+ watcher.close();
2593
+ stopListening();
2594
+ });
2595
+ }
2596
+ /** Stops the server and ends run(); called again, kills the server at once. */
2597
+ stop() {
2598
+ if (this.stopping) {
2599
+ this.running?.child.kill("SIGKILL");
2600
+ return;
2601
+ }
2602
+ this.stopping = true;
2603
+ void this.stopServer().then(() => this.finish(0));
2604
+ }
2605
+ async changed(files) {
2606
+ if (this.stopping || this.restarting || files.length === 0) return;
2607
+ this.restarting = true;
2608
+ try {
2609
+ this.options.info(
2610
+ files.some(isEnvironmentFile) ? "Environment modified. Restarting server..." : `Application change detected in [${files[0]}]. Restarting server...`
2611
+ );
2612
+ await this.stopServer();
2613
+ if (!this.stopping) this.start();
2614
+ } finally {
2615
+ this.restarting = false;
2616
+ }
2617
+ }
2618
+ start() {
2619
+ const [command, ...args] = this.options.command;
2620
+ const child = (this.options.spawn ?? spawnProcess)(command, args, {
2621
+ cwd: this.options.cwd,
2622
+ env: this.options.env,
2623
+ stdio: ["ignore", "inherit", "inherit"]
2624
+ });
2625
+ const running = { child, stopping: false, killed: false, exited: Promise.resolve() };
2626
+ running.exited = new Promise((resolve2) => {
2627
+ let settled = false;
2628
+ const settle = (report) => {
2629
+ if (settled) return;
2630
+ settled = true;
2631
+ if (this.running === running) this.running = void 0;
2632
+ if (!running.stopping && !this.stopping) this.options.error(`${report}. Waiting for changes before restarting...`);
2633
+ resolve2();
2634
+ };
2635
+ child.once("exit", (code, signal) => settle(signal !== null ? `The server stopped (signal ${signal})` : `The server stopped (exit code ${code})`));
2636
+ child.once("error", (error) => {
2637
+ if (child.pid === void 0) settle(`The server could not start (${error.message})`);
2638
+ });
2639
+ });
2640
+ this.running = running;
2641
+ }
2642
+ /** Sends SIGTERM once and waits for the exit, killing the server after stopTimeout. */
2643
+ async stopServer() {
2644
+ const running = this.running;
2645
+ if (running === void 0) return;
2646
+ if (!running.stopping) {
2647
+ running.stopping = true;
2648
+ running.child.kill("SIGTERM");
2649
+ }
2650
+ const timeout = this.options.stopTimeout ?? 15e3;
2651
+ const timer = setTimeout(() => {
2652
+ if (running.killed) return;
2653
+ running.killed = true;
2654
+ this.options.error(`The server did not stop within ${timeout / 1e3} seconds and was killed.`);
2655
+ running.child.kill("SIGKILL");
2656
+ }, timeout);
2657
+ try {
2658
+ await running.exited;
2659
+ } finally {
2660
+ clearTimeout(timer);
2661
+ }
2662
+ }
2663
+ listenForSignals() {
2664
+ const signals = this.options.signals ?? ["SIGINT", "SIGTERM", "SIGHUP"];
2665
+ const handler = () => this.stop();
2666
+ for (const signal of signals) process.on(signal, handler);
2667
+ return () => {
2668
+ for (const signal of signals) process.off(signal, handler);
2669
+ };
2670
+ }
2671
+ };
2321
2672
  function io(command) {
2322
2673
  return {
2323
2674
  option: (name) => {
@@ -2391,11 +2742,36 @@ ${web[1]}api: 'routes/api.ts',`);
2391
2742
  ${create[0]}`);
2392
2743
  return void 0;
2393
2744
  }
2745
+ async function serveWatching(app, out, port, run) {
2746
+ const hostname = out.option("host") ?? "127.0.0.1";
2747
+ const requested = port !== void 0 ? Number(port) : Number(process.env.PORT ?? process.env.SERVER_PORT ?? 8e3);
2748
+ let claimed;
2749
+ try {
2750
+ claimed = await claimPort(hostname, requested);
2751
+ } catch (error) {
2752
+ out.error(listenFailure(hostname, requested, error));
2753
+ return 1;
2754
+ }
2755
+ const script = process.argv[1];
2756
+ if (script === void 0) {
2757
+ out.error("serve --watch can't tell which script started this process, so it can't start the server.");
2758
+ return 1;
2759
+ }
2760
+ return run({
2761
+ command: [process.execPath, ...process.execArgv, script, "serve", `--host=${hostname}`, `--port=${claimed}`],
2762
+ cwd: process.cwd(),
2763
+ env: serverEnvironment(),
2764
+ watch: (onChange) => watchFiles(app.basePath(), onChange, { onError: (folder, error) => out.warn(`Unable to watch [${folder}] for changes: ${error.message}`) }),
2765
+ info: (message) => out.info(message),
2766
+ error: (message) => out.error(message)
2767
+ });
2768
+ }
2394
2769
  function registerHttpCommands(kernel, app, options = {}) {
2395
2770
  const serveApp = options.serve ?? serve;
2396
2771
  const terminalWidth = options.terminalWidth ?? (() => process.stdout.columns ?? 120);
2772
+ const runWatching = options.watch ?? ((watchOptions) => new WatchedServer(watchOptions).run());
2397
2773
  kernel.command(
2398
- "serve {--host=127.0.0.1 : The host address to serve the application on} {--port= : The port to serve the application on}",
2774
+ "serve {--host=127.0.0.1 : The host address to serve the application on} {--port= : The port to serve the application on} {--watch : Automatically restart the server when the application is modified}",
2399
2775
  async function() {
2400
2776
  const out = io(this);
2401
2777
  const port = out.option("port");
@@ -2403,6 +2779,7 @@ function registerHttpCommands(kernel, app, options = {}) {
2403
2779
  out.error("The --port option must be a number.");
2404
2780
  return 1;
2405
2781
  }
2782
+ if (out.flag("watch")) return serveWatching(app, out, port, runWatching);
2406
2783
  const server = await serveApp(app, {
2407
2784
  hostname: out.option("host") ?? "127.0.0.1",
2408
2785
  ...port !== void 0 && { port: Number(port) },
@@ -2450,7 +2827,7 @@ function registerHttpCommands(kernel, app, options = {}) {
2450
2827
  kernel.command("install:api {--force : Overwrite any existing API routes file}", async function() {
2451
2828
  const out = io(this);
2452
2829
  const routesFile = app.basePath("routes/api.ts");
2453
- if (existsSync4(routesFile) && !out.flag("force")) {
2830
+ if (existsSync32(routesFile) && !out.flag("force")) {
2454
2831
  out.error("API routes file already exists.");
2455
2832
  } else {
2456
2833
  await mkdir(dirname(routesFile), { recursive: true });
@@ -2458,7 +2835,7 @@ function registerHttpCommands(kernel, app, options = {}) {
2458
2835
  out.info("Published API routes file.");
2459
2836
  }
2460
2837
  const bootstrapFile = app.bootstrapPath("app.ts");
2461
- const source = existsSync4(bootstrapFile) ? await readFile2(bootstrapFile, "utf8") : void 0;
2838
+ const source = existsSync32(bootstrapFile) ? await readFile2(bootstrapFile, "utf8") : void 0;
2462
2839
  const updated = source === void 0 ? void 0 : addApiRouting(source);
2463
2840
  if (updated === void 0) {
2464
2841
  out.warn(
@@ -2467,7 +2844,7 @@ function registerHttpCommands(kernel, app, options = {}) {
2467
2844
  } else if (updated !== source) {
2468
2845
  await writeFile(bootstrapFile, updated);
2469
2846
  }
2470
- out.info("API scaffolding installed. Run [oriel migrate] to create the personal access tokens table.");
2847
+ out.info("API scaffolding installed.");
2471
2848
  return 0;
2472
2849
  }).purpose("Create an API routes file and prepare the application for API authentication");
2473
2850
  }
@@ -2505,16 +2882,16 @@ var HttpServiceProvider = class extends ServiceProvider {
2505
2882
  // ../Live/dist/index.js
2506
2883
  import { AssertionError } from "assert";
2507
2884
  import { isDeepStrictEqual } from "util";
2885
+ import { createHmac, timingSafeEqual } from "crypto";
2508
2886
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
2509
2887
  import { randomBytes as randomBytes2 } from "crypto";
2510
- import { createHmac, timingSafeEqual } from "crypto";
2511
2888
  import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
2512
- import { join as join5, relative as relative2, sep as sep2 } from "path";
2889
+ import { join as join6, relative as relative2, sep as sep2 } from "path";
2513
2890
  import { pathToFileURL as pathToFileURL2 } from "url";
2514
2891
  import { AsyncLocalStorage } from "async_hooks";
2515
- import { existsSync as existsSync22 } from "fs";
2892
+ import { existsSync as existsSync23 } from "fs";
2516
2893
  import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
2517
- import { dirname as dirname2, join as join23, relative as relative22, sep as sep22 } from "path";
2894
+ import { dirname as dirname2, join as join24, relative as relative22, sep as sep22 } from "path";
2518
2895
  var LiveError = class extends Error {
2519
2896
  name = "LiveError";
2520
2897
  kind = "Server";
@@ -2557,13 +2934,46 @@ var InvalidLiveRequestError = class extends LiveError {
2557
2934
  kind = "Http";
2558
2935
  status = 400;
2559
2936
  };
2937
+ var InvalidUploadSignatureError = class extends LiveError {
2938
+ name = "InvalidUploadSignatureError";
2939
+ kind = "Http";
2940
+ status = 401;
2941
+ constructor() {
2942
+ super("");
2943
+ }
2944
+ };
2945
+ var TemporaryFileNotFoundError = class extends LiveError {
2946
+ name = "TemporaryFileNotFoundError";
2947
+ kind = "Http";
2948
+ status = 404;
2949
+ constructor(filename) {
2950
+ super(`Unable to find the temporary upload [${filename}].`);
2951
+ }
2952
+ };
2953
+ var FileNotPreviewableError = class extends LiveError {
2954
+ name = "FileNotPreviewableError";
2955
+ constructor(extension) {
2956
+ super(`File with extension "${extension}" is not previewable. See the live.temporary_file_upload.preview_mimes config.`);
2957
+ }
2958
+ };
2959
+ var UploadValidationError = class extends LiveError {
2960
+ constructor(errors) {
2961
+ super("The upload failed.");
2962
+ this.errors = errors;
2963
+ }
2964
+ errors;
2965
+ name = "UploadValidationError";
2966
+ kind = "Validation";
2967
+ status = 422;
2968
+ errorBag = "default";
2969
+ };
2560
2970
  Symbol.metadata ??= /* @__PURE__ */ Symbol.for("Symbol.metadata");
2561
2971
  var LISTENERS = /* @__PURE__ */ Symbol.for("oriel:live.listeners");
2562
2972
  var LOCKED = /* @__PURE__ */ Symbol.for("oriel:live.locked");
2563
2973
  var COMPUTED = /* @__PURE__ */ Symbol.for("oriel:live.computed");
2564
- function own(metadata, key, empty2, copy) {
2565
- if (!Object.hasOwn(metadata, key)) metadata[key] = metadata[key] === void 0 ? empty2() : copy(metadata[key]);
2566
- return metadata[key];
2974
+ function own(metadata, key2, empty2, copy) {
2975
+ if (!Object.hasOwn(metadata, key2)) metadata[key2] = metadata[key2] === void 0 ? empty2() : copy(metadata[key2]);
2976
+ return metadata[key2];
2567
2977
  }
2568
2978
  function On(event) {
2569
2979
  return function(_method, context) {
@@ -2606,8 +3016,8 @@ function chainOf(cls) {
2606
3016
  }
2607
3017
  return chain;
2608
3018
  }
2609
- function staticsOf(cls, key) {
2610
- return chainOf(cls).flatMap((c) => Object.hasOwn(c, key) && c[key] !== void 0 ? [c[key]] : []);
3019
+ function staticsOf(cls, key2) {
3020
+ return chainOf(cls).flatMap((c) => Object.hasOwn(c, key2) && c[key2] !== void 0 ? [c[key2]] : []);
2611
3021
  }
2612
3022
  function listenersOf(cls) {
2613
3023
  const result = {};
@@ -2628,12 +3038,130 @@ function lockedOf(cls) {
2628
3038
  function computedOf(cls) {
2629
3039
  return /* @__PURE__ */ new Set([...metadataOf(cls)[COMPUTED] ?? [], ...staticsOf(cls, "computed").flat()]);
2630
3040
  }
3041
+ var TEMPORARY_UPLOAD_DEFAULTS = {
3042
+ disk: null,
3043
+ directory: "live-tmp",
3044
+ rules: ["required", "file", "max:12288"],
3045
+ middleware: "throttle:60,1",
3046
+ preview_mimes: ["png", "gif", "bmp", "svg", "wav", "mp4", "mov", "avi", "wmv", "mp3", "m4a", "jpg", "jpeg", "mpga", "webp", "wma"],
3047
+ max_upload_time: 5,
3048
+ cleanup: true
3049
+ };
3050
+ function container() {
3051
+ const current = currentContainer();
3052
+ if (current === void 0) throw new LiveError("Live uploads need the application: use them inside a request, a command or a test.");
3053
+ return current;
3054
+ }
3055
+ function uploadSetting(name) {
3056
+ const config = container().bound(ConfigRepository) ? container().make(ConfigRepository) : void 0;
3057
+ return config?.get(`live.temporary_file_upload.${name}`, null) ?? TEMPORARY_UPLOAD_DEFAULTS[name];
3058
+ }
3059
+ function temporaryDisk() {
3060
+ return container().make(FilesystemManager).disk(uploadSetting("disk") ?? void 0);
3061
+ }
3062
+ function temporaryDirectory() {
3063
+ return uploadSetting("directory").replace(/^\/+|\/+$/g, "");
3064
+ }
3065
+ var FILENAME = /^[A-Za-z0-9]{40}(?:\.[a-z0-9]+)?$/;
3066
+ function isTemporaryFileData(value) {
3067
+ if (typeof value !== "object" || value === null) return false;
3068
+ const data = value;
3069
+ return typeof data.filename === "string" && FILENAME.test(data.filename) && typeof data.name === "string" && typeof data.size === "number" && Number.isSafeInteger(data.size) && data.size >= 0 && typeof data.type === "string" && typeof data.extension === "string" && /^[a-z0-9]*$/.test(data.extension);
3070
+ }
3071
+ var HOUR = 36e5;
3072
+ var TemporaryUploadedFile = class {
3073
+ filename;
3074
+ clientName;
3075
+ mimeType;
3076
+ size;
3077
+ #extension;
3078
+ constructor(data) {
3079
+ if (!isTemporaryFileData(data)) throw new LiveError("A temporary upload needs a stored name, a name, a size, a type and an extension.");
3080
+ this.filename = data.filename;
3081
+ this.clientName = data.name;
3082
+ this.mimeType = data.type;
3083
+ this.size = data.size;
3084
+ this.#extension = data.extension;
3085
+ }
3086
+ /** What the snapshot carries. */
3087
+ toData() {
3088
+ return { filename: this.filename, name: this.clientName, size: this.size, type: this.mimeType, extension: this.#extension };
3089
+ }
3090
+ /** The file's path on the temporary disk. */
3091
+ get path() {
3092
+ const directory = temporaryDirectory();
3093
+ return directory === "" ? this.filename : `${directory}/${this.filename}`;
3094
+ }
3095
+ /** The extension of the upload's own type, as FormUploadedFile's extension(): what putFile() names a stored copy by. */
3096
+ extension() {
3097
+ return this.#extension;
3098
+ }
3099
+ /** The extension of the name the browser gave the file, lower-cased ('' without one). */
3100
+ clientExtension() {
3101
+ const dot = this.clientName.lastIndexOf(".");
3102
+ return dot === -1 ? "" : this.clientName.slice(dot + 1).toLowerCase();
3103
+ }
3104
+ /** A random 40-character name with extension(), as FormUploadedFile's. */
3105
+ hashName() {
3106
+ const extension = this.extension();
3107
+ return extension === "" ? Str.random(40) : `${Str.random(40)}.${extension}`;
3108
+ }
3109
+ async arrayBuffer() {
3110
+ const bytes = await temporaryDisk().getBytes(this.path);
3111
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
3112
+ }
3113
+ async text() {
3114
+ return new TextDecoder().decode(await temporaryDisk().getBytes(this.path));
3115
+ }
3116
+ /** The file's bytes from the temporary disk, opened on the first read. */
3117
+ stream() {
3118
+ let reader;
3119
+ return new ReadableStream({
3120
+ pull: async (controller) => {
3121
+ reader ??= (await temporaryDisk().readStream(this.path)).getReader();
3122
+ const { done, value } = await reader.read();
3123
+ if (done) controller.close();
3124
+ else controller.enqueue(value);
3125
+ },
3126
+ cancel: async (reason) => {
3127
+ await reader?.cancel(reason);
3128
+ }
3129
+ });
3130
+ }
3131
+ /** Stores a copy under a random name in `directory`, on `disk` (by default the temporary disk, as Livewire's). */
3132
+ store(directory, disk) {
3133
+ return container().make(FilesystemManager).disk(disk ?? uploadSetting("disk") ?? void 0).putFile(directory, this);
3134
+ }
3135
+ storeAs(directory, name, disk) {
3136
+ return container().make(FilesystemManager).disk(disk ?? uploadSetting("disk") ?? void 0).putFileAs(directory, this, name);
3137
+ }
3138
+ exists() {
3139
+ return temporaryDisk().exists(this.path);
3140
+ }
3141
+ /** Removes the temporary file (a stored copy stays). */
3142
+ delete() {
3143
+ return temporaryDisk().delete(this.path);
3144
+ }
3145
+ /** Whether temporaryUrl() serves this file: its type is in `live.temporary_file_upload.preview_mimes`. */
3146
+ isPreviewable() {
3147
+ return uploadSetting("preview_mimes").includes(this.#extension);
3148
+ }
3149
+ /**
3150
+ * A signed link to the file for `<img src>` or `<video src>` before it is stored. It lasts until the end of the hour
3151
+ * at least 30 minutes away (Livewire's), so a re-render keeps the same link and the browser doesn't reload it.
3152
+ */
3153
+ temporaryUrl() {
3154
+ if (!this.isPreviewable()) throw new FileNotPreviewableError(this.#extension);
3155
+ const expires = new Date((Math.floor((Date.now() + 30 * 6e4) / HOUR) + 1) * HOUR - 1e3);
3156
+ return container().make(UrlGenerator).temporarySignedRoute("live.preview-file", expires, { filename: this.filename }, false);
3157
+ }
3158
+ };
2631
3159
  function isModel(value) {
2632
3160
  if (typeof value !== "object" || value === null) return false;
2633
3161
  const candidate = value;
2634
3162
  return typeof candidate.getKey === "function" && typeof candidate.getKeyName === "function" && typeof candidate.getTable === "function" && typeof candidate.exists === "boolean";
2635
3163
  }
2636
- var isPlainObject2 = (value) => {
3164
+ var isPlainObject3 = (value) => {
2637
3165
  const proto = Object.getPrototypeOf(value);
2638
3166
  return proto === Object.prototype || proto === null;
2639
3167
  };
@@ -2662,6 +3190,7 @@ function dehydrate(value, registry, path, seen = /* @__PURE__ */ new Set()) {
2662
3190
  seen.add(object);
2663
3191
  try {
2664
3192
  if (object instanceof Date) return [object.toISOString(), { s: "dt" }];
3193
+ if (object instanceof TemporaryUploadedFile) return [object.toData(), { s: "fil" }];
2665
3194
  if (Array.isArray(object)) return [object.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), { s: "arr" }];
2666
3195
  if (isModel(object)) {
2667
3196
  const name = classNameOf(registry, object.constructor);
@@ -2678,11 +3207,12 @@ function dehydrate(value, registry, path, seen = /* @__PURE__ */ new Set()) {
2678
3207
  const name = classNameOf(registry, first.constructor);
2679
3208
  return [null, { s: "elcln", class: name, keys: items.map((item) => item.getKey()) }];
2680
3209
  }
2681
- return [items.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), { s: "clctn" }];
3210
+ const meta = object.constructor === Collection ? { s: "clctn" } : { s: "clctn", class: classNameOf(registry, object.constructor) };
3211
+ return [items.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), meta];
2682
3212
  }
2683
- if (isPlainObject2(object)) {
3213
+ if (isPlainObject3(object)) {
2684
3214
  const out = {};
2685
- for (const [key, item] of Object.entries(object)) out[key] = dehydrate(item, registry, `${path}.${key}`, seen);
3215
+ for (const [key2, item] of Object.entries(object)) out[key2] = dehydrate(item, registry, `${path}.${key2}`, seen);
2686
3216
  return [out, { s: "obj" }];
2687
3217
  }
2688
3218
  const type = object.constructor?.name || "object";
@@ -2697,6 +3227,11 @@ function modelClass(registry, name) {
2697
3227
  if (typeof name !== "string" || !registry.has(name)) throw new LiveError(`Unable to hydrate the [${String(name)}] model: it is not registered.`);
2698
3228
  return registry.resolve(name);
2699
3229
  }
3230
+ function collectionClass(registry, name) {
3231
+ const cls = registry.has(name) ? registry.resolve(name) : void 0;
3232
+ if (typeof cls !== "function" || !(cls.prototype instanceof Collection)) throw new LiveError(`Unable to hydrate the [${name}] collection: it is not registered.`);
3233
+ return cls;
3234
+ }
2700
3235
  async function hydrate(value, registry) {
2701
3236
  if (!isTuple(value)) return value;
2702
3237
  const [inner, meta] = value;
@@ -2707,29 +3242,36 @@ async function hydrate(value, registry) {
2707
3242
  return new Date(String(inner));
2708
3243
  case "arr":
2709
3244
  return Promise.all(inner.map((item) => hydrate(item, registry)));
2710
- case "clctn":
2711
- return new Collection(await Promise.all(inner.map((item) => hydrate(item, registry))));
3245
+ case "clctn": {
3246
+ const items = await Promise.all(inner.map((item) => hydrate(item, registry)));
3247
+ return meta.class === void 0 ? new Collection(items) : new (collectionClass(registry, meta.class))(items);
3248
+ }
2712
3249
  case "obj": {
2713
3250
  const out = {};
2714
- for (const [key, item] of Object.entries(inner)) {
2715
- Object.defineProperty(out, key, { value: await hydrate(item, registry), enumerable: true, writable: true, configurable: true });
3251
+ for (const [key2, item] of Object.entries(inner)) {
3252
+ Object.defineProperty(out, key2, { value: await hydrate(item, registry), enumerable: true, writable: true, configurable: true });
2716
3253
  }
2717
3254
  return out;
2718
3255
  }
2719
3256
  case "mdl":
2720
3257
  return modelClass(registry, meta.class).findOrFail(meta.key);
3258
+ case "fil":
3259
+ if (!isTemporaryFileData(inner)) throw new LiveError("Unable to hydrate a temporary upload: its data is incomplete.");
3260
+ return new TemporaryUploadedFile(inner);
2721
3261
  case "elcln": {
2722
3262
  const cls = modelClass(registry, meta.class);
2723
3263
  const keys = meta.keys ?? [];
2724
- const keyName = new cls().getKeyName();
3264
+ const model = new cls();
3265
+ const keyName = model.getKeyName();
2725
3266
  const found = [...await cls.query().whereIn(keyName, keys).get()];
2726
- const byKey = new Map(found.map((model) => [String(model.getKey()), model]));
2727
- return new Collection(keys.map((key) => byKey.get(String(key))).filter((model) => model !== void 0));
3267
+ const byKey = new Map(found.map((item) => [String(item.getKey()), item]));
3268
+ const models = keys.map((key2) => byKey.get(String(key2))).filter((item) => item !== void 0);
3269
+ return model.newCollection ? model.newCollection(models) : new Collection(models);
2728
3270
  }
2729
3271
  }
2730
3272
  throw new LiveError(`Unknown snapshot value type [${String(meta.s)}].`);
2731
3273
  }
2732
- var HOOKS = /* @__PURE__ */ new Set(["constructor", "render", "mount", "boot", "booted", "hydrate", "dehydrate", "updating", "updated", "rendering", "rendered", "exception"]);
3274
+ var HOOKS = /* @__PURE__ */ new Set(["constructor", "render", "placeholder", "mount", "boot", "booted", "hydrate", "dehydrate", "updating", "updated", "rendering", "rendered", "exception"]);
2733
3275
  var HOOK_PREFIX = /^(updating|updated|hydrate|dehydrate)[A-Z_]/;
2734
3276
  var UNSAFE_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
2735
3277
  var baseApi;
@@ -2782,14 +3324,24 @@ var ErrorBag = class {
2782
3324
  this.messages = messages;
2783
3325
  }
2784
3326
  messages;
2785
- has(key) {
2786
- return (this.messages[key]?.length ?? 0) > 0;
3327
+ /** The field's own messages: a field named `constructor` or `toString` doesn't reach Object's. */
3328
+ #own(key2) {
3329
+ return Object.hasOwn(this.messages, key2) ? this.messages[key2] : void 0;
3330
+ }
3331
+ /** Adds to the component's errors, as addError() does. */
3332
+ add(key2, message) {
3333
+ const messages = this.#own(key2);
3334
+ if (messages !== void 0) messages.push(message);
3335
+ else Object.defineProperty(this.messages, key2, { value: [message], enumerable: true, writable: true, configurable: true });
3336
+ }
3337
+ has(key2) {
3338
+ return (this.#own(key2)?.length ?? 0) > 0;
2787
3339
  }
2788
- first(key) {
2789
- return key === void 0 ? this.all()[0] : this.messages[key]?.[0];
3340
+ first(key2) {
3341
+ return key2 === void 0 ? this.all()[0] : this.#own(key2)?.[0];
2790
3342
  }
2791
- get(key) {
2792
- return [...this.messages[key] ?? []];
3343
+ get(key2) {
3344
+ return [...this.#own(key2) ?? []];
2793
3345
  }
2794
3346
  all() {
2795
3347
  return Object.values(this.messages).flat();
@@ -2804,7 +3356,7 @@ var ErrorBag = class {
2804
3356
  return this.all().length;
2805
3357
  }
2806
3358
  toObject() {
2807
- return Object.fromEntries(Object.entries(this.messages).map(([key, messages]) => [key, [...messages]]));
3359
+ return Object.fromEntries(Object.entries(this.messages).map(([key2, messages]) => [key2, [...messages]]));
2808
3360
  }
2809
3361
  };
2810
3362
  var PendingEvent = class {
@@ -2831,6 +3383,13 @@ var Component = class {
2831
3383
  static layout;
2832
3384
  /** The page title passed to the layout as `title`. */
2833
3385
  static title;
3386
+ /**
3387
+ * Livewire's #[Lazy]: every embed (and the full page) renders the placeholder first and mounts once the browser shows
3388
+ * it. `<Live component={…} lazy={false} />` mounts one at once. The placeholder is the component's
3389
+ * `placeholder(params)` method (TSX), else the `live.lazy_placeholder` view, else an empty `<div>`: give it the root
3390
+ * element the component renders.
3391
+ */
3392
+ static lazy;
2834
3393
  /** Static fallbacks for runtimes without decorators: { event: 'method' }, locked names, computed getter names. */
2835
3394
  static listeners;
2836
3395
  static locked;
@@ -2854,12 +3413,18 @@ var Component = class {
2854
3413
  if (options.navigate === true) state.redirectUsingNavigate = true;
2855
3414
  else delete state.redirectUsingNavigate;
2856
3415
  }
2857
- redirectRoute(name, parameters, options = {}) {
3416
+ /**
3417
+ * redirect() to a named route: `this.redirectRoute('posts.show', post, { navigate: true })`. Once `oriel
3418
+ * route:types` has run, the name and the route's parameters are checked, as route()'s are.
3419
+ */
3420
+ redirectRoute(name, ...args) {
3421
+ const [parameters, options = {}] = args;
2858
3422
  this.redirect(inject(UrlGenerator).route(name, parameters), options);
2859
3423
  }
2860
3424
  /**
2861
- * Validates the public state. On success clears the error bag and returns the validated data; on failure fills the
2862
- * error bag and throws (the rest of the action is skipped and the component re-renders with `errors`).
3425
+ * Validates the public state. On success clears the error bag and returns the validated data, typed from the rules
3426
+ * (`await this.validate({ title: 'required|string' })` is `{ title: string }`); on failure fills the error bag and
3427
+ * throws (the rest of the action is skipped and the component re-renders with `errors`).
2863
3428
  */
2864
3429
  async validate(rules, messages, attributes) {
2865
3430
  const validator = inject(ValidationFactory).make(Object.fromEntries(Object.entries(this)), rules, messages, attributes);
@@ -2873,15 +3438,14 @@ var Component = class {
2873
3438
  throw error;
2874
3439
  }
2875
3440
  }
2876
- addError(key, message) {
2877
- ;
2878
- (stateOf(this).errors[key] ??= []).push(message);
3441
+ addError(key2, message) {
3442
+ this.errors.add(key2, message);
2879
3443
  }
2880
3444
  /** Clears every error, or only those of `keys`. */
2881
3445
  resetErrorBag(...keys) {
2882
3446
  const state = stateOf(this);
2883
3447
  if (keys.length === 0) state.errors = {};
2884
- else for (const key of keys) delete state.errors[key];
3448
+ else for (const key2 of keys) delete state.errors[key2];
2885
3449
  }
2886
3450
  get errors() {
2887
3451
  return new ErrorBag(stateOf(this).errors);
@@ -2925,12 +3489,12 @@ function trackFlash(session, { saved = true } = {}) {
2925
3489
  return (redirected) => {
2926
3490
  if (redirected) return;
2927
3491
  const flashed = session.get("_flash.new", []);
2928
- const fresh = flashed.filter((key) => !before.has(key));
3492
+ const fresh = flashed.filter((key2) => !before.has(key2));
2929
3493
  if (fresh.length === 0) return;
2930
- session.put("_flash.new", flashed.filter((key) => before.has(key)));
3494
+ session.put("_flash.new", flashed.filter((key2) => before.has(key2)));
2931
3495
  if (!saved) return session.forget(...fresh);
2932
3496
  const old = session.get("_flash.old", []);
2933
- session.put("_flash.old", [...old, ...fresh.filter((key) => !old.includes(key))]);
3497
+ session.put("_flash.old", [...old, ...fresh.filter((key2) => !old.includes(key2))]);
2934
3498
  };
2935
3499
  }
2936
3500
  var ESCAPES = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#039;" };
@@ -2985,20 +3549,250 @@ function insertAttributesIntoRoot(html, attributes) {
2985
3549
  const rendered = Object.entries(attributes).map(([name, value]) => ` ${name}="${escapeAttribute2(value)}"`).join("");
2986
3550
  return match[1] + rendered + source.slice(match[1].length);
2987
3551
  }
3552
+ function canonicalJson(value) {
3553
+ if (Array.isArray(value)) return `[${value.map((item) => canonicalJson(item === void 0 ? null : item)).join(",")}]`;
3554
+ if (typeof value === "object" && value !== null) {
3555
+ const entries = Object.keys(value).sort().filter((key2) => value[key2] !== void 0).map((key2) => `${JSON.stringify(key2)}:${canonicalJson(value[key2])}`);
3556
+ return `{${entries.join(",")}}`;
3557
+ }
3558
+ return JSON.stringify(value);
3559
+ }
3560
+ function checksum(key2, payload) {
3561
+ return createHmac("sha256", key2).update(canonicalJson({ data: payload.data, memo: payload.memo })).digest("hex");
3562
+ }
3563
+ function verifyChecksum(key2, payload, given) {
3564
+ if (typeof given !== "string") return false;
3565
+ const expected = Buffer.from(checksum(key2, payload), "utf8");
3566
+ const actual = Buffer.from(given, "utf8");
3567
+ return expected.length === actual.length && timingSafeEqual(expected, actual);
3568
+ }
3569
+ function signatureOf(key2, purpose, payload) {
3570
+ return createHmac("sha256", key2).update(`${purpose}|${payload}`).digest("base64url");
3571
+ }
3572
+ function signValue(key2, purpose, value) {
3573
+ const payload = Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
3574
+ return `${payload}.${signatureOf(key2, purpose, payload)}`;
3575
+ }
3576
+ function verifiedValue(key2, purpose, token2) {
3577
+ if (typeof token2 !== "string") return void 0;
3578
+ const dot = token2.lastIndexOf(".");
3579
+ if (dot <= 0) return void 0;
3580
+ const payload = token2.slice(0, dot);
3581
+ const expected = Buffer.from(signatureOf(key2, purpose, payload), "utf8");
3582
+ const given = Buffer.from(token2.slice(dot + 1), "utf8");
3583
+ if (expected.length !== given.length || !timingSafeEqual(expected, given)) return void 0;
3584
+ try {
3585
+ return JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
3586
+ } catch {
3587
+ return void 0;
3588
+ }
3589
+ }
3590
+ var UPLOAD_URI = "/oriel/live/upload-file";
3591
+ var PREVIEW_URI = "/oriel/live/preview-file";
3592
+ var REFERENCE = "live.upload";
3593
+ var DAY = 24 * 36e5;
3594
+ var PREVIEW_TYPES = {
3595
+ png: "image/png",
3596
+ gif: "image/gif",
3597
+ bmp: "image/bmp",
3598
+ svg: "image/svg+xml",
3599
+ jpg: "image/jpeg",
3600
+ jpeg: "image/jpeg",
3601
+ webp: "image/webp",
3602
+ avif: "image/avif",
3603
+ wav: "audio/wav",
3604
+ mp3: "audio/mpeg",
3605
+ mpga: "audio/mpeg",
3606
+ m4a: "audio/mp4",
3607
+ wma: "audio/x-ms-wma",
3608
+ mp4: "video/mp4",
3609
+ mov: "video/quicktime",
3610
+ avi: "video/x-msvideo",
3611
+ wmv: "video/x-ms-wmv"
3612
+ };
3613
+ var key = () => inject(Encrypter).getKey();
3614
+ function temporaryFileReference(file) {
3615
+ return signValue(key(), REFERENCE, file.toData());
3616
+ }
3617
+ function temporaryFileFromReference(reference, component) {
3618
+ const data = verifiedValue(key(), REFERENCE, reference);
3619
+ if (!isTemporaryFileData(data)) throw new CorruptComponentPayloadError(component);
3620
+ return new TemporaryUploadedFile(data);
3621
+ }
3622
+ function uploadRules() {
3623
+ const rules = uploadSetting("rules");
3624
+ return Array.isArray(rules) ? [...rules] : rules.split("|");
3625
+ }
3626
+ function uploadMiddleware() {
3627
+ const configured = [uploadSetting("middleware")].flat();
3628
+ return configured.includes("web") ? configured : ["web", ...configured];
3629
+ }
3630
+ async function storeTemporaryFiles(files) {
3631
+ const disk = temporaryDisk();
3632
+ const directory = temporaryDirectory();
3633
+ return Promise.all(
3634
+ files.map(async (file) => {
3635
+ const extension = file.extension();
3636
+ const filename = extension === "" ? Str.random(40) : `${Str.random(40)}.${extension}`;
3637
+ await disk.putFileAs(directory, file, filename);
3638
+ return new TemporaryUploadedFile({ filename, name: file.clientName, size: file.size, type: file.mimeType, extension });
3639
+ })
3640
+ );
3641
+ }
3642
+ async function uploadedFileFrom(file) {
3643
+ const detected = fileType_exports.detectFileType(new Uint8Array(await file.slice(0, fileType_exports.SNIFF_BYTES).arrayBuffer()));
3644
+ const name = file.name ?? "";
3645
+ const claimed = name.includes(".") ? name.slice(name.lastIndexOf(".") + 1).toLowerCase() : "";
3646
+ const recognised = new Set(fileType_exports.BINARY_AND_MARKUP_TYPES.flatMap(({ extensions }) => extensions));
3647
+ const extension = detected !== fileType_exports.TEXT && detected.extensions.length > 0 ? detected.extensions[0] : /^[a-z0-9]+$/.test(claimed) && !recognised.has(claimed) ? claimed : detected === fileType_exports.TEXT ? "txt" : "";
3648
+ return {
3649
+ clientName: name,
3650
+ mimeType: file.type === "" ? "application/octet-stream" : file.type,
3651
+ size: file.size,
3652
+ extension: () => extension,
3653
+ clientExtension: () => claimed,
3654
+ hashName: () => extension === "" ? Str.random(40) : `${Str.random(40)}.${extension}`,
3655
+ arrayBuffer: () => file.arrayBuffer(),
3656
+ text: () => file.text(),
3657
+ stream: () => file.stream(),
3658
+ store: () => Promise.reject(new Error("Store the temporary upload instead.")),
3659
+ storeAs: () => Promise.reject(new Error("Store the temporary upload instead."))
3660
+ };
3661
+ }
3662
+ function filesOf(request2) {
3663
+ const files = request2.all().files;
3664
+ if (files === void 0 || files === null) return [];
3665
+ if (Array.isArray(files)) return files;
3666
+ return typeof files === "object" && Object.getPrototypeOf(files) === Object.prototype ? Object.values(files) : [files];
3667
+ }
3668
+ async function handleUpload(request2) {
3669
+ if (!inject(UrlGenerator).hasValidSignature(request2, false)) throw new InvalidUploadSignatureError();
3670
+ const files = filesOf(request2);
3671
+ await inject(ValidationFactory).make({ files }, { "files.*": uploadRules() }).validated();
3672
+ const stored = await storeTemporaryFiles(files);
3673
+ return { paths: stored.map(temporaryFileReference) };
3674
+ }
3675
+ async function handlePreview(request2, filename) {
3676
+ if (!inject(UrlGenerator).hasValidSignature(request2, false)) throw new InvalidUploadSignatureError();
3677
+ const data = { filename, name: "", size: 0, type: "", extension: filename.includes(".") ? filename.slice(filename.lastIndexOf(".") + 1) : "" };
3678
+ if (!isTemporaryFileData(data)) throw new TemporaryFileNotFoundError(filename);
3679
+ const file = new TemporaryUploadedFile(data);
3680
+ const disk = temporaryDisk();
3681
+ if (!await disk.exists(file.path)) throw new TemporaryFileNotFoundError(filename);
3682
+ const bytes = await disk.getBytes(file.path);
3683
+ return new Response(bytes, {
3684
+ headers: {
3685
+ "Content-Type": PREVIEW_TYPES[data.extension] ?? "application/octet-stream",
3686
+ "Content-Length": String(bytes.byteLength),
3687
+ "Content-Disposition": `attachment; filename="${filename}"`,
3688
+ "Content-Security-Policy": "sandbox; default-src 'none'",
3689
+ "X-Content-Type-Options": "nosniff",
3690
+ "Cache-Control": "private, max-age=3600"
3691
+ }
3692
+ });
3693
+ }
3694
+ async function cleanupOldUploads() {
3695
+ if (uploadSetting("cleanup") === false) return;
3696
+ const config = inject(ConfigRepository);
3697
+ const disk = uploadSetting("disk") ?? config.get("filesystems.default", "local");
3698
+ if (config.get(`filesystems.disks.${disk}.driver`, void 0) === "s3") return;
3699
+ const storage = temporaryDisk();
3700
+ const cutoff = Date.now() - DAY;
3701
+ for (const path of await storage.files(temporaryDirectory())) {
3702
+ try {
3703
+ if ((await storage.lastModified(path)).getTime() < cutoff) await storage.delete(path);
3704
+ } catch {
3705
+ }
3706
+ }
3707
+ }
3708
+ var UPLOADS = /* @__PURE__ */ Symbol.for("oriel:live.uploads");
3709
+ var UPLOAD_CALLS = /* @__PURE__ */ new Set(["_startUpload", "_finishUpload", "_uploadErrored", "_removeUpload"]);
3710
+ function WithFileUploads(Base) {
3711
+ class WithFileUploadsMixin extends Base {
3712
+ }
3713
+ Object.defineProperty(WithFileUploadsMixin.prototype, UPLOADS, { value: true });
3714
+ return WithFileUploadsMixin;
3715
+ }
3716
+ function usesFileUploads(component) {
3717
+ return component[UPLOADS] === true;
3718
+ }
3719
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3720
+ var read = (component, path) => path.split(".").reduce((node, segment) => isRecord(node) || Array.isArray(node) ? node[segment] : void 0, component);
3721
+ function uploadErrors(name, errors, multiple) {
3722
+ if (typeof errors === "string") {
3723
+ try {
3724
+ const replaced = errors.replace(multiple ? /files/gi : /files\.0/gi, name);
3725
+ const bag = JSON.parse(replaced).errors;
3726
+ if (isRecord(bag) && Object.values(bag).every((messages) => Array.isArray(messages) && messages.every((message) => typeof message === "string"))) {
3727
+ return bag;
3728
+ }
3729
+ } catch {
3730
+ }
3731
+ }
3732
+ return { [name]: [`The ${name} failed to upload.`] };
3733
+ }
3734
+ async function runUploadCall(component, method, params, update) {
3735
+ if (!usesFileUploads(component)) {
3736
+ throw new LiveError(`Cannot handle file upload without [WithFileUploads] on the [${stateOf(component).name}] component class.`);
3737
+ }
3738
+ const [name, value, multiple] = [String(params[0] ?? ""), params[1], params[2] === true];
3739
+ switch (method) {
3740
+ case "_startUpload":
3741
+ component.skipRender();
3742
+ return inject(UrlGenerator).temporarySignedRoute("live.upload-file", uploadSetting("max_upload_time") * 60, void 0, false);
3743
+ case "_finishUpload": {
3744
+ await cleanupOldUploads();
3745
+ const files = (Array.isArray(value) ? value : []).map((reference) => temporaryFileFromReference(reference, stateOf(component).name));
3746
+ const current = read(component, name);
3747
+ const next = multiple ? files : Array.isArray(current) ? [...current, ...files] : files[0] ?? null;
3748
+ await update(name, next);
3749
+ return files.map((file) => file.filename);
3750
+ }
3751
+ case "_uploadErrored":
3752
+ throw new UploadValidationError(uploadErrors(name, value, multiple));
3753
+ case "_removeUpload": {
3754
+ const filename = String(value ?? "");
3755
+ const current = read(component, name);
3756
+ if (Array.isArray(current)) {
3757
+ const removed = current.filter((file) => file instanceof TemporaryUploadedFile && file.filename === filename);
3758
+ for (const file of removed) await file.delete();
3759
+ await update(
3760
+ name,
3761
+ current.filter((file) => !removed.includes(file))
3762
+ );
3763
+ } else if (current instanceof TemporaryUploadedFile && current.filename === filename) {
3764
+ await current.delete();
3765
+ await update(name, null);
3766
+ }
3767
+ return filename;
3768
+ }
3769
+ }
3770
+ throw new LiveError(`Unknown upload call [${method}].`);
3771
+ }
3772
+ async function uploadForTest(files) {
3773
+ try {
3774
+ await inject(ValidationFactory).make({ files: [...files] }, { "files.*": uploadRules() }).validated();
3775
+ } catch (error) {
3776
+ const errors = error.errors;
3777
+ if (error.kind !== "Validation" || !isRecord(errors)) throw error;
3778
+ return { errors: JSON.stringify({ errors }) };
3779
+ }
3780
+ return { references: (await storeTemporaryFiles(files)).map(temporaryFileReference) };
3781
+ }
2988
3782
  var fail = (message, actual, expected) => {
2989
3783
  throw new AssertionError({ message, actual, expected, operator: "live" });
2990
3784
  };
2991
3785
  var getPath = (target2, path) => path.split(".").reduce((node, segment) => typeof node === "object" && node !== null ? node[segment] : void 0, target2);
2992
3786
  var TestRequestScope = token("Testing.RequestScope");
2993
3787
  function requestScope2() {
2994
- const container = currentContainer() ?? Container.getInstance();
2995
- return container?.bound(TestRequestScope) === true ? container.make(TestRequestScope) : (run) => run();
3788
+ const container2 = currentContainer() ?? Container.getInstance();
3789
+ return container2?.bound(TestRequestScope) === true ? container2.make(TestRequestScope) : (run) => run();
2996
3790
  }
2997
3791
  function currentSession() {
2998
- const container = currentContainer();
2999
- if (container?.bound(Session) !== true) return void 0;
3792
+ const container2 = currentContainer();
3793
+ if (container2?.bound(Session) !== true) return void 0;
3000
3794
  try {
3001
- return container.make(Session);
3795
+ return container2.make(Session);
3002
3796
  } catch {
3003
3797
  return void 0;
3004
3798
  }
@@ -3018,11 +3812,14 @@ var Testable = class _Testable {
3018
3812
  #html;
3019
3813
  #effects;
3020
3814
  #instance;
3021
- /** Mounts `component` with `params`, as `<Live component={…} {...params} />` would. */
3815
+ /**
3816
+ * Mounts `component` with `params`, as `<Live component={…} {...params} />` would. A lazy component mounts at once
3817
+ * (Livewire's withoutLazyLoading()); `{ lazy: true }` renders its placeholder, and `call('__lazyLoad', …)` loads it.
3818
+ */
3022
3819
  static async create(component, params = {}) {
3023
3820
  const scope = requestScope2();
3024
3821
  const manager = inject(LiveManager);
3025
- return new _Testable(manager, scope, await scope(() => manager.mountComponent(component, params)));
3822
+ return new _Testable(manager, scope, await scope(() => manager.mountComponent(component, { lazy: false, ...params })));
3026
3823
  }
3027
3824
  /** The component instance after the last request. */
3028
3825
  instance() {
@@ -3040,9 +3837,24 @@ var Testable = class _Testable {
3040
3837
  effects() {
3041
3838
  return this.#effects;
3042
3839
  }
3840
+ /**
3841
+ * Sets public properties through an update. A file (a FakeFile) or an array of files is uploaded as `o:model` on a
3842
+ * file input does it: the upload rules, the temporary disk, then a TemporaryUploadedFile (or an array) in the
3843
+ * property, or the upload's errors on it (Livewire's).
3844
+ */
3043
3845
  async set(path, value) {
3846
+ const files = [value].flat();
3847
+ if (typeof path === "string" && files.length > 0 && files.every((file) => file instanceof Blob)) return this.upload(path, files, Array.isArray(value));
3044
3848
  return this.#update(typeof path === "string" ? { [path]: value } : path, []);
3045
3849
  }
3850
+ /** Uploads `files` into the property `name` (an array when `multiple`), as the browser's upload calls do. */
3851
+ async upload(name, files, multiple = false) {
3852
+ const info = files.map((file) => ({ name: file.name ?? "", size: file.size, type: file.type }));
3853
+ await this.#update({}, [{ method: "_startUpload", params: [name, info, multiple] }]);
3854
+ const outcome = await this.scope(async () => uploadForTest(await Promise.all(files.map((file) => uploadedFileFrom(file)))));
3855
+ if ("errors" in outcome) return this.#update({}, [{ method: "_uploadErrored", params: [name, outcome.errors, multiple] }]);
3856
+ return this.#update({}, [{ method: "_finishUpload", params: [name, outcome.references, multiple] }]);
3857
+ }
3046
3858
  async call(method, ...params) {
3047
3859
  return this.#update({}, [{ method, params }]);
3048
3860
  }
@@ -3120,14 +3932,14 @@ var Testable = class _Testable {
3120
3932
  const errors = stateOf(this.#instance).errors;
3121
3933
  const wanted = [keys].flat();
3122
3934
  if (wanted.length === 0 && Object.keys(errors).length === 0) fail("Component has no errors.", errors);
3123
- for (const key of wanted) if ((errors[key]?.length ?? 0) === 0) fail(`Component missing error: ${key}`, errors, key);
3935
+ for (const key2 of wanted) if ((errors[key2]?.length ?? 0) === 0) fail(`Component missing error: ${key2}`, errors, key2);
3124
3936
  return this;
3125
3937
  }
3126
3938
  /** No errors for the keys given (or none at all with no keys). */
3127
3939
  assertHasNoErrors(keys = []) {
3128
3940
  const errors = stateOf(this.#instance).errors;
3129
3941
  const wanted = [keys].flat();
3130
- const found = wanted.length === 0 ? Object.keys(errors).filter((key) => (errors[key]?.length ?? 0) > 0) : wanted.filter((key) => (errors[key]?.length ?? 0) > 0);
3942
+ const found = wanted.length === 0 ? Object.keys(errors).filter((key2) => (errors[key2]?.length ?? 0) > 0) : wanted.filter((key2) => (errors[key2]?.length ?? 0) > 0);
3131
3943
  if (found.length > 0) fail(`Component has errors: "${found.join('", "')}"`, errors);
3132
3944
  return this;
3133
3945
  }
@@ -3148,12 +3960,12 @@ var Testable = class _Testable {
3148
3960
  return this;
3149
3961
  }
3150
3962
  };
3151
- async function live(component, params = {}, key) {
3152
- const html = await inject(LiveManager).mount(component, params, key === void 0 ? void 0 : String(key));
3963
+ async function live(component, params = {}, key2) {
3964
+ const html = await inject(LiveManager).mount(component, params, key2 === void 0 ? void 0 : String(key2));
3153
3965
  return { toHtml: () => html };
3154
3966
  }
3155
- function LiveComponent({ component, "o:key": key, children: _children, ...params }) {
3156
- return live(component, params, key);
3967
+ function LiveComponent({ component, "o:key": key2, children: _children, ...params }) {
3968
+ return live(component, params, key2);
3157
3969
  }
3158
3970
  var Live = Object.assign(LiveComponent, {
3159
3971
  /** Registers a component under a name (components in app/Live are registered at boot). */
@@ -3165,22 +3977,38 @@ var Live = Object.assign(LiveComponent, {
3165
3977
  return Testable.create(component, params);
3166
3978
  }
3167
3979
  });
3168
- function canonicalJson(value) {
3169
- if (Array.isArray(value)) return `[${value.map((item) => canonicalJson(item === void 0 ? null : item)).join(",")}]`;
3170
- if (typeof value === "object" && value !== null) {
3171
- const entries = Object.keys(value).sort().filter((key) => value[key] !== void 0).map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`);
3172
- return `{${entries.join(",")}}`;
3980
+ var PURPOSE = "live.lazy";
3981
+ function lazyMode(cls, params) {
3982
+ const { lazy } = params;
3983
+ if (lazy === "on-load") return "on-load";
3984
+ if (lazy === true || lazy === void 0 && cls.lazy === true) return "intersect";
3985
+ return void 0;
3986
+ }
3987
+ async function placeholderHtml(container2, instance, params) {
3988
+ const method = instance.placeholder;
3989
+ const views = container2.make(ViewFactory);
3990
+ let output = typeof method === "function" ? await method.call(instance, params) : void 0;
3991
+ if (output === void 0) {
3992
+ const view = container2.bound(ConfigRepository) ? container2.make(ConfigRepository).get("live.lazy_placeholder", null) : null;
3993
+ output = view === null || view === void 0 ? "<div></div>" : views.make(view, params);
3173
3994
  }
3174
- return JSON.stringify(value);
3995
+ return (typeof output === "string" ? output : await views.renderToString(output)).trim();
3175
3996
  }
3176
- function checksum(key, payload) {
3177
- return createHmac("sha256", key).update(canonicalJson({ data: payload.data, memo: payload.memo })).digest("hex");
3997
+ function lazyPayload(key2, registry, load) {
3998
+ const params = Object.fromEntries(Object.entries(load.params).map(([name, value]) => [name, dehydrate(value, registry, `${load.name}.${name}`)]));
3999
+ return signValue(key2, PURPOSE, { ...load, params });
3178
4000
  }
3179
- function verifyChecksum(key, payload, given) {
3180
- if (typeof given !== "string") return false;
3181
- const expected = Buffer.from(checksum(key, payload), "utf8");
3182
- const actual = Buffer.from(given, "utf8");
3183
- return expected.length === actual.length && timingSafeEqual(expected, actual);
4001
+ async function lazyLoadOf(key2, registry, payload, component) {
4002
+ const load = verifiedValue(key2, PURPOSE, payload);
4003
+ if (load === void 0 || load.id !== component.id || load.name !== component.name || typeof load.tag !== "string" || typeof load.params !== "object" || load.params === null) {
4004
+ throw new CorruptComponentPayloadError(component.name);
4005
+ }
4006
+ return { params: await hydrate([load.params, { s: "obj" }], registry), tag: load.tag };
4007
+ }
4008
+ function assertSameRoot(name, placeholder, rendered) {
4009
+ if (placeholder !== rendered) {
4010
+ throw new LiveError(`The lazy component [${name}] renders a <${rendered}> where its placeholder has a <${placeholder}>: give both the same root element.`);
4011
+ }
3184
4012
  }
3185
4013
  function componentName(file) {
3186
4014
  return file.replace(/\.(tsx|ts|jsx|js|mts|mjs)$/, "").split(/[\\/]/).map((segment) => Str.kebab(segment)).join(".");
@@ -3198,9 +4026,9 @@ async function discover(directory) {
3198
4026
  for (const file of componentFiles(directory)) {
3199
4027
  const module = await import(
3200
4028
  /* @vite-ignore */
3201
- pathToFileURL2(join5(directory, file)).href
4029
+ pathToFileURL2(join6(directory, file)).href
3202
4030
  );
3203
- found.push([componentName(relative2(directory, join5(directory, file))), module.default]);
4031
+ found.push([componentName(relative2(directory, join6(directory, file))), module.default]);
3204
4032
  }
3205
4033
  return found;
3206
4034
  }
@@ -3218,7 +4046,7 @@ function routePath(path) {
3218
4046
  const trimmed = path.replace(/^\/+|\/+$/g, "").replace(/\/{2,}/g, "/");
3219
4047
  return trimmed === "" ? "" : `/${trimmed}`;
3220
4048
  }
3221
- function expand(references, aliases, groups, out = [], visiting = /* @__PURE__ */ new Set()) {
4049
+ function expand2(references, aliases, groups, out = [], visiting = /* @__PURE__ */ new Set()) {
3222
4050
  for (const reference of references) {
3223
4051
  if (typeof reference === "function") {
3224
4052
  out.push({ cls: reference, parameters: [] });
@@ -3229,7 +4057,7 @@ function expand(references, aliases, groups, out = [], visiting = /* @__PURE__ *
3229
4057
  if (index === -1 && Object.hasOwn(groups, name)) {
3230
4058
  if (visiting.has(name)) continue;
3231
4059
  visiting.add(name);
3232
- expand(groups[name], aliases, groups, out, visiting);
4060
+ expand2(groups[name], aliases, groups, out, visiting);
3233
4061
  visiting.delete(name);
3234
4062
  } else if (Object.hasOwn(aliases, name)) {
3235
4063
  out.push({ cls: aliases[name], parameters: index === -1 ? [] : reference.slice(index + 1).split(",") });
@@ -3258,17 +4086,17 @@ function persistentPass(router, persistent, method, path) {
3258
4086
  const aliases = router.getMiddleware();
3259
4087
  const groups = router.getMiddlewareGroups();
3260
4088
  const wanted = new Set(persistent.flatMap((name) => Object.hasOwn(aliases, name) ? [aliases[name]] : []));
3261
- const excluded = new Set(expand(route2.excludedMiddleware(), aliases, groups).map((entry) => entry.cls));
4089
+ const excluded = new Set(expand2(route2.excludedMiddleware(), aliases, groups).map((entry) => entry.cls));
3262
4090
  const ids = /* @__PURE__ */ new Map();
3263
4091
  const seen = /* @__PURE__ */ new Set();
3264
- const middleware = expand(route2.gatherMiddleware(), aliases, groups).filter((entry) => {
4092
+ const middleware = expand2(route2.gatherMiddleware(), aliases, groups).filter((entry) => {
3265
4093
  if (!ids.has(entry.cls)) ids.set(entry.cls, ids.size);
3266
- const key = `${ids.get(entry.cls)}:${entry.parameters.join(",")}`;
3267
- if (!wanted.has(entry.cls) || excluded.has(entry.cls) || seen.has(key)) return false;
3268
- seen.add(key);
4094
+ const key2 = `${ids.get(entry.cls)}:${entry.parameters.join(",")}`;
4095
+ if (!wanted.has(entry.cls) || excluded.has(entry.cls) || seen.has(key2)) return false;
4096
+ seen.add(key2);
3269
4097
  return true;
3270
4098
  });
3271
- const early = new Set(expand(["web", "api"], aliases, groups).map((entry) => entry.cls));
4099
+ const early = new Set(expand2(["web", "api"], aliases, groups).map((entry) => entry.cls));
3272
4100
  for (const name of ["auth", "guest"]) if (Object.hasOwn(aliases, name)) early.add(aliases[name]);
3273
4101
  const before = middleware.filter((entry) => early.has(entry.cls));
3274
4102
  const after = middleware.filter((entry) => !early.has(entry.cls));
@@ -3277,38 +4105,38 @@ function persistentPass(router, persistent, method, path) {
3277
4105
  function persistentMiddleware(router, persistent, method, path) {
3278
4106
  return [...persistentPass(router, persistent, method, path)?.middleware ?? []];
3279
4107
  }
3280
- function currentRoute(container) {
3281
- if (!container.bound(CurrentRoute)) return void 0;
4108
+ function currentRoute(container2) {
4109
+ if (!container2.bound(CurrentRoute)) return void 0;
3282
4110
  try {
3283
- return container.make(CurrentRoute);
4111
+ return container2.make(CurrentRoute);
3284
4112
  } catch {
3285
4113
  return void 0;
3286
4114
  }
3287
4115
  }
3288
- async function throughMiddleware(container, request2, pass, destination) {
3289
- const original2 = currentRoute(container);
4116
+ async function throughMiddleware(container2, request2, pass, destination) {
4117
+ const original2 = currentRoute(container2);
3290
4118
  const page = { route: pass.route, parameters: { ...pass.values }, middleware: [] };
3291
4119
  let swapped = false;
3292
4120
  const show = () => {
3293
- container.instance(CurrentRoute, page);
4121
+ container2.instance(CurrentRoute, page);
3294
4122
  swapped = true;
3295
4123
  };
3296
4124
  const restore = () => {
3297
4125
  if (!swapped) return;
3298
- if (original2 === void 0) container.forgetInstance(CurrentRoute);
3299
- else container.instance(CurrentRoute, original2);
4126
+ if (original2 === void 0) container2.forgetInstance(CurrentRoute);
4127
+ else container2.instance(CurrentRoute, original2);
3300
4128
  swapped = false;
3301
4129
  };
3302
4130
  let ran;
3303
4131
  const run = async (index) => {
3304
- if (index === pass.at) Object.assign(page.parameters, await pass.router.substituteBindings(pass.route, pass.values, container));
4132
+ if (index === pass.at) Object.assign(page.parameters, await pass.router.substituteBindings(pass.route, pass.values, container2));
3305
4133
  if (index === pass.middleware.length) {
3306
4134
  restore();
3307
4135
  ran = { result: await destination() };
3308
4136
  return new Response(null, { status: 204 });
3309
4137
  }
3310
4138
  const { cls, parameters } = pass.middleware[index];
3311
- const instance = container.make(cls);
4139
+ const instance = container2.make(cls);
3312
4140
  const next = () => run(index + 1);
3313
4141
  return instance.handle(request2, next, ...parameters);
3314
4142
  };
@@ -3377,18 +4205,19 @@ var livePaginationState = {
3377
4205
  }
3378
4206
  };
3379
4207
  var renderContext = new AsyncLocalStorage2();
4208
+ var loading = /* @__PURE__ */ new WeakMap();
3380
4209
  var pageRequest = new AsyncLocalStorage2();
3381
- function optional(container, token2) {
3382
- if (!container.bound(token2)) return void 0;
4210
+ function optional(container2, token2) {
4211
+ if (!container2.bound(token2)) return void 0;
3383
4212
  try {
3384
- return container.make(token2);
4213
+ return container2.make(token2);
3385
4214
  } catch {
3386
4215
  return void 0;
3387
4216
  }
3388
4217
  }
3389
- var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3390
- function setOwn(object, key, value) {
3391
- Object.defineProperty(object, key, { value, enumerable: true, writable: true, configurable: true });
4218
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4219
+ function setOwn(object, key2, value) {
4220
+ Object.defineProperty(object, key2, { value, enumerable: true, writable: true, configurable: true });
3392
4221
  }
3393
4222
  async function hook2(instance, name, ...args) {
3394
4223
  const method = instance[name];
@@ -3432,10 +4261,10 @@ var LiveComponentManager = class {
3432
4261
  return request2.header("X-Oriel-Live") === "true";
3433
4262
  }
3434
4263
  /** The first render: HTML with o:id, o:snapshot and o:effects on the root element. */
3435
- async mount(component, params = {}, key) {
4264
+ async mount(component, params = {}, key2) {
3436
4265
  const context = renderContext.getStore();
3437
4266
  if (context === void 0) return (await this.mountComponent(component, params)).html;
3438
- const childKey = key ?? `lw-${context.index++}`;
4267
+ const childKey = key2 ?? `lw-${context.index++}`;
3439
4268
  const existing = Object.hasOwn(context.previous, childKey) ? context.previous[childKey] : void 0;
3440
4269
  if (existing !== void 0) {
3441
4270
  setOwn(context.children, childKey, existing);
@@ -3445,7 +4274,11 @@ var LiveComponentManager = class {
3445
4274
  setOwn(context.children, childKey, [result.tag, result.snapshot.memo.id]);
3446
4275
  return result.html;
3447
4276
  }
3448
- async mountComponent(component, params = {}, existing) {
4277
+ /**
4278
+ * Mounts a component: boot(), the params into its properties, mount(params), then its first render. A lazy one
4279
+ * (`lazy` in the params, or `static lazy`) renders its placeholder instead and mounts when the browser loads it.
4280
+ */
4281
+ async mountComponent(component, embedParams = {}, existing) {
3449
4282
  const cls = typeof component === "string" ? this.resolve(component) : component;
3450
4283
  if (!isComponentClass(cls)) throw new LiveError(`[${cls.name}] is not a Live component: it must extend Component.`);
3451
4284
  const name = this.nameOf(cls);
@@ -3453,7 +4286,10 @@ var LiveComponentManager = class {
3453
4286
  const state = stateOf(instance);
3454
4287
  state.id = randomBytes2(15).toString("base64url");
3455
4288
  state.name = name;
4289
+ const { lazy: _lazy, ...params } = embedParams;
4290
+ const lazy = lazyMode(cls, embedParams);
3456
4291
  await hook2(instance, "boot");
4292
+ if (lazy !== void 0) return this.mountPlaceholder(instance, params, lazy);
3457
4293
  for (const [property, value] of Object.entries(params)) {
3458
4294
  if (Object.hasOwn(instance, property)) instance[property] = value;
3459
4295
  }
@@ -3470,6 +4306,30 @@ var LiveComponentManager = class {
3470
4306
  const attributes = { "o:snapshot": JSON.stringify(snapshot), "o:effects": JSON.stringify(effects), "o:id": state.id };
3471
4307
  return { instance, snapshot, effects, html: insertAttributesIntoRoot(html, attributes), tag };
3472
4308
  }
4309
+ /**
4310
+ * A lazy component's first render (Livewire's): its placeholder, with the state it has before mount() and the mount
4311
+ * params signed for the `__lazyLoad` call the browser makes when it shows it (`o:lazy`, or `o:lazy-on-load`).
4312
+ */
4313
+ async mountPlaceholder(instance, params, lazy) {
4314
+ const state = stateOf(instance);
4315
+ const request2 = optional(this.container(), HttpRequest);
4316
+ const page = pageRequest.getStore() ?? { path: request2?.path() ?? "", method: request2?.method() ?? "GET" };
4317
+ const memo = { id: state.id, name: state.name, path: page.path, method: page.method, children: {}, errors: {} };
4318
+ const html = await placeholderHtml(this.container(), instance, params);
4319
+ const tag = rootTag(html, state.name);
4320
+ const effects = this.effects(instance);
4321
+ const listeners = Object.keys(listenersOf(instance.constructor));
4322
+ if (listeners.length > 0) effects.listeners = listeners;
4323
+ const snapshot = await this.snapshot(instance, memo);
4324
+ const payload = lazyPayload(this.key(), this.registry(), { id: state.id, name: state.name, tag, params });
4325
+ const attributes = {
4326
+ "o:snapshot": JSON.stringify(snapshot),
4327
+ "o:effects": JSON.stringify(effects),
4328
+ "o:id": state.id,
4329
+ [lazy === "on-load" ? "o:lazy-on-load" : "o:lazy"]: payload
4330
+ };
4331
+ return { instance, snapshot, effects, html: insertAttributesIntoRoot(html, attributes), tag };
4332
+ }
3473
4333
  /** Applies each component's updates and calls, re-renders it and returns its new snapshot and effects. */
3474
4334
  async update(request2) {
3475
4335
  const components = [];
@@ -3482,6 +4342,7 @@ var LiveComponentManager = class {
3482
4342
  }
3483
4343
  async updateComponent(snapshotJson, updates, calls) {
3484
4344
  const snapshot = this.verify(snapshotJson);
4345
+ const load = await this.lazyLoad(snapshot.memo, updates, calls);
3485
4346
  const cls = this.resolve(snapshot.memo.name);
3486
4347
  const instance = this.container().make(cls);
3487
4348
  const state = stateOf(instance);
@@ -3496,13 +4357,21 @@ var LiveComponentManager = class {
3496
4357
  this.prepareComputed(instance);
3497
4358
  const returns = [];
3498
4359
  await renderPaginated(instance, this.pageUrl(snapshot.memo.path), async () => {
3499
- await hook2(instance, "hydrate");
4360
+ if (load === void 0) await hook2(instance, "hydrate");
3500
4361
  try {
4362
+ if (load !== void 0) {
4363
+ for (const [property, value] of Object.entries(load.params)) {
4364
+ if (Object.hasOwn(instance, property)) instance[property] = value;
4365
+ }
4366
+ loading.set(instance, load.tag);
4367
+ await hook2(instance, "mount", load.params);
4368
+ returns.push(void 0);
4369
+ }
3501
4370
  for (const [path, value] of Object.entries(updates)) await this.applyUpdate(instance, path, value);
3502
- for (const call of calls) returns.push(await this.call(instance, call.method, call.params));
4371
+ for (const call of load === void 0 ? calls : []) returns.push(await this.call(instance, call.method, call.params));
3503
4372
  } catch (error) {
3504
4373
  const errors = error.errors;
3505
- if (error.kind !== "Validation" || !isRecord(errors)) throw error;
4374
+ if (error.kind !== "Validation" || !isRecord2(errors)) throw error;
3506
4375
  state.errors = { ...errors };
3507
4376
  }
3508
4377
  });
@@ -3513,12 +4382,24 @@ var LiveComponentManager = class {
3513
4382
  if (!state.skipRender) {
3514
4383
  ;
3515
4384
  ({ html, tag } = await this.render(instance, memo, snapshot.memo.children));
4385
+ const placeholder = loading.get(instance);
4386
+ if (placeholder !== void 0) assertSameRoot(state.name, placeholder, tag);
3516
4387
  html = insertAttributesIntoRoot(html, { "o:id": state.id });
3517
4388
  effects.html = html;
3518
4389
  }
3519
4390
  if (calls.length > 0) effects.returns = returns;
3520
4391
  return { instance, snapshot: await this.snapshot(instance, memo), effects, html, tag };
3521
4392
  }
4393
+ /**
4394
+ * The mount params and placeholder tag of a `__lazyLoad` request, or undefined for any other update. The load comes
4395
+ * alone (no updates, no other call: the browser sends it in a request of its own) and its payload must be the one
4396
+ * signed for this component: anything else is refused as corrupt (a 403) before the component boots.
4397
+ */
4398
+ async lazyLoad(memo, updates, calls) {
4399
+ if (!calls.some((call) => call.method === "__lazyLoad")) return void 0;
4400
+ if (calls.length !== 1 || Object.keys(updates).length > 0) throw new CorruptComponentPayloadError(memo.name);
4401
+ return lazyLoadOf(this.key(), this.registry(), calls[0].params[0], { id: memo.id, name: memo.name });
4402
+ }
3522
4403
  /**
3523
4404
  * The route action of POST /oriel/live/update. Each component's update runs behind the persistent middleware of the
3524
4405
  * route that rendered its page (Livewire's): `auth`, `verified`, `guest` and `can`, with the page route's parameters
@@ -3528,17 +4409,17 @@ var LiveComponentManager = class {
3528
4409
  */
3529
4410
  async handle(request2) {
3530
4411
  if (!this.isLiveRequest(request2)) throw new InvalidLiveRequestError("Live update requests need the X-Oriel-Live header.");
3531
- const container = this.container();
3532
- const persistent = optional(container, ConfigRepository)?.get("live.persistent_middleware", PERSISTENT_MIDDLEWARE) ?? PERSISTENT_MIDDLEWARE;
4412
+ const container2 = this.container();
4413
+ const persistent = optional(container2, ConfigRepository)?.get("live.persistent_middleware", PERSISTENT_MIDDLEWARE) ?? PERSISTENT_MIDDLEWARE;
3533
4414
  const components = [];
3534
- const flashed = trackFlash(optional(container, Session));
4415
+ const flashed = trackFlash(optional(container2, Session));
3535
4416
  for (const component of this.payload(request2).components) {
3536
4417
  const { memo } = this.verify(component.snapshot);
3537
- const pass = persistentPass(container.make(Router), persistent, memo.method, memo.path);
4418
+ const pass = persistentPass(container2.make(Router), persistent, memo.method, memo.path);
3538
4419
  const update = () => this.updateComponent(component.snapshot, component.updates, component.calls);
3539
4420
  const result = await pageRequest.run(
3540
4421
  { path: memo.path, method: memo.method },
3541
- () => pass === void 0 || pass.middleware.length === 0 ? update() : throughMiddleware(container, request2, pass, update)
4422
+ () => pass === void 0 || pass.middleware.length === 0 ? update() : throughMiddleware(container2, request2, pass, update)
3542
4423
  );
3543
4424
  if (result instanceof Response) return result;
3544
4425
  components.push({ snapshot: JSON.stringify(result.snapshot), effects: result.effects });
@@ -3548,19 +4429,19 @@ var LiveComponentManager = class {
3548
4429
  }
3549
4430
  /** A full-page component (Route.get('/counter', Counter)): mounted with the route parameters, inside the layout. */
3550
4431
  async page(component) {
3551
- const container = this.container();
3552
- const params = { ...optional(container, CurrentRoute)?.parameters ?? {} };
4432
+ const container2 = this.container();
4433
+ const params = { ...optional(container2, CurrentRoute)?.parameters ?? {} };
3553
4434
  const cls = component.constructor;
3554
4435
  const result = await this.mountComponent(cls, params, component);
3555
4436
  const redirect2 = result.effects.redirect;
3556
4437
  if (redirect2 !== void 0) {
3557
- return optional(container, Redirector)?.to(redirect2) ?? new Response(null, { status: 302, headers: { Location: redirect2 } });
4438
+ return optional(container2, Redirector)?.to(redirect2) ?? new Response(null, { status: 302, headers: { Location: redirect2 } });
3558
4439
  }
3559
- const layout = cls.layout ?? optional(container, ConfigRepository)?.get("live.layout") ?? "components.layouts.app";
4440
+ const layout = cls.layout ?? optional(container2, ConfigRepository)?.get("live.layout") ?? "components.layouts.app";
3560
4441
  const html = result.html;
3561
4442
  const data = { children: { toHtml: () => html } };
3562
4443
  if (cls.title !== void 0) data.title = cls.title;
3563
- return container.make(ViewFactory).make(layout, data);
4444
+ return container2.make(ViewFactory).make(layout, data);
3564
4445
  }
3565
4446
  /** Parses and checks a snapshot: a bad checksum throws before anything is hydrated. */
3566
4447
  verify(snapshotJson) {
@@ -3570,13 +4451,13 @@ var LiveComponentManager = class {
3570
4451
  } catch {
3571
4452
  throw new CorruptComponentPayloadError();
3572
4453
  }
3573
- if (!isRecord(snapshot) || !isRecord(snapshot.data) || !isRecord(snapshot.memo)) throw new CorruptComponentPayloadError();
4454
+ if (!isRecord2(snapshot) || !isRecord2(snapshot.data) || !isRecord2(snapshot.memo)) throw new CorruptComponentPayloadError();
3574
4455
  const memo = snapshot.memo;
3575
4456
  const name = typeof memo.name === "string" ? memo.name : "unknown";
3576
- const key = this.key();
4457
+ const key2 = this.key();
3577
4458
  let valid;
3578
4459
  try {
3579
- valid = verifyChecksum(key, { data: snapshot.data, memo: snapshot.memo }, snapshot.checksum);
4460
+ valid = verifyChecksum(key2, { data: snapshot.data, memo: snapshot.memo }, snapshot.checksum);
3580
4461
  } catch {
3581
4462
  valid = false;
3582
4463
  }
@@ -3595,9 +4476,9 @@ var LiveComponentManager = class {
3595
4476
  } catch {
3596
4477
  throw new InvalidLiveRequestError("The Live update request body is not valid JSON.");
3597
4478
  }
3598
- const components = isRecord(body) ? body.components : void 0;
4479
+ const components = isRecord2(body) ? body.components : void 0;
3599
4480
  const valid = Array.isArray(components) && components.every(
3600
- (component) => isRecord(component) && typeof component.snapshot === "string" && isRecord(component.updates) && Array.isArray(component.calls) && component.calls.every((call) => isRecord(call) && typeof call.method === "string" && Array.isArray(call.params))
4481
+ (component) => isRecord2(component) && typeof component.snapshot === "string" && isRecord2(component.updates) && Array.isArray(component.calls) && component.calls.every((call) => isRecord2(call) && typeof call.method === "string" && Array.isArray(call.params))
3601
4482
  );
3602
4483
  if (!valid) throw new InvalidLiveRequestError("The Live update request body is malformed.");
3603
4484
  return body;
@@ -3616,10 +4497,10 @@ var LiveComponentManager = class {
3616
4497
  async applyUpdate(instance, path, value) {
3617
4498
  if (!isUpdatable(instance, path)) throw new PropertyNotUpdatableError(path, stateOf(instance).name);
3618
4499
  const [root, ...rest] = path.split(".");
3619
- const key = rest.length > 0 ? rest.join(".") : void 0;
4500
+ const key2 = rest.length > 0 ? rest.join(".") : void 0;
3620
4501
  const studly = Str.studly(root);
3621
4502
  await hook2(instance, "updating", path, value);
3622
- await hook2(instance, `updating${studly}`, value, key);
4503
+ await hook2(instance, `updating${studly}`, value, key2);
3623
4504
  const target2 = instance;
3624
4505
  if (rest.length === 0) target2[root] = value;
3625
4506
  else {
@@ -3627,20 +4508,20 @@ var LiveComponentManager = class {
3627
4508
  if (Array.isArray(node2) && !isArraySlot(node2, segment)) throw new PropertyNotUpdatableError(path, stateOf(instance).name);
3628
4509
  };
3629
4510
  let node = target2[root];
3630
- if (!isRecord(node) && !Array.isArray(node)) target2[root] = node = {};
4511
+ if (!isRecord2(node) && !Array.isArray(node)) target2[root] = node = {};
3631
4512
  for (const segment of rest.slice(0, -1)) {
3632
4513
  assertSlot(node, segment);
3633
4514
  const next = node[segment];
3634
- node = isRecord(next) || Array.isArray(next) ? next : node[segment] = {};
4515
+ node = isRecord2(next) || Array.isArray(next) ? next : node[segment] = {};
3635
4516
  }
3636
4517
  const last = rest.at(-1);
3637
4518
  assertSlot(node, last);
3638
4519
  Object.defineProperty(node, last, { value, enumerable: true, writable: true, configurable: true });
3639
4520
  }
3640
4521
  await hook2(instance, "updated", path, value);
3641
- await hook2(instance, `updated${studly}`, value, key);
4522
+ await hook2(instance, `updated${studly}`, value, key2);
3642
4523
  }
3643
- /** Runs one call from the browser: a magic action, an event, or a public method. Returns its return value. */
4524
+ /** Runs one call from the browser: a magic action, an event, an upload call, or a public method. Returns its return value. */
3644
4525
  async call(instance, method, params) {
3645
4526
  const name = stateOf(instance).name;
3646
4527
  switch (method) {
@@ -3650,7 +4531,7 @@ var LiveComponentManager = class {
3650
4531
  return this.applyUpdate(instance, String(params[0]), params[1]);
3651
4532
  case "$toggle": {
3652
4533
  const path = String(params[0]);
3653
- const current = path.split(".").reduce((node, segment) => isRecord(node) || Array.isArray(node) ? node[segment] : void 0, instance);
4534
+ const current = path.split(".").reduce((node, segment) => isRecord2(node) || Array.isArray(node) ? node[segment] : void 0, instance);
3654
4535
  return this.applyUpdate(instance, path, !current);
3655
4536
  }
3656
4537
  case "__dispatch": {
@@ -3658,11 +4539,12 @@ var LiveComponentManager = class {
3658
4539
  const listeners = listenersOf(instance.constructor);
3659
4540
  for (const listener of Object.hasOwn(listeners, String(event)) ? listeners[String(event)] : []) {
3660
4541
  if (!isCallable(instance, listener)) throw new MethodNotCallableError(listener, name);
3661
- await instance[listener](isRecord(detail) ? detail : {});
4542
+ await instance[listener](isRecord2(detail) ? detail : {});
3662
4543
  }
3663
4544
  return void 0;
3664
4545
  }
3665
4546
  }
4547
+ if (UPLOAD_CALLS.has(method)) return runUploadCall(instance, method, params, (path, value) => this.applyUpdate(instance, path, value));
3666
4548
  if (!isCallable(instance, method)) throw new MethodNotCallableError(method, name);
3667
4549
  return instance[method](...params);
3668
4550
  }
@@ -3756,9 +4638,9 @@ function registerLiveCommands(kernel, app) {
3756
4638
  const className = segments.at(-1);
3757
4639
  const config = app.bound(ConfigRepository) ? app.make(ConfigRepository) : void 0;
3758
4640
  const directory = config?.get("live.class_path") ?? app.appPath("Live");
3759
- const file = join23(directory, ...segments.slice(0, -1), `${className}.tsx`);
4641
+ const file = join24(directory, ...segments.slice(0, -1), `${className}.tsx`);
3760
4642
  const force = this.option("force") === true;
3761
- if (existsSync22(file) && !force) {
4643
+ if (existsSync23(file) && !force) {
3762
4644
  this.error(`Component [${relative22(app.basePath(), file)}] already exists.`);
3763
4645
  return 1;
3764
4646
  }
@@ -3766,13 +4648,13 @@ function registerLiveCommands(kernel, app) {
3766
4648
  await writeFile2(file, COMPONENT_STUB.replace("{{ class }}", () => className));
3767
4649
  this.info(`Component [${relative22(app.basePath(), file)}] created successfully.`);
3768
4650
  if (this.option("test") === true) {
3769
- const testFile = app.basePath(join23("tests", "Feature", "Live", ...segments.slice(0, -1), `${className}.test.ts`));
3770
- if (existsSync22(testFile) && !force) {
4651
+ const testFile = app.basePath(join24("tests", "Feature", "Live", ...segments.slice(0, -1), `${className}.test.ts`));
4652
+ if (existsSync23(testFile) && !force) {
3771
4653
  this.error(`Test [${relative22(app.basePath(), testFile)}] already exists.`);
3772
4654
  return 1;
3773
4655
  }
3774
4656
  const name = segments.map((segment) => Str.kebab(segment)).join(".");
3775
- 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);
4657
+ const test = TEST_STUB.replaceAll("{{ class }}", () => className).replace("{{ component }}", () => importPath(testFile, file)).replace("{{ testCase }}", () => importPath(testFile, app.basePath(join24("tests", "TestCase.ts")))).replace("{{ name }}", () => name);
3776
4658
  await mkdir2(dirname2(testFile), { recursive: true });
3777
4659
  await writeFile2(testFile, test);
3778
4660
  this.info(`Test [${relative22(app.basePath(), testFile)}] created successfully.`);
@@ -3785,13 +4667,16 @@ var LiveServiceProvider = class extends ServiceProvider {
3785
4667
  register() {
3786
4668
  this.mergeConfigFrom({ class_path: this.app.appPath("Live"), layout: "components.layouts.app", persistent_middleware: [...PERSISTENT_MIDDLEWARE] }, "live");
3787
4669
  this.app.singleton(LiveComponentManager, () => new LiveComponentManager(this.app));
3788
- this.app.singleton(LiveManager, (container) => container.make(LiveComponentManager));
4670
+ this.app.singleton(LiveManager, (container2) => container2.make(LiveComponentManager));
3789
4671
  this.app.singleton(PaginationState, () => livePaginationState);
3790
4672
  }
3791
4673
  async boot() {
3792
4674
  const manager = this.app.make(LiveComponentManager);
3793
4675
  if (this.app.bound(Router)) {
3794
- this.app.make(Router).post(UPDATE_URI, () => manager.handle(inject(HttpRequest))).middleware("web").name("live.update");
4676
+ const router = this.app.make(Router);
4677
+ router.post(UPDATE_URI, () => manager.handle(inject(HttpRequest))).middleware("web").name("live.update");
4678
+ router.post(UPLOAD_URI, () => handleUpload(inject(HttpRequest))).middleware(uploadMiddleware()).name("live.upload-file");
4679
+ router.get(`${PREVIEW_URI}/{filename}`, (filename) => handlePreview(inject(HttpRequest), filename)).middleware("web").name("live.preview-file");
3795
4680
  }
3796
4681
  if (this.app.bound(ConsoleKernel)) registerLiveCommands(this.app.make(ConsoleKernel), this.app);
3797
4682
  const directory = this.app.make(ConfigRepository).get("live.class_path", this.app.appPath("Live"));
@@ -3839,7 +4724,7 @@ function Pagination({ paginator, class: className }) {
3839
4724
  import { AssertionError as AssertionError2 } from "assert";
3840
4725
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
3841
4726
  import { existsSync as existsSync6 } from "fs";
3842
- import { join as join6 } from "path";
4727
+ import { join as join7 } from "path";
3843
4728
  import { createHmac as createHmac2 } from "crypto";
3844
4729
  import { randomUUID } from "crypto";
3845
4730
  import { randomUUID as randomUUID2 } from "crypto";
@@ -4000,7 +4885,7 @@ function equals(a, b) {
4000
4885
  function canonical(value) {
4001
4886
  if (Array.isArray(value)) return value.map(canonical);
4002
4887
  if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype) {
4003
- return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical(value[key])]));
4888
+ return Object.fromEntries(Object.keys(value).sort().map((key2) => [key2, canonical(value[key2])]));
4004
4889
  }
4005
4890
  return value;
4006
4891
  }
@@ -4008,20 +4893,20 @@ function isSubset(subset, actual) {
4008
4893
  if (typeof subset !== "object" || subset === null) return equals(subset, actual);
4009
4894
  if (typeof actual !== "object" || actual === null) return false;
4010
4895
  if (Array.isArray(subset) !== Array.isArray(actual)) return false;
4011
- return Object.entries(subset).every(([key, value]) => Object.hasOwn(actual, key) && isSubset(value, actual[key]));
4896
+ return Object.entries(subset).every(([key2, value]) => Object.hasOwn(actual, key2) && isSubset(value, actual[key2]));
4012
4897
  }
4013
4898
  function* objectsIn(value) {
4014
4899
  if (typeof value !== "object" || value === null) return;
4015
4900
  if (!Array.isArray(value)) yield value;
4016
4901
  for (const child of Object.values(value)) yield* objectsIn(child);
4017
4902
  }
4018
- function containsPair(tree, key, expected) {
4019
- for (const object of objectsIn(tree)) if (Object.hasOwn(object, key) && isSubset(expected, object[key])) return true;
4903
+ function containsPair(tree, key2, expected) {
4904
+ for (const object of objectsIn(tree)) if (Object.hasOwn(object, key2) && isSubset(expected, object[key2])) return true;
4020
4905
  return false;
4021
4906
  }
4022
4907
  function assertStructure(structure, data, path = "") {
4023
- const at = (key) => path === "" ? key : `${path}.${key}`;
4024
- const hasKey = (key) => typeof data === "object" && data !== null && Object.hasOwn(data, key);
4908
+ const at = (key2) => path === "" ? key2 : `${path}.${key2}`;
4909
+ const hasKey = (key2) => typeof data === "object" && data !== null && Object.hasOwn(data, key2);
4025
4910
  if (Array.isArray(structure)) {
4026
4911
  for (const item of structure) {
4027
4912
  if (typeof item === "string") assert(hasKey(item), `Failed asserting that the JSON has the key [${at(item)}].`, { actual: data });
@@ -4029,14 +4914,14 @@ function assertStructure(structure, data, path = "") {
4029
4914
  }
4030
4915
  return;
4031
4916
  }
4032
- for (const [key, nested] of Object.entries(structure)) {
4033
- if (key === "*") {
4917
+ for (const [key2, nested] of Object.entries(structure)) {
4918
+ if (key2 === "*") {
4034
4919
  assert(Array.isArray(data) || typeof data === "object" && data !== null, `Failed asserting that [${path || "the JSON"}] is a list.`, { actual: data });
4035
4920
  for (const [index, element] of Object.entries(data)) assertStructure(nested, element, at(index));
4036
4921
  continue;
4037
4922
  }
4038
- assert(hasKey(key), `Failed asserting that the JSON has the key [${at(key)}].`, { actual: data });
4039
- assertStructure(nested, data[key], at(key));
4923
+ assert(hasKey(key2), `Failed asserting that the JSON has the key [${at(key2)}].`, { actual: data });
4924
+ assertStructure(nested, data[key2], at(key2));
4040
4925
  }
4041
4926
  }
4042
4927
  var PendingCommand = class {
@@ -4239,14 +5124,14 @@ var TESTING_ENVIRONMENT = Object.freeze({
4239
5124
  SESSION_DRIVER: "array"
4240
5125
  });
4241
5126
  function applyEnvironment(values, env2 = process.env) {
4242
- for (const [key, value] of Object.entries(values)) env2[key] ??= value;
5127
+ for (const [key2, value] of Object.entries(values)) env2[key2] ??= value;
4243
5128
  }
4244
5129
  function clonePlain2(value) {
4245
5130
  if (Array.isArray(value)) return value.map((item) => clonePlain2(item));
4246
5131
  if (typeof value === "object" && value !== null) {
4247
5132
  const proto = Object.getPrototypeOf(value);
4248
5133
  if (proto !== Object.prototype && proto !== null) return value;
4249
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, clonePlain2(item)]));
5134
+ return Object.fromEntries(Object.entries(value).map(([key2, item]) => [key2, clonePlain2(item)]));
4250
5135
  }
4251
5136
  return value;
4252
5137
  }
@@ -4281,31 +5166,31 @@ var AssertableJson = class _AssertableJson {
4281
5166
  newScope(props, path) {
4282
5167
  return new _AssertableJson(props, path);
4283
5168
  }
4284
- dotPath(key = "") {
4285
- if (this.path === void 0) return key;
4286
- return key === "" ? this.path : `${this.path}.${key}`;
5169
+ dotPath(key2 = "") {
5170
+ if (this.path === void 0) return key2;
5171
+ return key2 === "" ? this.path : `${this.path}.${key2}`;
4287
5172
  }
4288
5173
  /** The value at `key` in this scope, or the whole scope. */
4289
- prop(key) {
4290
- if (key !== void 0 && this.hasOwnKey(key)) return this.props[key];
4291
- return Arr.get(this.props, key);
5174
+ prop(key2) {
5175
+ if (key2 !== void 0 && this.hasOwnKey(key2)) return this.props[key2];
5176
+ return Arr.get(this.props, key2);
4292
5177
  }
4293
5178
  /** A literal key of this scope wins over a dot path, like Laravel's Arr::get (keys such as 'items.0.name'). */
4294
- hasOwnKey(key) {
4295
- return typeof this.props === "object" && this.props !== null && Object.hasOwn(this.props, key);
5179
+ hasOwnKey(key2) {
5180
+ return typeof this.props === "object" && this.props !== null && Object.hasOwn(this.props, key2);
4296
5181
  }
4297
- exists(key) {
4298
- return this.hasOwnKey(key) || Arr.has(this.props, key);
5182
+ exists(key2) {
5183
+ return this.hasOwnKey(key2) || Arr.has(this.props, key2);
4299
5184
  }
4300
- interactsWith(key) {
4301
- const prop = this.hasOwnKey(String(key)) ? String(key) : String(key).split(".")[0];
5185
+ interactsWith(key2) {
5186
+ const prop = this.hasOwnKey(String(key2)) ? String(key2) : String(key2).split(".")[0];
4302
5187
  if (!this.interactedKeys.includes(prop)) this.interactedKeys.push(prop);
4303
5188
  }
4304
5189
  /** Every property of the scope was checked (called when a scope ends). */
4305
5190
  interacted() {
4306
5191
  const props = this.prop();
4307
5192
  const keys = typeof props === "object" && props !== null ? Object.keys(props) : [];
4308
- const unexpected = keys.filter((key) => !this.interactedKeys.includes(key));
5193
+ const unexpected = keys.filter((key2) => !this.interactedKeys.includes(key2));
4309
5194
  assert(
4310
5195
  unexpected.length === 0,
4311
5196
  this.path === void 0 ? "Unexpected properties were found on the root level." : `Unexpected properties were found in scope [${this.path}].`,
@@ -4320,9 +5205,9 @@ var AssertableJson = class _AssertableJson {
4320
5205
  }
4321
5206
  // Has ------------------------------------------------------------------------------------------------------
4322
5207
  /** has('key'), has('key', 3), has('key', json => …), has('key', 3, first => …), or has(3) for the root size. */
4323
- has(key, length, callback) {
4324
- if (typeof key === "number" && length === void 0) return this.count(key);
4325
- const name = String(key);
5208
+ has(key2, length, callback) {
5209
+ if (typeof key2 === "number" && length === void 0) return this.count(key2);
5210
+ const name = String(key2);
4326
5211
  assert(this.exists(name), `Property [${this.dotPath(name)}] does not exist.`);
4327
5212
  this.interactsWith(name);
4328
5213
  if (callback !== void 0) {
@@ -4338,105 +5223,105 @@ var AssertableJson = class _AssertableJson {
4338
5223
  /** hasAll(['a', 'b']) or hasAll({ items: 3 }). */
4339
5224
  hasAll(keys) {
4340
5225
  if (typeof keys === "string" || Array.isArray(keys)) {
4341
- for (const key of [keys].flat()) this.has(key);
5226
+ for (const key2 of [keys].flat()) this.has(key2);
4342
5227
  return this;
4343
5228
  }
4344
- for (const [key, length] of Object.entries(keys)) this.has(key, length);
5229
+ for (const [key2, length] of Object.entries(keys)) this.has(key2, length);
4345
5230
  return this;
4346
5231
  }
4347
5232
  hasAny(keys) {
4348
5233
  const list = [keys].flat();
4349
- const present = list.filter((key) => this.exists(key));
4350
- assert(present.length > 0, `None of properties [${list.map((key) => this.dotPath(key)).join(", ")}] exist.`);
4351
- for (const key of present) this.interactsWith(key);
5234
+ const present = list.filter((key2) => this.exists(key2));
5235
+ assert(present.length > 0, `None of properties [${list.map((key2) => this.dotPath(key2)).join(", ")}] exist.`);
5236
+ for (const key2 of present) this.interactsWith(key2);
4352
5237
  return this;
4353
5238
  }
4354
- missing(key) {
4355
- assert(!this.exists(key), `Property [${this.dotPath(key)}] was found while it was expected to be missing.`);
5239
+ missing(key2) {
5240
+ assert(!this.exists(key2), `Property [${this.dotPath(key2)}] was found while it was expected to be missing.`);
4356
5241
  return this;
4357
5242
  }
4358
5243
  missingAll(keys) {
4359
- for (const key of [keys].flat()) this.missing(key);
5244
+ for (const key2 of [keys].flat()) this.missing(key2);
4360
5245
  return this;
4361
5246
  }
4362
5247
  /** count(3) checks this scope's size; count('items', 3) a property's. */
4363
- count(key, length) {
4364
- if (typeof key === "number") {
5248
+ count(key2, length) {
5249
+ if (typeof key2 === "number") {
4365
5250
  const size2 = sizeOf(this.prop());
4366
- assert(size2 === key, this.path === void 0 ? "Root level does not have the expected size." : `Property [${this.path}] does not have the expected size.`, { actual: size2, expected: key });
5251
+ assert(size2 === key2, this.path === void 0 ? "Root level does not have the expected size." : `Property [${this.path}] does not have the expected size.`, { actual: size2, expected: key2 });
4367
5252
  return this;
4368
5253
  }
4369
- this.has(key);
4370
- const size = sizeOf(this.prop(key));
4371
- assert(size === length, `Property [${this.dotPath(key)}] does not have the expected size.`, { actual: size, expected: length });
5254
+ this.has(key2);
5255
+ const size = sizeOf(this.prop(key2));
5256
+ assert(size === length, `Property [${this.dotPath(key2)}] does not have the expected size.`, { actual: size, expected: length });
4372
5257
  return this;
4373
5258
  }
4374
- countBetween(key, min, max) {
4375
- this.has(key);
4376
- const size = sizeOf(this.prop(key));
4377
- assert(size >= min && size <= max, `Property [${this.dotPath(key)}] size is not between ${min} and ${max}.`, { actual: size });
5259
+ countBetween(key2, min, max) {
5260
+ this.has(key2);
5261
+ const size = sizeOf(this.prop(key2));
5262
+ assert(size >= min && size <= max, `Property [${this.dotPath(key2)}] size is not between ${min} and ${max}.`, { actual: size });
4378
5263
  return this;
4379
5264
  }
4380
5265
  // Matching -------------------------------------------------------------------------------------------------
4381
5266
  /** where('name', 'Ann'), or where('name', value => value.startsWith('A')). Objects compare ignoring key order. */
4382
- where(key, expected) {
4383
- this.has(key);
4384
- const actual = this.prop(key);
5267
+ where(key2, expected) {
5268
+ this.has(key2);
5269
+ const actual = this.prop(key2);
4385
5270
  if (typeof expected === "function") {
4386
- assert(expected(actual), `Property [${this.dotPath(key)}] was marked as invalid using a closure.`);
5271
+ assert(expected(actual), `Property [${this.dotPath(key2)}] was marked as invalid using a closure.`);
4387
5272
  return this;
4388
5273
  }
4389
5274
  const normalized = toPlain(expected);
4390
- assert(equals(normalized, actual), `Property [${this.dotPath(key)}] does not match the expected value.`, { actual, expected: normalized });
5275
+ assert(equals(normalized, actual), `Property [${this.dotPath(key2)}] does not match the expected value.`, { actual, expected: normalized });
4391
5276
  return this;
4392
5277
  }
4393
- whereNot(key, expected) {
4394
- this.has(key);
4395
- const actual = this.prop(key);
5278
+ whereNot(key2, expected) {
5279
+ this.has(key2);
5280
+ const actual = this.prop(key2);
4396
5281
  if (typeof expected === "function") {
4397
- assert(!expected(actual), `Property [${this.dotPath(key)}] was marked as invalid using a closure.`);
5282
+ assert(!expected(actual), `Property [${this.dotPath(key2)}] was marked as invalid using a closure.`);
4398
5283
  return this;
4399
5284
  }
4400
- assert(!equals(toPlain(expected), actual), `Property [${this.dotPath(key)}] contains a value that should be missing: [${key}, ${format(expected)}]`);
5285
+ assert(!equals(toPlain(expected), actual), `Property [${this.dotPath(key2)}] contains a value that should be missing: [${key2}, ${format(expected)}]`);
4401
5286
  return this;
4402
5287
  }
4403
- whereNull(key) {
4404
- this.has(key);
4405
- const actual = this.prop(key);
4406
- assert(actual === null, `Property [${this.dotPath(key)}] should be null.`, { actual, expected: null });
5288
+ whereNull(key2) {
5289
+ this.has(key2);
5290
+ const actual = this.prop(key2);
5291
+ assert(actual === null, `Property [${this.dotPath(key2)}] should be null.`, { actual, expected: null });
4407
5292
  return this;
4408
5293
  }
4409
- whereNotNull(key) {
4410
- this.has(key);
4411
- assert(this.prop(key) !== null, `Property [${this.dotPath(key)}] should not be null.`);
5294
+ whereNotNull(key2) {
5295
+ this.has(key2);
5296
+ assert(this.prop(key2) !== null, `Property [${this.dotPath(key2)}] should not be null.`);
4412
5297
  return this;
4413
5298
  }
4414
5299
  whereAll(bindings) {
4415
- for (const [key, value] of Object.entries(bindings)) this.where(key, value);
5300
+ for (const [key2, value] of Object.entries(bindings)) this.where(key2, value);
4416
5301
  return this;
4417
5302
  }
4418
5303
  /** whereType('id', 'integer'), whereType('name', 'string|null'), whereType('tags', ['array', 'null']). */
4419
- whereType(key, expected) {
4420
- this.has(key);
5304
+ whereType(key2, expected) {
5305
+ this.has(key2);
4421
5306
  const types = typeof expected === "string" ? expected.split("|") : expected;
4422
- const actual = typeOf(this.prop(key));
5307
+ const actual = typeOf(this.prop(key2));
4423
5308
  assert(
4424
5309
  types.some((type) => actual.includes(type)),
4425
- `Property [${this.dotPath(key)}] is not of expected type [${types.join("|")}].`,
5310
+ `Property [${this.dotPath(key2)}] is not of expected type [${types.join("|")}].`,
4426
5311
  { actual: actual[0], expected: types.join("|") }
4427
5312
  );
4428
5313
  return this;
4429
5314
  }
4430
5315
  whereAllType(bindings) {
4431
- for (const [key, type] of Object.entries(bindings)) this.whereType(key, type);
5316
+ for (const [key2, type] of Object.entries(bindings)) this.whereType(key2, type);
4432
5317
  return this;
4433
5318
  }
4434
5319
  /** The list contains every expected value (or one passing the callback); a string contains the substring. */
4435
- whereContains(key, expected) {
4436
- this.has(key);
4437
- const actual = this.prop(key);
5320
+ whereContains(key2, expected) {
5321
+ this.has(key2);
5322
+ const actual = this.prop(key2);
4438
5323
  if (typeof actual === "string") {
4439
- assert(actual.includes(String(expected)), `Property [${this.dotPath(key)}] does not contain [${format(expected)}].`);
5324
+ assert(actual.includes(String(expected)), `Property [${this.dotPath(key2)}] does not contain [${format(expected)}].`);
4440
5325
  return this;
4441
5326
  }
4442
5327
  const values = typeof actual === "object" && actual !== null ? Object.values(actual) : [actual];
@@ -4444,13 +5329,13 @@ var AssertableJson = class _AssertableJson {
4444
5329
  const missing = wanted.filter(
4445
5330
  (item) => typeof item === "function" ? !values.some((value) => item(value)) : !values.some((value) => equals(toPlain(item), value))
4446
5331
  );
4447
- assert(missing.length === 0, `Property [${this.dotPath(key)}] does not contain [${missing.map(format).join(", ")}].`, { actual });
5332
+ assert(missing.length === 0, `Property [${this.dotPath(key2)}] does not contain [${missing.map(format).join(", ")}].`, { actual });
4448
5333
  return this;
4449
5334
  }
4450
5335
  // Scoping --------------------------------------------------------------------------------------------------
4451
- scope(key, callback) {
4452
- const props = this.prop(key);
4453
- const path = this.dotPath(key);
5336
+ scope(key2, callback) {
5337
+ const props = this.prop(key2);
5338
+ const path = this.dotPath(key2);
4454
5339
  assert(typeof props === "object" && props !== null, `Property [${path}] is not scopeable.`);
4455
5340
  const scope = this.newScope(props, path);
4456
5341
  callback(scope);
@@ -4472,9 +5357,9 @@ var AssertableJson = class _AssertableJson {
4472
5357
  if (keys.length === 0) {
4473
5358
  fail2(this.path === void 0 ? "Cannot scope directly onto each element of the root level because it is empty." : `Cannot scope directly onto each element of property [${this.path}] because it is empty.`);
4474
5359
  }
4475
- for (const key of keys) {
4476
- this.interactsWith(key);
4477
- this.scope(key, callback);
5360
+ for (const key2 of keys) {
5361
+ this.interactsWith(key2);
5362
+ this.scope(key2, callback);
4478
5363
  }
4479
5364
  return this;
4480
5365
  }
@@ -4484,8 +5369,8 @@ var AssertableJson = class _AssertableJson {
4484
5369
  else otherwise?.(this);
4485
5370
  return this;
4486
5371
  }
4487
- dump(key) {
4488
- console.log(format(this.prop(key)));
5372
+ dump(key2) {
5373
+ console.log(format(this.prop(key2)));
4489
5374
  return this;
4490
5375
  }
4491
5376
  toArray() {
@@ -4512,7 +5397,7 @@ function inertiaPageOf(response2) {
4512
5397
  } catch {
4513
5398
  page = void 0;
4514
5399
  }
4515
- const valid = typeof page === "object" && page !== null && ["component", "props", "url", "version"].every((key) => Object.hasOwn(page, key));
5400
+ const valid = typeof page === "object" && page !== null && ["component", "props", "url", "version"].every((key2) => Object.hasOwn(page, key2));
4516
5401
  if (!valid) fail2("Not a valid Inertia response.");
4517
5402
  return page;
4518
5403
  }
@@ -4543,7 +5428,7 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
4543
5428
  if (shouldExist === true || shouldExist === void 0 && config?.get("inertia.testing.ensure_pages_exist", true) !== false) {
4544
5429
  const paths = config?.get("inertia.pages.paths", []) ?? [];
4545
5430
  const extensions = config?.get("inertia.pages.extensions", []) ?? [];
4546
- const exists = paths.some((path) => extensions.some((extension) => existsSync6(join6(path, `${value}.${extension}`))));
5431
+ const exists = paths.some((path) => extensions.some((extension) => existsSync6(join7(path, `${value}.${extension}`))));
4547
5432
  if (!exists) fail2(`Inertia page component file [${value}] does not exist.`);
4548
5433
  }
4549
5434
  return this;
@@ -4557,23 +5442,23 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
4557
5442
  return this;
4558
5443
  }
4559
5444
  /** The flash data has `key` (dot paths allowed), equal to `expected` when given. */
4560
- hasFlash(key, ...expected) {
4561
- _AssertableInertia.assertFlashHas(this.page.flash ?? {}, key, ...expected);
5445
+ hasFlash(key2, ...expected) {
5446
+ _AssertableInertia.assertFlashHas(this.page.flash ?? {}, key2, ...expected);
4562
5447
  return this;
4563
5448
  }
4564
- missingFlash(key) {
4565
- _AssertableInertia.assertFlashMissing(this.page.flash ?? {}, key);
5449
+ missingFlash(key2) {
5450
+ _AssertableInertia.assertFlashMissing(this.page.flash ?? {}, key2);
4566
5451
  return this;
4567
5452
  }
4568
- static assertFlashHas(flash, key, ...expected) {
4569
- assert(Arr.has(flash, key), `Inertia Flash Data is missing key [${key}].`);
5453
+ static assertFlashHas(flash, key2, ...expected) {
5454
+ assert(Arr.has(flash, key2), `Inertia Flash Data is missing key [${key2}].`);
4570
5455
  if (expected.length > 0) {
4571
- const actual = Arr.get(flash, key);
4572
- assert(equals(actual, expected[0]), `Inertia Flash Data [${key}] does not match expected value.`, { actual, expected: expected[0] });
5456
+ const actual = Arr.get(flash, key2);
5457
+ assert(equals(actual, expected[0]), `Inertia Flash Data [${key2}] does not match expected value.`, { actual, expected: expected[0] });
4573
5458
  }
4574
5459
  }
4575
- static assertFlashMissing(flash, key) {
4576
- assert(!Arr.has(flash, key), `Inertia Flash Data has unexpected key [${key}].`);
5460
+ static assertFlashMissing(flash, key2) {
5461
+ assert(!Arr.has(flash, key2), `Inertia Flash Data has unexpected key [${key2}].`);
4577
5462
  }
4578
5463
  /**
4579
5464
  * Requests the page again as a partial reload (only/except) and checks it is the same page. Uses the test's
@@ -4639,8 +5524,8 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
4639
5524
  return this.#response?.context.app;
4640
5525
  }
4641
5526
  };
4642
- function cookiePrefix(name, key) {
4643
- return `${createHmac2("sha1", key).update(`${name}v2`).digest("hex")}|`;
5527
+ function cookiePrefix(name, key2) {
5528
+ return `${createHmac2("sha1", key2).update(`${name}v2`).digest("hex")}|`;
4644
5529
  }
4645
5530
  function encryptCookie(encrypter, name, value) {
4646
5531
  return encrypter.encrypt(cookiePrefix(name, encrypter.getKey()) + value, false);
@@ -4672,15 +5557,15 @@ function parseSetCookie(header) {
4672
5557
  };
4673
5558
  for (const attribute of attributes) {
4674
5559
  const at = attribute.indexOf("=");
4675
- const key = (at === -1 ? attribute : attribute.slice(0, at)).trim().toLowerCase();
5560
+ const key2 = (at === -1 ? attribute : attribute.slice(0, at)).trim().toLowerCase();
4676
5561
  const value = at === -1 ? "" : attribute.slice(at + 1).trim();
4677
- if (key === "path") cookie.path = value;
4678
- else if (key === "domain") cookie.domain = value;
4679
- else if (key === "max-age") cookie.maxAge = Number(value);
4680
- else if (key === "expires") cookie.expires = new Date(value);
4681
- else if (key === "httponly") cookie.httpOnly = true;
4682
- else if (key === "secure") cookie.secure = true;
4683
- else if (key === "samesite") cookie.sameSite = value;
5562
+ if (key2 === "path") cookie.path = value;
5563
+ else if (key2 === "domain") cookie.domain = value;
5564
+ else if (key2 === "max-age") cookie.maxAge = Number(value);
5565
+ else if (key2 === "expires") cookie.expires = new Date(value);
5566
+ else if (key2 === "httponly") cookie.httpOnly = true;
5567
+ else if (key2 === "secure") cookie.secure = true;
5568
+ else if (key2 === "samesite") cookie.sameSite = value;
4684
5569
  }
4685
5570
  return cookie;
4686
5571
  }
@@ -4937,16 +5822,16 @@ ${this.excerpt(text)}`);
4937
5822
  return inertiaPageOf(this);
4938
5823
  }
4939
5824
  /** The page props, or one prop by dot path. */
4940
- inertiaProps(key) {
4941
- return Arr.get(inertiaPageOf(this).props, key);
5825
+ inertiaProps(key2) {
5826
+ return Arr.get(inertiaPageOf(this).props, key2);
4942
5827
  }
4943
5828
  /** Inertia flash data waiting in the session for the next page (after a redirect). */
4944
- assertInertiaFlash(key, ...expected) {
4945
- AssertableInertia.assertFlashHas(this.session().get("inertia.flash_data", {}), key, ...expected);
5829
+ assertInertiaFlash(key2, ...expected) {
5830
+ AssertableInertia.assertFlashHas(this.session().get("inertia.flash_data", {}), key2, ...expected);
4946
5831
  return this;
4947
5832
  }
4948
- assertInertiaFlashMissing(key) {
4949
- AssertableInertia.assertFlashMissing(this.session().get("inertia.flash_data", {}), key);
5833
+ assertInertiaFlashMissing(key2) {
5834
+ AssertableInertia.assertFlashMissing(this.session().get("inertia.flash_data", {}), key2);
4950
5835
  return this;
4951
5836
  }
4952
5837
  /** The response JSON equals `data` exactly (object key order is ignored). */
@@ -4973,15 +5858,15 @@ ${this.excerpt(text)}`);
4973
5858
  /** Each key/value pair appears somewhere in the JSON (Laravel's assertJsonFragment). */
4974
5859
  assertJsonFragment(data) {
4975
5860
  const actual = this.json();
4976
- for (const [key, value] of Object.entries(data)) {
4977
- assert(containsPair(actual, key, value), `Unable to find JSON fragment ${format({ [key]: value })} in the response.`, { actual, expected: data });
5861
+ for (const [key2, value] of Object.entries(data)) {
5862
+ assert(containsPair(actual, key2, value), `Unable to find JSON fragment ${format({ [key2]: value })} in the response.`, { actual, expected: data });
4978
5863
  }
4979
5864
  return this;
4980
5865
  }
4981
5866
  /** Passes when at least one pair of `data` is absent (Laravel's assertJsonMissing). */
4982
5867
  assertJsonMissing(data) {
4983
5868
  const actual = this.json();
4984
- const allPresent = Object.entries(data).every(([key, value]) => containsPair(actual, key, value));
5869
+ const allPresent = Object.entries(data).every(([key2, value]) => containsPair(actual, key2, value));
4985
5870
  assert(!allPresent, `Found unexpected JSON fragment ${format(data)} in the response.`, { actual });
4986
5871
  return this;
4987
5872
  }
@@ -4992,10 +5877,10 @@ ${this.excerpt(text)}`);
4992
5877
  return this;
4993
5878
  }
4994
5879
  /** The JSON (or the value at `key`) has `count` elements. */
4995
- assertJsonCount(count, key) {
4996
- const value = this.json(key);
5880
+ assertJsonCount(count, key2) {
5881
+ const value = this.json(key2);
4997
5882
  const actual = Array.isArray(value) ? value.length : typeof value === "object" && value !== null ? Object.keys(value).length : -1;
4998
- assert(actual === count, `Failed to assert that the response ${key === void 0 ? "" : `[${key}] `}count matched the expected ${count} (got ${actual}).`, { actual, expected: count });
5883
+ assert(actual === count, `Failed to assert that the response ${key2 === void 0 ? "" : `[${key2}] `}count matched the expected ${count} (got ${actual}).`, { actual, expected: count });
4999
5884
  return this;
5000
5885
  }
5001
5886
  /** assertRedirect() to the URL of a named route (resolved with the app's UrlGenerator). */
@@ -5019,7 +5904,7 @@ ${this.excerpt()}`);
5019
5904
  if (this.content === "") return this;
5020
5905
  const errors = Arr.get(this.json(), responseKey);
5021
5906
  if (errors === void 0 || errors === null) return this;
5022
- const unexpected = keys === void 0 ? Object.keys(errors) : [keys].flat().filter((key) => Object.hasOwn(errors, key));
5907
+ const unexpected = keys === void 0 ? Object.keys(errors) : [keys].flat().filter((key2) => Object.hasOwn(errors, key2));
5023
5908
  assert(unexpected.length === 0, `Found unexpected validation error(s) for [${unexpected.join(", ")}]: ${format(errors)}`);
5024
5909
  return this;
5025
5910
  }
@@ -5044,38 +5929,38 @@ ${this.excerpt()}`);
5044
5929
  return session;
5045
5930
  }
5046
5931
  /** A key (with an optional value or predicate), several keys, or a { key: value } record. */
5047
- assertSessionHas(key, value) {
5048
- if (Array.isArray(key)) {
5049
- for (const k of key) this.assertSessionHas(k);
5932
+ assertSessionHas(key2, value) {
5933
+ if (Array.isArray(key2)) {
5934
+ for (const k of key2) this.assertSessionHas(k);
5050
5935
  return this;
5051
5936
  }
5052
- if (typeof key === "object") return this.assertSessionHasAll(key);
5937
+ if (typeof key2 === "object") return this.assertSessionHasAll(key2);
5053
5938
  const session = this.session();
5054
5939
  if (value === void 0) {
5055
- assert(session.has(key), `Session is missing expected key [${key}].`);
5940
+ assert(session.has(key2), `Session is missing expected key [${key2}].`);
5056
5941
  } else if (typeof value === "function") {
5057
- assert(value(session.get(key)) === true, `Session value for [${key}] does not pass the given check.`);
5942
+ assert(value(session.get(key2)) === true, `Session value for [${key2}] does not pass the given check.`);
5058
5943
  } else {
5059
- assert(equals(session.get(key), value), `Session value for [${key}] does not match.`, { actual: session.get(key), expected: value });
5944
+ assert(equals(session.get(key2), value), `Session value for [${key2}] does not match.`, { actual: session.get(key2), expected: value });
5060
5945
  }
5061
5946
  return this;
5062
5947
  }
5063
5948
  assertSessionHasAll(bindings) {
5064
- for (const [key, value] of Object.entries(bindings)) this.assertSessionHas(key, value);
5949
+ for (const [key2, value] of Object.entries(bindings)) this.assertSessionHas(key2, value);
5065
5950
  return this;
5066
5951
  }
5067
- assertSessionMissing(key, value) {
5952
+ assertSessionMissing(key2, value) {
5068
5953
  const session = this.session();
5069
- for (const k of [key].flat()) {
5954
+ for (const k of [key2].flat()) {
5070
5955
  if (value === void 0) assert(!session.has(k), `Session has unexpected key [${k}].`);
5071
5956
  else assert(!equals(session.get(k), value), `Session has unexpected value for [${k}].`);
5072
5957
  }
5073
5958
  return this;
5074
5959
  }
5075
5960
  /** Old input flashed with the redirect (withInput()). */
5076
- assertSessionHasInput(key, value) {
5961
+ assertSessionHasInput(key2, value) {
5077
5962
  const session = this.session();
5078
- const entries = typeof key === "string" ? [[key, value]] : Array.isArray(key) ? key.map((k) => [k, void 0]) : Object.entries(key);
5963
+ const entries = typeof key2 === "string" ? [[key2, value]] : Array.isArray(key2) ? key2.map((k) => [k, void 0]) : Object.entries(key2);
5079
5964
  for (const [k, expected] of entries) {
5080
5965
  const actual = session.getOldInput(k);
5081
5966
  assert(actual !== void 0, `Session is missing expected key [${k}] in the old input.`);
@@ -5095,10 +5980,10 @@ ${this.excerpt()}`);
5095
5980
  assert(Object.keys(errors).length > 0, `Session has no errors in the [${errorBag}] bag.`);
5096
5981
  return this;
5097
5982
  }
5098
- for (const [key, messages] of normalizeExpected(expected)) {
5099
- assert(Object.hasOwn(errors, key), `Session missing error: ${key}`, { actual: errors });
5983
+ for (const [key2, messages] of normalizeExpected(expected)) {
5984
+ assert(Object.hasOwn(errors, key2), `Session missing error: ${key2}`, { actual: errors });
5100
5985
  for (const message of messages) {
5101
- assert(errors[key].includes(message), `Session error for [${key}] does not include ${format2 === void 0 ? "" : "the formatted "}message ${JSON.stringify(message)}.`, { actual: errors[key], expected: message });
5986
+ assert(errors[key2].includes(message), `Session error for [${key2}] does not include ${format2 === void 0 ? "" : "the formatted "}message ${JSON.stringify(message)}.`, { actual: errors[key2], expected: message });
5102
5987
  }
5103
5988
  }
5104
5989
  return this;
@@ -5125,7 +6010,7 @@ ${JSON.stringify(found, null, 2)}`, { actual: found, expected: {} });
5125
6010
  ${JSON.stringify(errors, null, 2)}`, { actual: errors, expected: {} });
5126
6011
  return this;
5127
6012
  }
5128
- for (const key of list) assert(!Object.hasOwn(errors, key), `Session has unexpected error: ${key}`, { actual: errors[key] });
6013
+ for (const key2 of list) assert(!Object.hasOwn(errors, key2), `Session has unexpected error: ${key2}`, { actual: errors[key2] });
5129
6014
  return this;
5130
6015
  }
5131
6016
  // Cookies -----------------------------------------------------------------------------------------------
@@ -5176,27 +6061,27 @@ ${JSON.stringify(errors, null, 2)}`, { actual: errors, expected: {} });
5176
6061
  return this;
5177
6062
  }
5178
6063
  /** The view received `key` (dot paths allowed), optionally with this value or passing this predicate. */
5179
- assertViewHas(key, value) {
5180
- if (typeof key !== "string") return this.assertViewHasAll(key);
6064
+ assertViewHas(key2, value) {
6065
+ if (typeof key2 !== "string") return this.assertViewHasAll(key2);
5181
6066
  const data = this.viewDataOf(this.ensureView());
5182
- assert(Arr.has(data, key), `Failed asserting that the view has key [${key}].`, { actual: Object.keys(data) });
5183
- const actual = Arr.get(data, key);
5184
- if (typeof value === "function") assert(value(actual) === true, `View data [${key}] does not pass the given check.`);
5185
- else if (value !== void 0) assert(equals(actual, value), `View data [${key}] does not match the expected value.`, { actual, expected: value });
6067
+ assert(Arr.has(data, key2), `Failed asserting that the view has key [${key2}].`, { actual: Object.keys(data) });
6068
+ const actual = Arr.get(data, key2);
6069
+ if (typeof value === "function") assert(value(actual) === true, `View data [${key2}] does not pass the given check.`);
6070
+ else if (value !== void 0) assert(equals(actual, value), `View data [${key2}] does not match the expected value.`, { actual, expected: value });
5186
6071
  return this;
5187
6072
  }
5188
6073
  assertViewHasAll(bindings) {
5189
- if (Array.isArray(bindings)) for (const key of bindings) this.assertViewHas(key);
5190
- else for (const [key, value] of Object.entries(bindings)) this.assertViewHas(key, value);
6074
+ if (Array.isArray(bindings)) for (const key2 of bindings) this.assertViewHas(key2);
6075
+ else for (const [key2, value] of Object.entries(bindings)) this.assertViewHas(key2, value);
5191
6076
  return this;
5192
6077
  }
5193
- assertViewMissing(key) {
5194
- assert(!Arr.has(this.viewDataOf(this.ensureView()), key), `Failed asserting that the view does not have key [${key}].`);
6078
+ assertViewMissing(key2) {
6079
+ assert(!Arr.has(this.viewDataOf(this.ensureView()), key2), `Failed asserting that the view does not have key [${key2}].`);
5195
6080
  return this;
5196
6081
  }
5197
6082
  /** The data the view received (dot path), or all of it. */
5198
- viewData(key) {
5199
- return Arr.get(this.viewDataOf(this.ensureView()), key);
6083
+ viewData(key2) {
6084
+ return Arr.get(this.viewDataOf(this.ensureView()), key2);
5200
6085
  }
5201
6086
  // Internals ---------------------------------------------------------------------------------------------
5202
6087
  absolute(url2) {
@@ -5231,15 +6116,15 @@ ${this.excerpt()}`;
5231
6116
  }
5232
6117
  formatted(errors, template) {
5233
6118
  if (template === void 0) return errors;
5234
- return Object.fromEntries(Object.entries(errors).map(([key, messages]) => [key, messages.map((m) => template.replaceAll(":message", m))]));
6119
+ return Object.fromEntries(Object.entries(errors).map(([key2, messages]) => [key2, messages.map((m) => template.replaceAll(":message", m))]));
5235
6120
  }
5236
6121
  /** Laravel's assertInvalid/assertJsonValidationErrors: each key present; each expected fragment contained in a message. */
5237
6122
  assertErrorsContain(errors, expected, where) {
5238
- for (const [key, fragments] of normalizeExpected(expected)) {
5239
- assert(Object.hasOwn(errors, key), `Failed to find a validation error in the ${where} for key: [${key}]`, { actual: errors });
5240
- const messages = [errors[key]].flat();
6123
+ for (const [key2, fragments] of normalizeExpected(expected)) {
6124
+ assert(Object.hasOwn(errors, key2), `Failed to find a validation error in the ${where} for key: [${key2}]`, { actual: errors });
6125
+ const messages = [errors[key2]].flat();
5241
6126
  for (const fragment of fragments) {
5242
- assert(messages.some((message) => message.includes(fragment)), `Failed to find a validation error for key and message: [${key}] => [${fragment}]`, { actual: messages, expected: fragment });
6127
+ assert(messages.some((message) => message.includes(fragment)), `Failed to find a validation error for key and message: [${key2}] => [${fragment}]`, { actual: messages, expected: fragment });
5243
6128
  }
5244
6129
  }
5245
6130
  }
@@ -5272,8 +6157,8 @@ ${this.excerpt()}`;
5272
6157
  };
5273
6158
  function normalizeExpected(expected) {
5274
6159
  if (typeof expected === "string") return [[expected, []]];
5275
- if (Array.isArray(expected)) return expected.map((key) => [key, []]);
5276
- return Object.entries(expected).map(([key, messages]) => [key, [messages].flat()]);
6160
+ if (Array.isArray(expected)) return expected.map((key2) => [key2, []]);
6161
+ return Object.entries(expected).map(([key2, messages]) => [key2, [messages].flat()]);
5277
6162
  }
5278
6163
  function renderedHtml(view) {
5279
6164
  try {
@@ -5338,6 +6223,11 @@ var HttpClient = class {
5338
6223
  defaultGuard;
5339
6224
  /** Session attributes carried from one request to the next (Laravel keeps one session store per test). */
5340
6225
  session;
6226
+ /**
6227
+ * withSession() and flushSession() since the last started session: applied to the stored session (the one the
6228
+ * session cookie points to) once the next request starts it, so they win over what it holds, as in Laravel.
6229
+ */
6230
+ sessionChanges = [];
5341
6231
  /** from(url): the session's previous URL for the next request only (Laravel sets it once). */
5342
6232
  previousUrl;
5343
6233
  /** Requests whose actingAs users reached the guards (the AuthFactory they went through). */
@@ -5373,6 +6263,7 @@ var HttpClient = class {
5373
6263
  const context = { request: request2, app: this.app, client: this };
5374
6264
  const result = await scope.run(async () => {
5375
6265
  this.beforeRequest(scope, context);
6266
+ if (context.session !== void 0) this.applyPending(context.session);
5376
6267
  scope.instance(HttpRequest, new IncomingRequest(request2));
5377
6268
  if (scope.bound(AuthFactory)) {
5378
6269
  try {
@@ -5413,6 +6304,7 @@ var HttpClient = class {
5413
6304
  const session = this.scopeSession(scope);
5414
6305
  if (session !== void 0) {
5415
6306
  if (this.session !== void 0) session.put(clonePlain2(this.session));
6307
+ if (this.sessionChanges.length > 0) this.applyOnStart(session);
5416
6308
  if (this.previousUrl !== void 0) session.setPreviousUrl(this.previousUrl);
5417
6309
  context.session = session;
5418
6310
  }
@@ -5445,6 +6337,20 @@ var HttpClient = class {
5445
6337
  else this.users.set(guard, user);
5446
6338
  }
5447
6339
  }
6340
+ /** Applies the pending withSession()/flushSession() changes once the session has loaded what it stored. */
6341
+ applyOnStart(session) {
6342
+ const start = session.start.bind(session);
6343
+ session.start = async () => {
6344
+ await start();
6345
+ this.applyPending(session);
6346
+ };
6347
+ }
6348
+ /** Applies the pending withSession()/flushSession() changes to the session, once. */
6349
+ applyPending(session) {
6350
+ if (this.sessionChanges.length === 0) return;
6351
+ for (const change of this.sessionChanges.splice(0)) change(session);
6352
+ if (!session.has("_token")) session.regenerateToken();
6353
+ }
5448
6354
  /** The request's (scoped) Session, created now so the carried attributes are in place before StartSession. */
5449
6355
  scopeSession(scope) {
5450
6356
  if (!scope.bound(Session)) return void 0;
@@ -5461,14 +6367,14 @@ var HttpClient = class {
5461
6367
  let body;
5462
6368
  const data = options.data ?? {};
5463
6369
  if (verb === "GET" || verb === "HEAD") {
5464
- for (const [key, value] of flatten(data)) if (typeof value === "string") url2.searchParams.append(key, value);
6370
+ for (const [key2, value] of flatten(data)) if (typeof value === "string") url2.searchParams.append(key2, value);
5465
6371
  } else if (options.json) {
5466
6372
  body = JSON.stringify(data);
5467
6373
  } else if (Object.keys(data).length > 0) {
5468
6374
  const fields = flatten(data);
5469
6375
  if (fields.some(([, value]) => typeof value !== "string")) {
5470
6376
  const form = new FormData();
5471
- for (const [key, value] of fields) form.append(key, value);
6377
+ for (const [key2, value] of fields) form.append(key2, value);
5472
6378
  body = form;
5473
6379
  } else {
5474
6380
  body = new URLSearchParams(fields);
@@ -5511,8 +6417,8 @@ function setClientAddress2(request2, address) {
5511
6417
  }
5512
6418
  function flatten(data, prefix = "") {
5513
6419
  const out = [];
5514
- for (const [key, value] of Object.entries(data)) {
5515
- const name = prefix === "" ? key : `${prefix}[${key}]`;
6420
+ for (const [key2, value] of Object.entries(data)) {
6421
+ const name = prefix === "" ? key2 : `${prefix}[${key2}]`;
5516
6422
  if (value === void 0) continue;
5517
6423
  if (value === null) out.push([name, ""]);
5518
6424
  else if (value instanceof Blob) out.push([name, value]);
@@ -5710,14 +6616,17 @@ var MakesHttpRequests = class extends InteractsWithApplication {
5710
6616
  this.http.previousUrl = url2;
5711
6617
  return this.withHeader("Referer", url2);
5712
6618
  }
5713
- /** Puts data in the session for the following requests (Laravel's withSession()). */
6619
+ /** Puts data in the session for the following requests, over what the stored session holds (Laravel's withSession()). */
5714
6620
  withSession(data) {
5715
- this.http.session = { ...this.http.session, ...data };
6621
+ const values = clonePlain2(data);
6622
+ this.http.session = { ...this.http.session, ...values };
6623
+ this.http.sessionChanges.push((session) => session.put(clonePlain2(values)));
5716
6624
  return this;
5717
6625
  }
5718
- /** Empties the session carried between requests. */
6626
+ /** Empties the session, the stored one included, so the next request starts with none (Laravel's flushSession()). */
5719
6627
  flushSession() {
5720
6628
  this.http.session = {};
6629
+ this.http.sessionChanges.splice(0, Infinity, (session) => session.flush());
5721
6630
  return this;
5722
6631
  }
5723
6632
  /** Every following request is authenticated as `user` on `guard` (which also becomes the default guard). */
@@ -5887,8 +6796,8 @@ function install2(token2, fake) {
5887
6796
  return fake;
5888
6797
  }
5889
6798
  function bound(token2) {
5890
- const container = currentContainer();
5891
- return container !== void 0 && container.bound(token2) ? container.make(token2) : void 0;
6799
+ const container2 = currentContainer();
6800
+ return container2 !== void 0 && container2.bound(token2) ? container2.make(token2) : void 0;
5892
6801
  }
5893
6802
  function nameOf(target2) {
5894
6803
  if (typeof target2 === "string") return target2;
@@ -6033,8 +6942,8 @@ var EventFake = class _EventFake {
6033
6942
  if (raw === void 0) return [];
6034
6943
  const listeners = [...raw.get(event) ?? []];
6035
6944
  if (typeof event !== "string") return listeners;
6036
- for (const [key, list] of raw) {
6037
- if (typeof key === "string" && key !== event && key.includes("*") && Str.is(key, event)) listeners.push(...list);
6945
+ for (const [key2, list] of raw) {
6946
+ if (typeof key2 === "string" && key2 !== event && key2.includes("*") && Str.is(key2, event)) listeners.push(...list);
6038
6947
  }
6039
6948
  return listeners;
6040
6949
  }
@@ -6658,8 +7567,8 @@ function sameNotifiable(a, b) {
6658
7567
  const model = n;
6659
7568
  return typeof model.getKey === "function" ? model.getKey() : void 0;
6660
7569
  };
6661
- const key = keyOf(a);
6662
- return key !== void 0 && a.constructor === b.constructor && key === keyOf(b);
7570
+ const key2 = keyOf(a);
7571
+ return key2 !== void 0 && a.constructor === b.constructor && key2 === keyOf(b);
6663
7572
  }
6664
7573
  var NotificationFake = class _NotificationFake {
6665
7574
  constructor(dispatcher = void 0) {
@@ -6844,8 +7753,8 @@ var DISK_TYPES = {
6844
7753
  wasm: "application/wasm"
6845
7754
  };
6846
7755
  function diskType(extension) {
6847
- const key = extension.replace(/^\./, "").toLowerCase();
6848
- return Object.hasOwn(DISK_TYPES, key) ? DISK_TYPES[key] : void 0;
7756
+ const key2 = extension.replace(/^\./, "").toLowerCase();
7757
+ return Object.hasOwn(DISK_TYPES, key2) ? DISK_TYPES[key2] : void 0;
6849
7758
  }
6850
7759
  function hashName(file) {
6851
7760
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -6877,8 +7786,8 @@ var FakeDisk = class {
6877
7786
  /** Directories made explicitly (others exist because files are in them). */
6878
7787
  made = /* @__PURE__ */ new Set();
6879
7788
  async exists(path) {
6880
- const key = normalize(path);
6881
- return this.stored.has(key) || this.isDirectory(key);
7789
+ const key2 = normalize(path);
7790
+ return this.stored.has(key2) || this.isDirectory(key2);
6882
7791
  }
6883
7792
  async missing(path) {
6884
7793
  return !await this.exists(path);
@@ -6899,9 +7808,9 @@ var FakeDisk = class {
6899
7808
  });
6900
7809
  }
6901
7810
  async put(path, contents, options = {}) {
6902
- const key = normalize(path);
7811
+ const key2 = normalize(path);
6903
7812
  const bytes = typeof contents === "string" ? new TextEncoder().encode(contents) : contents instanceof Uint8Array ? contents.slice() : await collect(contents);
6904
- this.write(key, bytes, options.mimeType ?? diskType(posix.extname(key)), options);
7813
+ this.write(key2, bytes, options.mimeType ?? diskType(posix.extname(key2)), options);
6905
7814
  return true;
6906
7815
  }
6907
7816
  putFile(directory, file, options = {}) {
@@ -6916,8 +7825,8 @@ var FakeDisk = class {
6916
7825
  this.write(path, new Uint8Array(await file.arrayBuffer()), options.mimeType ?? diskType(file.extension()), options);
6917
7826
  return path;
6918
7827
  }
6919
- write(key, bytes, mimeType, options) {
6920
- this.stored.set(key, { bytes, visibility: options.visibility ?? "private", mimeType, modified: /* @__PURE__ */ new Date() });
7828
+ write(key2, bytes, mimeType, options) {
7829
+ this.stored.set(key2, { bytes, visibility: options.visibility ?? "private", mimeType, modified: /* @__PURE__ */ new Date() });
6921
7830
  }
6922
7831
  async delete(paths) {
6923
7832
  for (const path of [paths].flat()) this.stored.delete(normalize(path));
@@ -7006,8 +7915,8 @@ var FakeDisk = class {
7006
7915
  if (file === void 0) throw new FakeDiskError("FileNotFoundError", `File not found at path: ${path}`, path);
7007
7916
  return file;
7008
7917
  }
7009
- isDirectory(key) {
7010
- return this.made.has(key) || [...this.stored.keys()].some((path) => key !== "" && path.startsWith(`${key}/`));
7918
+ isDirectory(key2) {
7919
+ return this.made.has(key2) || [...this.stored.keys()].some((path) => key2 !== "" && path.startsWith(`${key2}/`));
7011
7920
  }
7012
7921
  };
7013
7922
  async function collect(stream) {
@@ -7208,8 +8117,8 @@ function lzwZeros(pixels, minCodeSize) {
7208
8117
  let current = 0;
7209
8118
  out.write(clear, codeSize);
7210
8119
  for (let i = 1; i < pixels; i++) {
7211
- const key = current << 8;
7212
- const known = table.get(key);
8120
+ const key2 = current << 8;
8121
+ const known = table.get(key2);
7213
8122
  if (known !== void 0) {
7214
8123
  current = known;
7215
8124
  continue;
@@ -7222,7 +8131,7 @@ function lzwZeros(pixels, minCodeSize) {
7222
8131
  next = end + 1;
7223
8132
  } else {
7224
8133
  if (next >= 1 << codeSize) codeSize++;
7225
- table.set(key, next++);
8134
+ table.set(key2, next++);
7226
8135
  }
7227
8136
  current = 0;
7228
8137
  }
@@ -7343,6 +8252,7 @@ var FakeFile = {
7343
8252
  };
7344
8253
 
7345
8254
  export {
8255
+ Config,
7346
8256
  Repository,
7347
8257
  env,
7348
8258
  ApplicationBuilder,
@@ -7426,12 +8336,21 @@ export {
7426
8336
  PropertyNotUpdatableError,
7427
8337
  LiveComponentNotFoundError,
7428
8338
  InvalidLiveRequestError,
8339
+ InvalidUploadSignatureError,
8340
+ TemporaryFileNotFoundError,
8341
+ FileNotPreviewableError,
8342
+ UploadValidationError,
7429
8343
  On,
7430
8344
  Locked,
7431
8345
  Computed,
8346
+ TEMPORARY_UPLOAD_DEFAULTS,
8347
+ TemporaryUploadedFile,
7432
8348
  ErrorBag,
7433
8349
  PendingEvent,
7434
8350
  Component,
8351
+ UPLOAD_URI,
8352
+ PREVIEW_URI,
8353
+ WithFileUploads,
7435
8354
  TestRequestScope,
7436
8355
  Testable,
7437
8356
  live,
@@ -7496,4 +8415,4 @@ export {
7496
8415
  StorageFake,
7497
8416
  FakeFile
7498
8417
  };
7499
- //# sourceMappingURL=chunk-ZOEPKBVF.js.map
8418
+ //# sourceMappingURL=chunk-4MWG3B7G.js.map