@orieljs/framework 0.1.0-alpha.5 → 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 (87) hide show
  1. package/NOTICE +7 -0
  2. package/README.md +10 -1
  3. package/TRADEMARKS.md +33 -0
  4. package/dist/{chunk-WNJP46B7.js → chunk-4MWG3B7G.js} +1785 -626
  5. package/dist/chunk-4MWG3B7G.js.map +1 -0
  6. package/dist/chunk-7ZQZYDOC.js +3253 -0
  7. package/dist/chunk-7ZQZYDOC.js.map +1 -0
  8. package/dist/{chunk-BSQKOLSK.js → chunk-GVNLZZTN.js} +1 -1
  9. package/dist/chunk-GVNLZZTN.js.map +1 -0
  10. package/dist/{chunk-A5XRLEWM.js → chunk-VOYW3R5J.js} +4 -4
  11. package/dist/chunk-VOYW3R5J.js.map +1 -0
  12. package/dist/contracts.d.ts +535 -17
  13. package/dist/contracts.js +1 -1
  14. package/dist/index.d-WQpfdIbG.d.ts +630 -0
  15. package/dist/index.d.ts +2202 -5952
  16. package/dist/index.js +4900 -944
  17. package/dist/index.js.map +1 -1
  18. package/dist/{jsx-runtime-WKTl3EEw.d.ts → jsx-runtime-6um4Mwfb.d.ts} +97 -51
  19. package/dist/jsx-runtime.d.ts +2 -2
  20. package/dist/jsx-runtime.js +3 -3
  21. package/dist/{testing-BErengYY.d.ts → testing-CKwfWJGn.d.ts} +313 -994
  22. package/dist/testing.d.ts +2 -2
  23. package/dist/testing.js +19 -5
  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-A5XRLEWM.js.map +0 -1
  83. package/dist/chunk-BSQKOLSK.js.map +0 -1
  84. package/dist/chunk-PM5YTG57.js +0 -880
  85. package/dist/chunk-PM5YTG57.js.map +0 -1
  86. package/dist/chunk-WNJP46B7.js.map +0 -1
  87. package/dist/index.d-DOlTeG-8.d.ts +0 -99
@@ -10,7 +10,7 @@ import {
10
10
  facade,
11
11
  fileType_exports,
12
12
  inject
13
- } from "./chunk-PM5YTG57.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-BSQKOLSK.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.5";
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,43 +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));
1379
+ }
1380
+ /** Laravel's string(): the input as a Stringable (the default when it is missing or null): request().string('q').trim(). */
1381
+ string(key2, defaultValue = "") {
1382
+ const value = this.input(key2);
1383
+ return Str.of(value === void 0 || value === null ? defaultValue : value);
1292
1384
  }
1293
- string(key, defaultValue = "") {
1294
- const value = this.input(key);
1295
- return value === void 0 || value === null ? defaultValue : String(value);
1385
+ /** Laravel's str(): string() under its short name. */
1386
+ str(key2, defaultValue = "") {
1387
+ return this.string(key2, defaultValue);
1296
1388
  }
1297
1389
  /** PHP's FILTER_VALIDATE_BOOLEAN: '1', 'true', 'on', 'yes' (any case) and true are true. */
1298
- boolean(key, defaultValue = false) {
1299
- const value = this.input(key);
1390
+ boolean(key2, defaultValue = false) {
1391
+ const value = this.input(key2);
1300
1392
  if (value === void 0) return defaultValue;
1301
1393
  if (typeof value === "boolean") return value;
1302
1394
  if (typeof value === "number") return value === 1;
1303
1395
  return typeof value === "string" && TRUE_VALUES.has(value.trim().toLowerCase());
1304
1396
  }
1305
1397
  /** PHP's intval: leading digits, else 0. */
1306
- integer(key, defaultValue = 0) {
1307
- const value = this.input(key);
1398
+ integer(key2, defaultValue = 0) {
1399
+ const value = this.input(key2);
1308
1400
  if (value === void 0) return defaultValue;
1309
1401
  if (typeof value === "number") return Number.isFinite(value) ? Math.trunc(value) : 0;
1310
1402
  if (typeof value === "boolean") return value ? 1 : 0;
@@ -1322,12 +1414,12 @@ var IncomingRequest = class {
1322
1414
  this.#body = transformLeaves(this.#body, transform);
1323
1415
  return this;
1324
1416
  }
1325
- file(key) {
1326
- const value = Arr.get(this.#files, key);
1417
+ file(key2) {
1418
+ const value = Arr.get(this.#files, key2);
1327
1419
  return value instanceof FormUploadedFile ? value : void 0;
1328
1420
  }
1329
- hasFile(key) {
1330
- const value = Arr.get(this.#files, key);
1421
+ hasFile(key2) {
1422
+ const value = Arr.get(this.#files, key2);
1331
1423
  return value instanceof FormUploadedFile || Array.isArray(value) && value.some((item) => item instanceof FormUploadedFile);
1332
1424
  }
1333
1425
  allFiles() {
@@ -1361,38 +1453,48 @@ var IncomingRequest = class {
1361
1453
  this.#cookies = { ...cookies };
1362
1454
  }
1363
1455
  /** One route parameter (after model binding), or all of them. */
1364
- route(key) {
1456
+ route(key2) {
1365
1457
  const parameters = this.#currentRoute()?.parameters ?? {};
1366
- return key === void 0 ? { ...parameters } : parameters[key];
1458
+ return key2 === void 0 ? { ...parameters } : parameters[key2];
1367
1459
  }
1368
1460
  session() {
1369
1461
  return inject(Session);
1370
1462
  }
1371
- user(guard) {
1372
- 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();
1373
1472
  }
1374
1473
  setUserResolver(resolver) {
1375
1474
  this.#userResolver = resolver;
1376
1475
  }
1377
- /** 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
+ */
1378
1480
  validate(rules, messages, attributes) {
1379
1481
  return inject(ValidationFactory).make(this.all(), rules, messages, attributes).validated();
1380
1482
  }
1381
1483
  #currentRoute() {
1382
- const container = currentContainer();
1383
- return container?.bound(CurrentRoute) ? container.make(CurrentRoute) : void 0;
1484
+ const container2 = currentContainer();
1485
+ return container2?.bound(CurrentRoute) ? container2.make(CurrentRoute) : void 0;
1384
1486
  }
1385
1487
  };
1386
1488
  function structuredCloneInput(tree) {
1387
1489
  const out = {};
1388
- for (const [key, value] of Object.entries(tree)) {
1389
- 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;
1390
1492
  }
1391
1493
  return out;
1392
1494
  }
1393
- function request(key, defaultValue) {
1495
+ function request(key2, defaultValue) {
1394
1496
  const current = inject(HttpRequest);
1395
- return key === void 0 ? current : current.input(key, defaultValue);
1497
+ return key2 === void 0 ? current : current.input(key2, defaultValue);
1396
1498
  }
1397
1499
  function response(content, status, headers) {
1398
1500
  const factory = inject(ResponseFactory);
@@ -1409,7 +1511,8 @@ function url(path, query, absolute) {
1409
1511
  const generator = inject(UrlGenerator);
1410
1512
  return path === void 0 ? generator : generator.to(path, query, absolute);
1411
1513
  }
1412
- function route(name, parameters, absolute) {
1514
+ function route(name, ...args) {
1515
+ const [parameters, absolute] = args;
1413
1516
  return inject(UrlGenerator).route(name, parameters, absolute);
1414
1517
  }
1415
1518
  function abort(status, message = "", headers = {}) {
@@ -1482,7 +1585,7 @@ var TrimStrings = class {
1482
1585
  async handle(request2, next) {
1483
1586
  if (request2 instanceof IncomingRequest) {
1484
1587
  request2.transformInput(
1485
- (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
1486
1589
  );
1487
1590
  }
1488
1591
  return next(request2);
@@ -1728,18 +1831,41 @@ var STATUS_TEXTS = {
1728
1831
  403: "Forbidden",
1729
1832
  404: "Not Found",
1730
1833
  405: "Method Not Allowed",
1834
+ 406: "Not Acceptable",
1835
+ 407: "Proxy Authentication Required",
1731
1836
  408: "Request Timeout",
1732
1837
  409: "Conflict",
1733
1838
  410: "Gone",
1839
+ 411: "Length Required",
1840
+ 412: "Precondition Failed",
1734
1841
  413: "Content Too Large",
1842
+ 414: "URI Too Long",
1735
1843
  415: "Unsupported Media Type",
1844
+ 416: "Range Not Satisfiable",
1845
+ 417: "Expectation Failed",
1846
+ 418: "I'm a teapot",
1736
1847
  419: "Page Expired",
1848
+ 421: "Misdirected Request",
1737
1849
  422: "Unprocessable Content",
1850
+ 423: "Locked",
1851
+ 424: "Failed Dependency",
1852
+ 425: "Too Early",
1853
+ 426: "Upgrade Required",
1854
+ 428: "Precondition Required",
1738
1855
  429: "Too Many Requests",
1856
+ 431: "Request Header Fields Too Large",
1857
+ 451: "Unavailable For Legal Reasons",
1739
1858
  500: "Server Error",
1859
+ 501: "Not Implemented",
1740
1860
  502: "Bad Gateway",
1741
1861
  503: "Service Unavailable",
1742
- 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"
1743
1869
  };
1744
1870
  function statusText(status) {
1745
1871
  return STATUS_TEXTS[status] ?? (status >= 500 ? "Server Error" : "Error");
@@ -1840,8 +1966,8 @@ function summarizeValidation(errors) {
1840
1966
  return more === 0 ? messages[0] : `${messages[0]} (and ${more} more ${more === 1 ? "error" : "errors"})`;
1841
1967
  }
1842
1968
  var isHttpLike = (error) => {
1843
- const kind = kindOf(error);
1844
- return kind !== void 0 && HTTP_KINDS.has(kind);
1969
+ const kind2 = kindOf(error);
1970
+ return kind2 !== void 0 && HTTP_KINDS.has(kind2);
1845
1971
  };
1846
1972
  var ErrorHandler = class {
1847
1973
  constructor(app) {
@@ -1873,8 +1999,8 @@ var ErrorHandler = class {
1873
1999
  return this.#jsonWhen !== void 0 ? this.#jsonWhen(request2, error) : request2.expectsJson();
1874
2000
  }
1875
2001
  shouldReport(error) {
1876
- const kind = kindOf(error);
1877
- 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;
1878
2004
  return !this.#dontReport.some((cls) => error instanceof cls);
1879
2005
  }
1880
2006
  /** Report callbacks first (returning false stops the default log), then LogManager.error(), else console.error. Once per error. */
@@ -1886,10 +2012,10 @@ var ErrorHandler = class {
1886
2012
  }
1887
2013
  try {
1888
2014
  for (const callback of this.#reportCallbacks) if (await callback(error) === false) return;
1889
- const container = this.#container();
1890
- if (container.bound(LogManager)) {
2015
+ const container2 = this.#container();
2016
+ if (container2.bound(LogManager)) {
1891
2017
  const message = error instanceof Error ? error.message : String(error);
1892
- container.make(LogManager).error(message, { exception: error });
2018
+ container2.make(LogManager).error(message, { exception: error });
1893
2019
  } else {
1894
2020
  console.error(error);
1895
2021
  }
@@ -1906,15 +2032,14 @@ var ErrorHandler = class {
1906
2032
  const custom = await this.#renderCustom(request2, error);
1907
2033
  if (custom !== void 0) return custom;
1908
2034
  const http = this.#httpRequest(request2);
1909
- const kind = kindOf(error);
2035
+ const kind2 = kindOf(error);
1910
2036
  let response2;
1911
- if (kind === "Validation") response2 = await this.#renderValidation(http, error);
1912
- 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);
1913
2039
  else if (this.shouldReturnJson(http, error)) response2 = this.#renderJson(error);
1914
2040
  else response2 = await this.#renderHtml(http, error);
1915
2041
  const headers = error?.headers;
1916
- if (headers !== void 0) for (const [name, value] of Object.entries(headers)) response2.headers.set(name, value);
1917
- return response2;
2042
+ return headers === void 0 ? response2 : withHeaders(response2, headers);
1918
2043
  }
1919
2044
  /** An error with its own render(request) or toResponse(request) (Laravel's renderable exceptions / Responsable). */
1920
2045
  async #renderCustom(request2, error) {
@@ -1932,12 +2057,12 @@ var ErrorHandler = class {
1932
2057
  return this.#json({ message: summarizeValidation(error.errors), errors: error.errors }, statusFor(error));
1933
2058
  }
1934
2059
  const input = { ...request2.input() };
1935
- for (const key of this.dontFlash) delete input[key];
1936
- const container = this.#container();
1937
- if (!container.bound(Redirector)) {
2060
+ for (const key2 of this.dontFlash) delete input[key2];
2061
+ const container2 = this.#container();
2062
+ if (!container2.bound(Redirector)) {
1938
2063
  return this.#redirect(error.redirectTo ?? request2.header("referer") ?? "/");
1939
2064
  }
1940
- const redirector = container.make(Redirector);
2065
+ const redirector = container2.make(Redirector);
1941
2066
  const redirect2 = error.redirectTo !== void 0 ? redirector.to(error.redirectTo) : redirector.back();
1942
2067
  const response2 = await redirect2.withInput(input).withErrors(error.errors, error.errorBag).toResponse(request2);
1943
2068
  return response2;
@@ -1945,9 +2070,9 @@ var ErrorHandler = class {
1945
2070
  async #renderAuthentication(request2, error) {
1946
2071
  if (this.shouldReturnJson(request2, error)) return this.#json({ message: error.message || "Unauthenticated." }, 401);
1947
2072
  const target2 = error.redirectTo ?? this.#guestRedirect(request2);
1948
- const container = this.#container();
1949
- if (!container.bound(Redirector)) return this.#redirect(target2);
1950
- const redirector = container.make(Redirector);
2073
+ const container2 = this.#container();
2074
+ if (!container2.bound(Redirector)) return this.#redirect(target2);
2075
+ const redirector = container2.make(Redirector);
1951
2076
  const redirect2 = typeof redirector.guest === "function" ? redirector.guest(target2) : redirector.to(target2);
1952
2077
  return await redirect2.toResponse(request2);
1953
2078
  }
@@ -1956,9 +2081,9 @@ var ErrorHandler = class {
1956
2081
  const configured = this.#config("http.redirect_guests_to", void 0);
1957
2082
  if (typeof configured === "function") return String(configured(request2));
1958
2083
  if (typeof configured === "string" && configured !== "") return configured;
1959
- const container = this.#container();
1960
- if (container.bound(UrlGenerator)) {
1961
- const url2 = container.make(UrlGenerator);
2084
+ const container2 = this.#container();
2085
+ if (container2.bound(UrlGenerator)) {
2086
+ const url2 = container2.make(UrlGenerator);
1962
2087
  if (url2.hasRoute("login")) return url2.route("login");
1963
2088
  }
1964
2089
  return "/login";
@@ -1994,9 +2119,9 @@ var ErrorHandler = class {
1994
2119
  }
1995
2120
  /** resources/views/errors/{status}.tsx, then errors/4xx or errors/5xx, through the ViewFactory. */
1996
2121
  async #errorView(status, error, message) {
1997
- const container = this.#container();
1998
- if (!container.bound(ViewFactory)) return void 0;
1999
- const views = container.make(ViewFactory);
2122
+ const container2 = this.#container();
2123
+ if (!container2.bound(ViewFactory)) return void 0;
2124
+ const views = container2.make(ViewFactory);
2000
2125
  for (const name of [`errors.${status}`, `errors.${Math.floor(status / 100)}xx`]) {
2001
2126
  if (!views.exists(name)) continue;
2002
2127
  try {
@@ -2026,14 +2151,19 @@ var ErrorHandler = class {
2026
2151
  #debug() {
2027
2152
  return this.#config("app.debug", false) === true;
2028
2153
  }
2029
- #config(key, fallback) {
2030
- 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;
2031
2156
  }
2032
2157
  /** The request scope while handling a request (so scoped services resolve), else the application. */
2033
2158
  #container() {
2034
2159
  return currentContainer() ?? this.app;
2035
2160
  }
2036
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
+ }
2037
2167
  var ExceptionsConfiguration = class {
2038
2168
  constructor(handler) {
2039
2169
  this.handler = handler;
@@ -2108,9 +2238,9 @@ async function handleSafely(app, request2, publicPath) {
2108
2238
  return plainServerError();
2109
2239
  }
2110
2240
  }
2111
- function exitOnSignals(close, closed) {
2241
+ function exitOnSignals(close, closed, app) {
2112
2242
  const onSignal = () => {
2113
- void close().then(() => process.exit(0));
2243
+ void close().then(() => app.shutdown()).then(() => process.exit(0));
2114
2244
  };
2115
2245
  process.once("SIGINT", onSignal);
2116
2246
  process.once("SIGTERM", onSignal);
@@ -2152,7 +2282,7 @@ async function serveBun(app, options) {
2152
2282
  clearTimeout(timer);
2153
2283
  markClosed();
2154
2284
  })();
2155
- if (options.signals) exitOnSignals(close, closed);
2285
+ if (options.signals) exitOnSignals(close, closed, app);
2156
2286
  return {
2157
2287
  runtime: "bun",
2158
2288
  hostname: options.hostname,
@@ -2285,7 +2415,7 @@ async function serveNode(app, options) {
2285
2415
  clearInterval(idle);
2286
2416
  clearTimeout(force);
2287
2417
  })();
2288
- if (options.signals) exitOnSignals(close, closed);
2418
+ if (options.signals) exitOnSignals(close, closed, app);
2289
2419
  return {
2290
2420
  runtime: "node",
2291
2421
  hostname: options.hostname,
@@ -2313,6 +2443,232 @@ async function serve(app, options = {}) {
2313
2443
  onListen(server);
2314
2444
  return server;
2315
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
+ };
2316
2672
  function io(command) {
2317
2673
  return {
2318
2674
  option: (name) => {
@@ -2386,11 +2742,36 @@ ${web[1]}api: 'routes/api.ts',`);
2386
2742
  ${create[0]}`);
2387
2743
  return void 0;
2388
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
+ }
2389
2769
  function registerHttpCommands(kernel, app, options = {}) {
2390
2770
  const serveApp = options.serve ?? serve;
2391
2771
  const terminalWidth = options.terminalWidth ?? (() => process.stdout.columns ?? 120);
2772
+ const runWatching = options.watch ?? ((watchOptions) => new WatchedServer(watchOptions).run());
2392
2773
  kernel.command(
2393
- "serve {--host=127.0.0.1 : The host address to serve the application on} {--port= : The port to serve the application on}",
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}",
2394
2775
  async function() {
2395
2776
  const out = io(this);
2396
2777
  const port = out.option("port");
@@ -2398,6 +2779,7 @@ function registerHttpCommands(kernel, app, options = {}) {
2398
2779
  out.error("The --port option must be a number.");
2399
2780
  return 1;
2400
2781
  }
2782
+ if (out.flag("watch")) return serveWatching(app, out, port, runWatching);
2401
2783
  const server = await serveApp(app, {
2402
2784
  hostname: out.option("host") ?? "127.0.0.1",
2403
2785
  ...port !== void 0 && { port: Number(port) },
@@ -2445,7 +2827,7 @@ function registerHttpCommands(kernel, app, options = {}) {
2445
2827
  kernel.command("install:api {--force : Overwrite any existing API routes file}", async function() {
2446
2828
  const out = io(this);
2447
2829
  const routesFile = app.basePath("routes/api.ts");
2448
- if (existsSync4(routesFile) && !out.flag("force")) {
2830
+ if (existsSync32(routesFile) && !out.flag("force")) {
2449
2831
  out.error("API routes file already exists.");
2450
2832
  } else {
2451
2833
  await mkdir(dirname(routesFile), { recursive: true });
@@ -2453,7 +2835,7 @@ function registerHttpCommands(kernel, app, options = {}) {
2453
2835
  out.info("Published API routes file.");
2454
2836
  }
2455
2837
  const bootstrapFile = app.bootstrapPath("app.ts");
2456
- const source = existsSync4(bootstrapFile) ? await readFile2(bootstrapFile, "utf8") : void 0;
2838
+ const source = existsSync32(bootstrapFile) ? await readFile2(bootstrapFile, "utf8") : void 0;
2457
2839
  const updated = source === void 0 ? void 0 : addApiRouting(source);
2458
2840
  if (updated === void 0) {
2459
2841
  out.warn(
@@ -2462,7 +2844,7 @@ function registerHttpCommands(kernel, app, options = {}) {
2462
2844
  } else if (updated !== source) {
2463
2845
  await writeFile(bootstrapFile, updated);
2464
2846
  }
2465
- out.info("API scaffolding installed. Run [oriel migrate] to create the personal access tokens table.");
2847
+ out.info("API scaffolding installed.");
2466
2848
  return 0;
2467
2849
  }).purpose("Create an API routes file and prepare the application for API authentication");
2468
2850
  }
@@ -2500,23 +2882,98 @@ var HttpServiceProvider = class extends ServiceProvider {
2500
2882
  // ../Live/dist/index.js
2501
2883
  import { AssertionError } from "assert";
2502
2884
  import { isDeepStrictEqual } from "util";
2885
+ import { createHmac, timingSafeEqual } from "crypto";
2503
2886
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
2504
2887
  import { randomBytes as randomBytes2 } from "crypto";
2505
- import { createHmac, timingSafeEqual } from "crypto";
2506
2888
  import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
2507
- 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";
2508
2890
  import { pathToFileURL as pathToFileURL2 } from "url";
2509
2891
  import { AsyncLocalStorage } from "async_hooks";
2510
- import { existsSync as existsSync22 } from "fs";
2892
+ import { existsSync as existsSync23 } from "fs";
2511
2893
  import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
2512
- import { dirname as dirname2, join as join23, relative as relative22, sep as sep22 } from "path";
2894
+ import { dirname as dirname2, join as join24, relative as relative22, sep as sep22 } from "path";
2895
+ var LiveError = class extends Error {
2896
+ name = "LiveError";
2897
+ kind = "Server";
2898
+ status = 500;
2899
+ };
2900
+ var CorruptComponentPayloadError = class extends LiveError {
2901
+ name = "CorruptComponentPayloadError";
2902
+ kind = "Authorization";
2903
+ status = 403;
2904
+ constructor(component = "unknown") {
2905
+ super(`Live encountered corrupt data when trying to hydrate the [${component}] component.`);
2906
+ }
2907
+ };
2908
+ var MethodNotCallableError = class extends LiveError {
2909
+ name = "MethodNotCallableError";
2910
+ kind = "Authorization";
2911
+ status = 403;
2912
+ constructor(method, component) {
2913
+ super(`Unable to call component method. Public method [${method}] not found on component [${component}].`);
2914
+ }
2915
+ };
2916
+ var PropertyNotUpdatableError = class extends LiveError {
2917
+ name = "PropertyNotUpdatableError";
2918
+ kind = "Authorization";
2919
+ status = 403;
2920
+ constructor(path, component) {
2921
+ super(`Cannot update property [${path}] on component [${component}]: it is not a public, unlocked property.`);
2922
+ }
2923
+ };
2924
+ var LiveComponentNotFoundError = class extends LiveError {
2925
+ name = "LiveComponentNotFoundError";
2926
+ kind = "Http";
2927
+ status = 404;
2928
+ constructor(component) {
2929
+ super(`Unable to find component: [${component}].`);
2930
+ }
2931
+ };
2932
+ var InvalidLiveRequestError = class extends LiveError {
2933
+ name = "InvalidLiveRequestError";
2934
+ kind = "Http";
2935
+ status = 400;
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
+ };
2513
2970
  Symbol.metadata ??= /* @__PURE__ */ Symbol.for("Symbol.metadata");
2514
2971
  var LISTENERS = /* @__PURE__ */ Symbol.for("oriel:live.listeners");
2515
2972
  var LOCKED = /* @__PURE__ */ Symbol.for("oriel:live.locked");
2516
2973
  var COMPUTED = /* @__PURE__ */ Symbol.for("oriel:live.computed");
2517
- function own(metadata, key, empty2, copy) {
2518
- if (!Object.hasOwn(metadata, key)) metadata[key] = metadata[key] === void 0 ? empty2() : copy(metadata[key]);
2519
- 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];
2520
2977
  }
2521
2978
  function On(event) {
2522
2979
  return function(_method, context) {
@@ -2559,8 +3016,8 @@ function chainOf(cls) {
2559
3016
  }
2560
3017
  return chain;
2561
3018
  }
2562
- function staticsOf(cls, key) {
2563
- 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]] : []);
2564
3021
  }
2565
3022
  function listenersOf(cls) {
2566
3023
  const result = {};
@@ -2581,6 +3038,277 @@ function lockedOf(cls) {
2581
3038
  function computedOf(cls) {
2582
3039
  return /* @__PURE__ */ new Set([...metadataOf(cls)[COMPUTED] ?? [], ...staticsOf(cls, "computed").flat()]);
2583
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
+ };
3159
+ function isModel(value) {
3160
+ if (typeof value !== "object" || value === null) return false;
3161
+ const candidate = value;
3162
+ return typeof candidate.getKey === "function" && typeof candidate.getKeyName === "function" && typeof candidate.getTable === "function" && typeof candidate.exists === "boolean";
3163
+ }
3164
+ var isPlainObject3 = (value) => {
3165
+ const proto = Object.getPrototypeOf(value);
3166
+ return proto === Object.prototype || proto === null;
3167
+ };
3168
+ var isTuple = (value) => Array.isArray(value) && value.length === 2 && typeof value[1] === "object" && value[1] !== null && !Array.isArray(value[1]) && typeof value[1].s === "string";
3169
+ function classNameOf(registry, cls) {
3170
+ return registry.nameOf(cls) ?? registry.register(cls);
3171
+ }
3172
+ function dehydrate(value, registry, path, seen = /* @__PURE__ */ new Set()) {
3173
+ switch (typeof value) {
3174
+ case "undefined":
3175
+ return [null, { s: "u" }];
3176
+ case "string":
3177
+ case "boolean":
3178
+ return value;
3179
+ case "number":
3180
+ if (!Number.isFinite(value)) throw new LiveError(`Property [${path}] is ${value}: Live state must be JSON (finite numbers only).`);
3181
+ return value;
3182
+ case "bigint":
3183
+ case "symbol":
3184
+ case "function":
3185
+ throw new LiveError(`Property [${path}] holds a ${typeof value}: Live state may only hold primitives, arrays, plain objects, Dates, models and collections.`);
3186
+ }
3187
+ if (value === null) return null;
3188
+ const object = value;
3189
+ if (seen.has(object)) throw new LiveError(`Property [${path}] contains a circular reference.`);
3190
+ seen.add(object);
3191
+ try {
3192
+ if (object instanceof Date) return [object.toISOString(), { s: "dt" }];
3193
+ if (object instanceof TemporaryUploadedFile) return [object.toData(), { s: "fil" }];
3194
+ if (Array.isArray(object)) return [object.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), { s: "arr" }];
3195
+ if (isModel(object)) {
3196
+ const name = classNameOf(registry, object.constructor);
3197
+ if (!object.exists) throw new LiveError(`Property [${path}] holds an unsaved [${name}] model: only saved models can be kept between requests.`);
3198
+ return [null, { s: "mdl", class: name, key: object.getKey() }];
3199
+ }
3200
+ if (object instanceof Collection) {
3201
+ const items = object.all();
3202
+ const first = items[0];
3203
+ if (items.length > 0 && isModel(first) && items.every((item) => isModel(item) && item.constructor === first.constructor)) {
3204
+ for (const [i, item] of items.entries()) {
3205
+ if (!item.exists) throw new LiveError(`Property [${path}.${i}] holds an unsaved model.`);
3206
+ }
3207
+ const name = classNameOf(registry, first.constructor);
3208
+ return [null, { s: "elcln", class: name, keys: items.map((item) => item.getKey()) }];
3209
+ }
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];
3212
+ }
3213
+ if (isPlainObject3(object)) {
3214
+ const out = {};
3215
+ for (const [key2, item] of Object.entries(object)) out[key2] = dehydrate(item, registry, `${path}.${key2}`, seen);
3216
+ return [out, { s: "obj" }];
3217
+ }
3218
+ const type = object.constructor?.name || "object";
3219
+ throw new LiveError(
3220
+ `Property [${path}] holds a [${type}] instance: Live state may only hold primitives, arrays, plain objects, Dates, models and collections.`
3221
+ );
3222
+ } finally {
3223
+ seen.delete(object);
3224
+ }
3225
+ }
3226
+ function modelClass(registry, name) {
3227
+ if (typeof name !== "string" || !registry.has(name)) throw new LiveError(`Unable to hydrate the [${String(name)}] model: it is not registered.`);
3228
+ return registry.resolve(name);
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
+ }
3235
+ async function hydrate(value, registry) {
3236
+ if (!isTuple(value)) return value;
3237
+ const [inner, meta] = value;
3238
+ switch (meta.s) {
3239
+ case "u":
3240
+ return void 0;
3241
+ case "dt":
3242
+ return new Date(String(inner));
3243
+ case "arr":
3244
+ return 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
+ }
3249
+ case "obj": {
3250
+ const out = {};
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 });
3253
+ }
3254
+ return out;
3255
+ }
3256
+ case "mdl":
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);
3261
+ case "elcln": {
3262
+ const cls = modelClass(registry, meta.class);
3263
+ const keys = meta.keys ?? [];
3264
+ const model = new cls();
3265
+ const keyName = model.getKeyName();
3266
+ const found = [...await cls.query().whereIn(keyName, keys).get()];
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);
3270
+ }
3271
+ }
3272
+ throw new LiveError(`Unknown snapshot value type [${String(meta.s)}].`);
3273
+ }
3274
+ var HOOKS = /* @__PURE__ */ new Set(["constructor", "render", "placeholder", "mount", "boot", "booted", "hydrate", "dehydrate", "updating", "updated", "rendering", "rendered", "exception"]);
3275
+ var HOOK_PREFIX = /^(updating|updated|hydrate|dehydrate)[A-Z_]/;
3276
+ var UNSAFE_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
3277
+ var baseApi;
3278
+ var isBaseApi = (name) => (baseApi ??= new Set(Object.getOwnPropertyNames(Component.prototype))).has(name);
3279
+ var INDEX = /^(?:0|[1-9]\d*)$/;
3280
+ function isArraySlot(array, segment) {
3281
+ return INDEX.test(segment) && Number(segment) <= array.length;
3282
+ }
3283
+ function isCallable(instance, method) {
3284
+ if (method === "" || method.startsWith("_") || method.startsWith("$") || method.startsWith("#")) return false;
3285
+ if (HOOKS.has(method) || HOOK_PREFIX.test(method) || isBaseApi(method)) return false;
3286
+ if (lockedOf(instance.constructor).has(method)) return false;
3287
+ const isBase = (proto) => proto === Component.prototype || proto === Object.prototype;
3288
+ for (let proto = Object.getPrototypeOf(instance); proto !== null && !isBase(proto); proto = Object.getPrototypeOf(proto)) {
3289
+ const descriptor = Object.getOwnPropertyDescriptor(proto, method);
3290
+ if (descriptor !== void 0) return typeof descriptor.value === "function";
3291
+ }
3292
+ return false;
3293
+ }
3294
+ function isUpdatable(instance, path) {
3295
+ const segments = path.split(".");
3296
+ const root = segments[0];
3297
+ if (root === "" || root.startsWith("_") || segments.some((segment) => segment === "" || UNSAFE_SEGMENTS.has(segment))) return false;
3298
+ if (!Object.hasOwn(instance, root)) return false;
3299
+ const descriptor = Object.getOwnPropertyDescriptor(instance, root);
3300
+ if (!descriptor.enumerable || descriptor.get !== void 0) return false;
3301
+ const cls = instance.constructor;
3302
+ if (lockedOf(cls).has(root) || computedOf(cls).has(root)) return false;
3303
+ let node = descriptor.value;
3304
+ for (const segment of segments.slice(1)) {
3305
+ if (isModel(node) || node instanceof Collection) return false;
3306
+ if (Array.isArray(node) && !isArraySlot(node, segment)) return false;
3307
+ if (typeof node !== "object" || node === null || !Object.hasOwn(node, segment)) return true;
3308
+ node = node[segment];
3309
+ }
3310
+ return !isModel(node) && !(node instanceof Collection);
3311
+ }
2584
3312
  var COMPONENT = /* @__PURE__ */ Symbol.for("oriel:live.component");
2585
3313
  var states = /* @__PURE__ */ new WeakMap();
2586
3314
  function stateOf(component) {
@@ -2596,14 +3324,24 @@ var ErrorBag = class {
2596
3324
  this.messages = messages;
2597
3325
  }
2598
3326
  messages;
2599
- has(key) {
2600
- 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 });
2601
3336
  }
2602
- first(key) {
2603
- return key === void 0 ? this.all()[0] : this.messages[key]?.[0];
3337
+ has(key2) {
3338
+ return (this.#own(key2)?.length ?? 0) > 0;
2604
3339
  }
2605
- get(key) {
2606
- return [...this.messages[key] ?? []];
3340
+ first(key2) {
3341
+ return key2 === void 0 ? this.all()[0] : this.#own(key2)?.[0];
3342
+ }
3343
+ get(key2) {
3344
+ return [...this.#own(key2) ?? []];
2607
3345
  }
2608
3346
  all() {
2609
3347
  return Object.values(this.messages).flat();
@@ -2618,7 +3356,7 @@ var ErrorBag = class {
2618
3356
  return this.all().length;
2619
3357
  }
2620
3358
  toObject() {
2621
- 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]]));
2622
3360
  }
2623
3361
  };
2624
3362
  var PendingEvent = class {
@@ -2645,6 +3383,13 @@ var Component = class {
2645
3383
  static layout;
2646
3384
  /** The page title passed to the layout as `title`. */
2647
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;
2648
3393
  /** Static fallbacks for runtimes without decorators: { event: 'method' }, locked names, computed getter names. */
2649
3394
  static listeners;
2650
3395
  static locked;
@@ -2668,12 +3413,18 @@ var Component = class {
2668
3413
  if (options.navigate === true) state.redirectUsingNavigate = true;
2669
3414
  else delete state.redirectUsingNavigate;
2670
3415
  }
2671
- 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;
2672
3422
  this.redirect(inject(UrlGenerator).route(name, parameters), options);
2673
3423
  }
2674
3424
  /**
2675
- * Validates the public state. On success clears the error bag and returns the validated data; on failure fills the
2676
- * 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`).
2677
3428
  */
2678
3429
  async validate(rules, messages, attributes) {
2679
3430
  const validator = inject(ValidationFactory).make(Object.fromEntries(Object.entries(this)), rules, messages, attributes);
@@ -2687,15 +3438,14 @@ var Component = class {
2687
3438
  throw error;
2688
3439
  }
2689
3440
  }
2690
- addError(key, message) {
2691
- ;
2692
- (stateOf(this).errors[key] ??= []).push(message);
3441
+ addError(key2, message) {
3442
+ this.errors.add(key2, message);
2693
3443
  }
2694
3444
  /** Clears every error, or only those of `keys`. */
2695
3445
  resetErrorBag(...keys) {
2696
3446
  const state = stateOf(this);
2697
3447
  if (keys.length === 0) state.errors = {};
2698
- else for (const key of keys) delete state.errors[key];
3448
+ else for (const key2 of keys) delete state.errors[key2];
2699
3449
  }
2700
3450
  get errors() {
2701
3451
  return new ErrorBag(stateOf(this).errors);
@@ -2711,6 +3461,23 @@ var Component = class {
2711
3461
  skipRender() {
2712
3462
  stateOf(this).skipRender = true;
2713
3463
  }
3464
+ /**
3465
+ * An action for an `o:` directive, typed against its method: `o:click={this.$action(this.delete, tag.id)}` writes
3466
+ * `delete(5)`, and a string where `delete` takes a number is a type error. The arguments are written as JSON, the
3467
+ * trailing `undefined` ones left out so the method's defaults apply. The method must be one of this component's
3468
+ * actions (a public method of its class, not a hook and not @Locked); anything else throws while rendering.
3469
+ */
3470
+ $action(method, ...params) {
3471
+ const name = typeof method === "function" ? method.name : "";
3472
+ if (name === "" || this[name] !== method || !isCallable(this, name)) {
3473
+ throw new LiveError(
3474
+ `[${name === "" ? "anonymous function" : name}] is not an action of [${this.getName() || this.constructor.name}]: pass one of its public methods, as in this.$action(this.save).`
3475
+ );
3476
+ }
3477
+ let count = params.length;
3478
+ while (count > 0 && params[count - 1] === void 0) count--;
3479
+ return count === 0 ? name : `${name}(${params.slice(0, count).map((param) => JSON.stringify(param) ?? "null").join(", ")})`;
3480
+ }
2714
3481
  /** A full-page component as a route action: Route.get('/counter', Counter). Route parameters go to mount(). */
2715
3482
  async __invoke() {
2716
3483
  return inject(LiveManager).page(this);
@@ -2722,60 +3489,21 @@ function trackFlash(session, { saved = true } = {}) {
2722
3489
  return (redirected) => {
2723
3490
  if (redirected) return;
2724
3491
  const flashed = session.get("_flash.new", []);
2725
- const fresh = flashed.filter((key) => !before.has(key));
3492
+ const fresh = flashed.filter((key2) => !before.has(key2));
2726
3493
  if (fresh.length === 0) return;
2727
- session.put("_flash.new", flashed.filter((key) => before.has(key)));
3494
+ session.put("_flash.new", flashed.filter((key2) => before.has(key2)));
2728
3495
  if (!saved) return session.forget(...fresh);
2729
3496
  const old = session.get("_flash.old", []);
2730
- session.put("_flash.old", [...old, ...fresh.filter((key) => !old.includes(key))]);
3497
+ session.put("_flash.old", [...old, ...fresh.filter((key2) => !old.includes(key2))]);
2731
3498
  };
2732
3499
  }
2733
- var LiveError = class extends Error {
2734
- name = "LiveError";
2735
- kind = "Server";
2736
- status = 500;
2737
- };
2738
- var CorruptComponentPayloadError = class extends LiveError {
2739
- name = "CorruptComponentPayloadError";
2740
- kind = "Authorization";
2741
- status = 403;
2742
- constructor(component = "unknown") {
2743
- super(`Live encountered corrupt data when trying to hydrate the [${component}] component.`);
2744
- }
2745
- };
2746
- var MethodNotCallableError = class extends LiveError {
2747
- name = "MethodNotCallableError";
2748
- kind = "Authorization";
2749
- status = 403;
2750
- constructor(method, component) {
2751
- super(`Unable to call component method. Public method [${method}] not found on component [${component}].`);
2752
- }
2753
- };
2754
- var PropertyNotUpdatableError = class extends LiveError {
2755
- name = "PropertyNotUpdatableError";
2756
- kind = "Authorization";
2757
- status = 403;
2758
- constructor(path, component) {
2759
- super(`Cannot update property [${path}] on component [${component}]: it is not a public, unlocked property.`);
2760
- }
2761
- };
2762
- var LiveComponentNotFoundError = class extends LiveError {
2763
- name = "LiveComponentNotFoundError";
2764
- kind = "Http";
2765
- status = 404;
2766
- constructor(component) {
2767
- super(`Unable to find component: [${component}].`);
2768
- }
2769
- };
2770
- var InvalidLiveRequestError = class extends LiveError {
2771
- name = "InvalidLiveRequestError";
2772
- kind = "Http";
2773
- status = 400;
2774
- };
2775
3500
  var ESCAPES = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#039;" };
2776
3501
  function escapeAttribute2(value) {
2777
3502
  return value.replace(/[&<>"']/g, (char) => ESCAPES[char]);
2778
3503
  }
3504
+ function stripTags(html) {
3505
+ return html.replace(/<[^>]*>/g, "");
3506
+ }
2779
3507
  var VOID = /* @__PURE__ */ new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr"]);
2780
3508
  var RAW_TEXT = /* @__PURE__ */ new Set(["script", "style", "textarea", "title"]);
2781
3509
  var TOKEN = /<\/([A-Za-z][\w:-]*)\s*>|<([A-Za-z][\w:-]*)(?:\s+[^\s"'<>/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*\s*(\/?)>/g;
@@ -2811,15 +3539,245 @@ function rootTag(html, component) {
2811
3539
  if (depth === 0) rootEnd = match.index + token2.length;
2812
3540
  }
2813
3541
  }
2814
- if (root === void 0 || depth !== 0 || source.slice(rootEnd).trim() !== "") fail22();
2815
- return root;
3542
+ if (root === void 0 || depth !== 0 || source.slice(rootEnd).trim() !== "") fail22();
3543
+ return root;
3544
+ }
3545
+ function insertAttributesIntoRoot(html, attributes) {
3546
+ const source = html.trim();
3547
+ const match = /^((?:<!--[\s\S]*?-->\s*)*<[A-Za-z][\w:-]*)/.exec(source);
3548
+ if (match === null) throw new LiveError("Live could not find the root element of the component.");
3549
+ const rendered = Object.entries(attributes).map(([name, value]) => ` ${name}="${escapeAttribute2(value)}"`).join("");
3550
+ return match[1] + rendered + source.slice(match[1].length);
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}].`);
2816
3771
  }
2817
- function insertAttributesIntoRoot(html, attributes) {
2818
- const source = html.trim();
2819
- const match = /^((?:<!--[\s\S]*?-->\s*)*<[A-Za-z][\w:-]*)/.exec(source);
2820
- if (match === null) throw new LiveError("Live could not find the root element of the component.");
2821
- const rendered = Object.entries(attributes).map(([name, value]) => ` ${name}="${escapeAttribute2(value)}"`).join("");
2822
- return match[1] + rendered + source.slice(match[1].length);
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) };
2823
3781
  }
2824
3782
  var fail = (message, actual, expected) => {
2825
3783
  throw new AssertionError({ message, actual, expected, operator: "live" });
@@ -2827,14 +3785,14 @@ var fail = (message, actual, expected) => {
2827
3785
  var getPath = (target2, path) => path.split(".").reduce((node, segment) => typeof node === "object" && node !== null ? node[segment] : void 0, target2);
2828
3786
  var TestRequestScope = token("Testing.RequestScope");
2829
3787
  function requestScope2() {
2830
- const container = currentContainer() ?? Container.getInstance();
2831
- 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();
2832
3790
  }
2833
3791
  function currentSession() {
2834
- const container = currentContainer();
2835
- if (container?.bound(Session) !== true) return void 0;
3792
+ const container2 = currentContainer();
3793
+ if (container2?.bound(Session) !== true) return void 0;
2836
3794
  try {
2837
- return container.make(Session);
3795
+ return container2.make(Session);
2838
3796
  } catch {
2839
3797
  return void 0;
2840
3798
  }
@@ -2854,11 +3812,14 @@ var Testable = class _Testable {
2854
3812
  #html;
2855
3813
  #effects;
2856
3814
  #instance;
2857
- /** 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
+ */
2858
3819
  static async create(component, params = {}) {
2859
3820
  const scope = requestScope2();
2860
3821
  const manager = inject(LiveManager);
2861
- 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 })));
2862
3823
  }
2863
3824
  /** The component instance after the last request. */
2864
3825
  instance() {
@@ -2876,9 +3837,24 @@ var Testable = class _Testable {
2876
3837
  effects() {
2877
3838
  return this.#effects;
2878
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
+ */
2879
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));
2880
3848
  return this.#update(typeof path === "string" ? { [path]: value } : path, []);
2881
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
+ }
2882
3858
  async call(method, ...params) {
2883
3859
  return this.#update({}, [{ method, params }]);
2884
3860
  }
@@ -2928,6 +3904,17 @@ var Testable = class _Testable {
2928
3904
  for (const value of values) if (!this.html().includes(value)) fail(`Failed asserting that [${value}] is in the rendered HTML.`, this.html(), value);
2929
3905
  return this;
2930
3906
  }
3907
+ /** Each value, HTML-escaped, appears in the rendered text: the HTML with its tags stripped (Livewire's assertSeeText). */
3908
+ assertSeeText(...values) {
3909
+ const text = stripTags(this.html());
3910
+ for (const value of values) if (!text.includes(escapeAttribute2(value))) fail(`Failed asserting that [${value}] is in the rendered text.`, text, value);
3911
+ return this;
3912
+ }
3913
+ assertDontSeeText(...values) {
3914
+ const text = stripTags(this.html());
3915
+ for (const value of values) if (text.includes(escapeAttribute2(value))) fail(`Failed asserting that [${value}] is not in the rendered text.`, text, value);
3916
+ return this;
3917
+ }
2931
3918
  assertDispatched(name, params) {
2932
3919
  const dispatched = (this.#effects.dispatches ?? []).filter((event) => event.name === name);
2933
3920
  if (dispatched.length === 0) fail(`Failed asserting that an event [${name}] was dispatched.`, this.#effects.dispatches ?? [], name);
@@ -2945,14 +3932,14 @@ var Testable = class _Testable {
2945
3932
  const errors = stateOf(this.#instance).errors;
2946
3933
  const wanted = [keys].flat();
2947
3934
  if (wanted.length === 0 && Object.keys(errors).length === 0) fail("Component has no errors.", errors);
2948
- 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);
2949
3936
  return this;
2950
3937
  }
2951
3938
  /** No errors for the keys given (or none at all with no keys). */
2952
3939
  assertHasNoErrors(keys = []) {
2953
3940
  const errors = stateOf(this.#instance).errors;
2954
3941
  const wanted = [keys].flat();
2955
- 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);
2956
3943
  if (found.length > 0) fail(`Component has errors: "${found.join('", "')}"`, errors);
2957
3944
  return this;
2958
3945
  }
@@ -2973,12 +3960,12 @@ var Testable = class _Testable {
2973
3960
  return this;
2974
3961
  }
2975
3962
  };
2976
- async function live(component, params = {}, key) {
2977
- 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));
2978
3965
  return { toHtml: () => html };
2979
3966
  }
2980
- function LiveComponent({ component, "o:key": key, children: _children, ...params }) {
2981
- return live(component, params, key);
3967
+ function LiveComponent({ component, "o:key": key2, children: _children, ...params }) {
3968
+ return live(component, params, key2);
2982
3969
  }
2983
3970
  var Live = Object.assign(LiveComponent, {
2984
3971
  /** Registers a component under a name (components in app/Live are registered at boot). */
@@ -2990,22 +3977,38 @@ var Live = Object.assign(LiveComponent, {
2990
3977
  return Testable.create(component, params);
2991
3978
  }
2992
3979
  });
2993
- function canonicalJson(value) {
2994
- if (Array.isArray(value)) return `[${value.map((item) => canonicalJson(item === void 0 ? null : item)).join(",")}]`;
2995
- if (typeof value === "object" && value !== null) {
2996
- const entries = Object.keys(value).sort().filter((key) => value[key] !== void 0).map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`);
2997
- 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);
2998
3994
  }
2999
- return JSON.stringify(value);
3995
+ return (typeof output === "string" ? output : await views.renderToString(output)).trim();
3000
3996
  }
3001
- function checksum(key, payload) {
3002
- 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 });
3003
4000
  }
3004
- function verifyChecksum(key, payload, given) {
3005
- if (typeof given !== "string") return false;
3006
- const expected = Buffer.from(checksum(key, payload), "utf8");
3007
- const actual = Buffer.from(given, "utf8");
3008
- 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
+ }
3009
4012
  }
3010
4013
  function componentName(file) {
3011
4014
  return file.replace(/\.(tsx|ts|jsx|js|mts|mjs)$/, "").split(/[\\/]/).map((segment) => Str.kebab(segment)).join(".");
@@ -3023,9 +4026,9 @@ async function discover(directory) {
3023
4026
  for (const file of componentFiles(directory)) {
3024
4027
  const module = await import(
3025
4028
  /* @vite-ignore */
3026
- pathToFileURL2(join5(directory, file)).href
4029
+ pathToFileURL2(join6(directory, file)).href
3027
4030
  );
3028
- found.push([componentName(relative2(directory, join5(directory, file))), module.default]);
4031
+ found.push([componentName(relative2(directory, join6(directory, file))), module.default]);
3029
4032
  }
3030
4033
  return found;
3031
4034
  }
@@ -3043,7 +4046,7 @@ function routePath(path) {
3043
4046
  const trimmed = path.replace(/^\/+|\/+$/g, "").replace(/\/{2,}/g, "/");
3044
4047
  return trimmed === "" ? "" : `/${trimmed}`;
3045
4048
  }
3046
- function expand(references, aliases, groups, out = [], visiting = /* @__PURE__ */ new Set()) {
4049
+ function expand2(references, aliases, groups, out = [], visiting = /* @__PURE__ */ new Set()) {
3047
4050
  for (const reference of references) {
3048
4051
  if (typeof reference === "function") {
3049
4052
  out.push({ cls: reference, parameters: [] });
@@ -3054,7 +4057,7 @@ function expand(references, aliases, groups, out = [], visiting = /* @__PURE__ *
3054
4057
  if (index === -1 && Object.hasOwn(groups, name)) {
3055
4058
  if (visiting.has(name)) continue;
3056
4059
  visiting.add(name);
3057
- expand(groups[name], aliases, groups, out, visiting);
4060
+ expand2(groups[name], aliases, groups, out, visiting);
3058
4061
  visiting.delete(name);
3059
4062
  } else if (Object.hasOwn(aliases, name)) {
3060
4063
  out.push({ cls: aliases[name], parameters: index === -1 ? [] : reference.slice(index + 1).split(",") });
@@ -3083,17 +4086,17 @@ function persistentPass(router, persistent, method, path) {
3083
4086
  const aliases = router.getMiddleware();
3084
4087
  const groups = router.getMiddlewareGroups();
3085
4088
  const wanted = new Set(persistent.flatMap((name) => Object.hasOwn(aliases, name) ? [aliases[name]] : []));
3086
- const excluded = new Set(expand(route2.excludedMiddleware(), aliases, groups).map((entry) => entry.cls));
4089
+ const excluded = new Set(expand2(route2.excludedMiddleware(), aliases, groups).map((entry) => entry.cls));
3087
4090
  const ids = /* @__PURE__ */ new Map();
3088
4091
  const seen = /* @__PURE__ */ new Set();
3089
- const middleware = expand(route2.gatherMiddleware(), aliases, groups).filter((entry) => {
4092
+ const middleware = expand2(route2.gatherMiddleware(), aliases, groups).filter((entry) => {
3090
4093
  if (!ids.has(entry.cls)) ids.set(entry.cls, ids.size);
3091
- const key = `${ids.get(entry.cls)}:${entry.parameters.join(",")}`;
3092
- if (!wanted.has(entry.cls) || excluded.has(entry.cls) || seen.has(key)) return false;
3093
- seen.add(key);
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);
3094
4097
  return true;
3095
4098
  });
3096
- 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));
3097
4100
  for (const name of ["auth", "guest"]) if (Object.hasOwn(aliases, name)) early.add(aliases[name]);
3098
4101
  const before = middleware.filter((entry) => early.has(entry.cls));
3099
4102
  const after = middleware.filter((entry) => !early.has(entry.cls));
@@ -3102,38 +4105,38 @@ function persistentPass(router, persistent, method, path) {
3102
4105
  function persistentMiddleware(router, persistent, method, path) {
3103
4106
  return [...persistentPass(router, persistent, method, path)?.middleware ?? []];
3104
4107
  }
3105
- function currentRoute(container) {
3106
- if (!container.bound(CurrentRoute)) return void 0;
4108
+ function currentRoute(container2) {
4109
+ if (!container2.bound(CurrentRoute)) return void 0;
3107
4110
  try {
3108
- return container.make(CurrentRoute);
4111
+ return container2.make(CurrentRoute);
3109
4112
  } catch {
3110
4113
  return void 0;
3111
4114
  }
3112
4115
  }
3113
- async function throughMiddleware(container, request2, pass, destination) {
3114
- const original = currentRoute(container);
4116
+ async function throughMiddleware(container2, request2, pass, destination) {
4117
+ const original2 = currentRoute(container2);
3115
4118
  const page = { route: pass.route, parameters: { ...pass.values }, middleware: [] };
3116
4119
  let swapped = false;
3117
4120
  const show = () => {
3118
- container.instance(CurrentRoute, page);
4121
+ container2.instance(CurrentRoute, page);
3119
4122
  swapped = true;
3120
4123
  };
3121
4124
  const restore = () => {
3122
4125
  if (!swapped) return;
3123
- if (original === void 0) container.forgetInstance(CurrentRoute);
3124
- else container.instance(CurrentRoute, original);
4126
+ if (original2 === void 0) container2.forgetInstance(CurrentRoute);
4127
+ else container2.instance(CurrentRoute, original2);
3125
4128
  swapped = false;
3126
4129
  };
3127
4130
  let ran;
3128
4131
  const run = async (index) => {
3129
- 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));
3130
4133
  if (index === pass.middleware.length) {
3131
4134
  restore();
3132
4135
  ran = { result: await destination() };
3133
4136
  return new Response(null, { status: 204 });
3134
4137
  }
3135
4138
  const { cls, parameters } = pass.middleware[index];
3136
- const instance = container.make(cls);
4139
+ const instance = container2.make(cls);
3137
4140
  const next = () => run(index + 1);
3138
4141
  return instance.handle(request2, next, ...parameters);
3139
4142
  };
@@ -3145,144 +4148,6 @@ async function throughMiddleware(container, request2, pass, destination) {
3145
4148
  restore();
3146
4149
  }
3147
4150
  }
3148
- function isModel(value) {
3149
- if (typeof value !== "object" || value === null) return false;
3150
- const candidate = value;
3151
- return typeof candidate.getKey === "function" && typeof candidate.getKeyName === "function" && typeof candidate.getTable === "function" && typeof candidate.exists === "boolean";
3152
- }
3153
- var isPlainObject2 = (value) => {
3154
- const proto = Object.getPrototypeOf(value);
3155
- return proto === Object.prototype || proto === null;
3156
- };
3157
- var isTuple = (value) => Array.isArray(value) && value.length === 2 && typeof value[1] === "object" && value[1] !== null && !Array.isArray(value[1]) && typeof value[1].s === "string";
3158
- function classNameOf(registry, cls) {
3159
- return registry.nameOf(cls) ?? registry.register(cls);
3160
- }
3161
- function dehydrate(value, registry, path, seen = /* @__PURE__ */ new Set()) {
3162
- switch (typeof value) {
3163
- case "undefined":
3164
- return [null, { s: "u" }];
3165
- case "string":
3166
- case "boolean":
3167
- return value;
3168
- case "number":
3169
- if (!Number.isFinite(value)) throw new LiveError(`Property [${path}] is ${value}: Live state must be JSON (finite numbers only).`);
3170
- return value;
3171
- case "bigint":
3172
- case "symbol":
3173
- case "function":
3174
- throw new LiveError(`Property [${path}] holds a ${typeof value}: Live state may only hold primitives, arrays, plain objects, Dates, models and collections.`);
3175
- }
3176
- if (value === null) return null;
3177
- const object = value;
3178
- if (seen.has(object)) throw new LiveError(`Property [${path}] contains a circular reference.`);
3179
- seen.add(object);
3180
- try {
3181
- if (object instanceof Date) return [object.toISOString(), { s: "dt" }];
3182
- if (Array.isArray(object)) return [object.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), { s: "arr" }];
3183
- if (isModel(object)) {
3184
- const name = classNameOf(registry, object.constructor);
3185
- if (!object.exists) throw new LiveError(`Property [${path}] holds an unsaved [${name}] model: only saved models can be kept between requests.`);
3186
- return [null, { s: "mdl", class: name, key: object.getKey() }];
3187
- }
3188
- if (object instanceof Collection) {
3189
- const items = object.all();
3190
- const first = items[0];
3191
- if (items.length > 0 && isModel(first) && items.every((item) => isModel(item) && item.constructor === first.constructor)) {
3192
- for (const [i, item] of items.entries()) {
3193
- if (!item.exists) throw new LiveError(`Property [${path}.${i}] holds an unsaved model.`);
3194
- }
3195
- const name = classNameOf(registry, first.constructor);
3196
- return [null, { s: "elcln", class: name, keys: items.map((item) => item.getKey()) }];
3197
- }
3198
- return [items.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), { s: "clctn" }];
3199
- }
3200
- if (isPlainObject2(object)) {
3201
- const out = {};
3202
- for (const [key, item] of Object.entries(object)) out[key] = dehydrate(item, registry, `${path}.${key}`, seen);
3203
- return [out, { s: "obj" }];
3204
- }
3205
- const type = object.constructor?.name || "object";
3206
- throw new LiveError(
3207
- `Property [${path}] holds a [${type}] instance: Live state may only hold primitives, arrays, plain objects, Dates, models and collections.`
3208
- );
3209
- } finally {
3210
- seen.delete(object);
3211
- }
3212
- }
3213
- function modelClass(registry, name) {
3214
- if (typeof name !== "string" || !registry.has(name)) throw new LiveError(`Unable to hydrate the [${String(name)}] model: it is not registered.`);
3215
- return registry.resolve(name);
3216
- }
3217
- async function hydrate(value, registry) {
3218
- if (!isTuple(value)) return value;
3219
- const [inner, meta] = value;
3220
- switch (meta.s) {
3221
- case "u":
3222
- return void 0;
3223
- case "dt":
3224
- return new Date(String(inner));
3225
- case "arr":
3226
- return Promise.all(inner.map((item) => hydrate(item, registry)));
3227
- case "clctn":
3228
- return new Collection(await Promise.all(inner.map((item) => hydrate(item, registry))));
3229
- case "obj": {
3230
- const out = {};
3231
- for (const [key, item] of Object.entries(inner)) {
3232
- Object.defineProperty(out, key, { value: await hydrate(item, registry), enumerable: true, writable: true, configurable: true });
3233
- }
3234
- return out;
3235
- }
3236
- case "mdl":
3237
- return modelClass(registry, meta.class).findOrFail(meta.key);
3238
- case "elcln": {
3239
- const cls = modelClass(registry, meta.class);
3240
- const keys = meta.keys ?? [];
3241
- const keyName = new cls().getKeyName();
3242
- const found = [...await cls.query().whereIn(keyName, keys).get()];
3243
- const byKey = new Map(found.map((model) => [String(model.getKey()), model]));
3244
- return new Collection(keys.map((key) => byKey.get(String(key))).filter((model) => model !== void 0));
3245
- }
3246
- }
3247
- throw new LiveError(`Unknown snapshot value type [${String(meta.s)}].`);
3248
- }
3249
- var HOOKS = /* @__PURE__ */ new Set(["constructor", "render", "mount", "boot", "booted", "hydrate", "dehydrate", "updating", "updated", "rendering", "rendered", "exception"]);
3250
- var HOOK_PREFIX = /^(updating|updated|hydrate|dehydrate)[A-Z_]/;
3251
- var UNSAFE_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
3252
- var BASE_API = new Set(Object.getOwnPropertyNames(Component.prototype));
3253
- var INDEX = /^(?:0|[1-9]\d*)$/;
3254
- function isArraySlot(array, segment) {
3255
- return INDEX.test(segment) && Number(segment) <= array.length;
3256
- }
3257
- function isCallable(instance, method) {
3258
- if (method === "" || method.startsWith("_") || method.startsWith("$") || method.startsWith("#")) return false;
3259
- if (HOOKS.has(method) || HOOK_PREFIX.test(method) || BASE_API.has(method)) return false;
3260
- if (lockedOf(instance.constructor).has(method)) return false;
3261
- const isBase = (proto) => proto === Component.prototype || proto === Object.prototype;
3262
- for (let proto = Object.getPrototypeOf(instance); proto !== null && !isBase(proto); proto = Object.getPrototypeOf(proto)) {
3263
- const descriptor = Object.getOwnPropertyDescriptor(proto, method);
3264
- if (descriptor !== void 0) return typeof descriptor.value === "function";
3265
- }
3266
- return false;
3267
- }
3268
- function isUpdatable(instance, path) {
3269
- const segments = path.split(".");
3270
- const root = segments[0];
3271
- if (root === "" || root.startsWith("_") || segments.some((segment) => segment === "" || UNSAFE_SEGMENTS.has(segment))) return false;
3272
- if (!Object.hasOwn(instance, root)) return false;
3273
- const descriptor = Object.getOwnPropertyDescriptor(instance, root);
3274
- if (!descriptor.enumerable || descriptor.get !== void 0) return false;
3275
- const cls = instance.constructor;
3276
- if (lockedOf(cls).has(root) || computedOf(cls).has(root)) return false;
3277
- let node = descriptor.value;
3278
- for (const segment of segments.slice(1)) {
3279
- if (isModel(node) || node instanceof Collection) return false;
3280
- if (Array.isArray(node) && !isArraySlot(node, segment)) return false;
3281
- if (typeof node !== "object" || node === null || !Object.hasOwn(node, segment)) return true;
3282
- node = node[segment];
3283
- }
3284
- return !isModel(node) && !(node instanceof Collection);
3285
- }
3286
4151
  var PAGINATED = /* @__PURE__ */ Symbol.for("oriel:live.pagination");
3287
4152
  function toPage(value) {
3288
4153
  const page = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : Number.NaN;
@@ -3340,18 +4205,19 @@ var livePaginationState = {
3340
4205
  }
3341
4206
  };
3342
4207
  var renderContext = new AsyncLocalStorage2();
4208
+ var loading = /* @__PURE__ */ new WeakMap();
3343
4209
  var pageRequest = new AsyncLocalStorage2();
3344
- function optional(container, token2) {
3345
- if (!container.bound(token2)) return void 0;
4210
+ function optional(container2, token2) {
4211
+ if (!container2.bound(token2)) return void 0;
3346
4212
  try {
3347
- return container.make(token2);
4213
+ return container2.make(token2);
3348
4214
  } catch {
3349
4215
  return void 0;
3350
4216
  }
3351
4217
  }
3352
- var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3353
- function setOwn(object, key, value) {
3354
- 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 });
3355
4221
  }
3356
4222
  async function hook2(instance, name, ...args) {
3357
4223
  const method = instance[name];
@@ -3395,10 +4261,10 @@ var LiveComponentManager = class {
3395
4261
  return request2.header("X-Oriel-Live") === "true";
3396
4262
  }
3397
4263
  /** The first render: HTML with o:id, o:snapshot and o:effects on the root element. */
3398
- async mount(component, params = {}, key) {
4264
+ async mount(component, params = {}, key2) {
3399
4265
  const context = renderContext.getStore();
3400
4266
  if (context === void 0) return (await this.mountComponent(component, params)).html;
3401
- const childKey = key ?? `lw-${context.index++}`;
4267
+ const childKey = key2 ?? `lw-${context.index++}`;
3402
4268
  const existing = Object.hasOwn(context.previous, childKey) ? context.previous[childKey] : void 0;
3403
4269
  if (existing !== void 0) {
3404
4270
  setOwn(context.children, childKey, existing);
@@ -3408,7 +4274,11 @@ var LiveComponentManager = class {
3408
4274
  setOwn(context.children, childKey, [result.tag, result.snapshot.memo.id]);
3409
4275
  return result.html;
3410
4276
  }
3411
- 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) {
3412
4282
  const cls = typeof component === "string" ? this.resolve(component) : component;
3413
4283
  if (!isComponentClass(cls)) throw new LiveError(`[${cls.name}] is not a Live component: it must extend Component.`);
3414
4284
  const name = this.nameOf(cls);
@@ -3416,7 +4286,10 @@ var LiveComponentManager = class {
3416
4286
  const state = stateOf(instance);
3417
4287
  state.id = randomBytes2(15).toString("base64url");
3418
4288
  state.name = name;
4289
+ const { lazy: _lazy, ...params } = embedParams;
4290
+ const lazy = lazyMode(cls, embedParams);
3419
4291
  await hook2(instance, "boot");
4292
+ if (lazy !== void 0) return this.mountPlaceholder(instance, params, lazy);
3420
4293
  for (const [property, value] of Object.entries(params)) {
3421
4294
  if (Object.hasOwn(instance, property)) instance[property] = value;
3422
4295
  }
@@ -3433,6 +4306,30 @@ var LiveComponentManager = class {
3433
4306
  const attributes = { "o:snapshot": JSON.stringify(snapshot), "o:effects": JSON.stringify(effects), "o:id": state.id };
3434
4307
  return { instance, snapshot, effects, html: insertAttributesIntoRoot(html, attributes), tag };
3435
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
+ }
3436
4333
  /** Applies each component's updates and calls, re-renders it and returns its new snapshot and effects. */
3437
4334
  async update(request2) {
3438
4335
  const components = [];
@@ -3445,6 +4342,7 @@ var LiveComponentManager = class {
3445
4342
  }
3446
4343
  async updateComponent(snapshotJson, updates, calls) {
3447
4344
  const snapshot = this.verify(snapshotJson);
4345
+ const load = await this.lazyLoad(snapshot.memo, updates, calls);
3448
4346
  const cls = this.resolve(snapshot.memo.name);
3449
4347
  const instance = this.container().make(cls);
3450
4348
  const state = stateOf(instance);
@@ -3459,13 +4357,21 @@ var LiveComponentManager = class {
3459
4357
  this.prepareComputed(instance);
3460
4358
  const returns = [];
3461
4359
  await renderPaginated(instance, this.pageUrl(snapshot.memo.path), async () => {
3462
- await hook2(instance, "hydrate");
4360
+ if (load === void 0) await hook2(instance, "hydrate");
3463
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
+ }
3464
4370
  for (const [path, value] of Object.entries(updates)) await this.applyUpdate(instance, path, value);
3465
- for (const call of calls) returns.push(await this.call(instance, call.method, call.params));
4371
+ for (const call of load === void 0 ? calls : []) returns.push(await this.call(instance, call.method, call.params));
3466
4372
  } catch (error) {
3467
4373
  const errors = error.errors;
3468
- if (error.kind !== "Validation" || !isRecord(errors)) throw error;
4374
+ if (error.kind !== "Validation" || !isRecord2(errors)) throw error;
3469
4375
  state.errors = { ...errors };
3470
4376
  }
3471
4377
  });
@@ -3476,12 +4382,24 @@ var LiveComponentManager = class {
3476
4382
  if (!state.skipRender) {
3477
4383
  ;
3478
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);
3479
4387
  html = insertAttributesIntoRoot(html, { "o:id": state.id });
3480
4388
  effects.html = html;
3481
4389
  }
3482
4390
  if (calls.length > 0) effects.returns = returns;
3483
4391
  return { instance, snapshot: await this.snapshot(instance, memo), effects, html, tag };
3484
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
+ }
3485
4403
  /**
3486
4404
  * The route action of POST /oriel/live/update. Each component's update runs behind the persistent middleware of the
3487
4405
  * route that rendered its page (Livewire's): `auth`, `verified`, `guest` and `can`, with the page route's parameters
@@ -3491,17 +4409,17 @@ var LiveComponentManager = class {
3491
4409
  */
3492
4410
  async handle(request2) {
3493
4411
  if (!this.isLiveRequest(request2)) throw new InvalidLiveRequestError("Live update requests need the X-Oriel-Live header.");
3494
- const container = this.container();
3495
- const persistent = optional(container, ConfigRepository)?.get("live.persistent_middleware", PERSISTENT_MIDDLEWARE) ?? PERSISTENT_MIDDLEWARE;
4412
+ const container2 = this.container();
4413
+ const persistent = optional(container2, ConfigRepository)?.get("live.persistent_middleware", PERSISTENT_MIDDLEWARE) ?? PERSISTENT_MIDDLEWARE;
3496
4414
  const components = [];
3497
- const flashed = trackFlash(optional(container, Session));
4415
+ const flashed = trackFlash(optional(container2, Session));
3498
4416
  for (const component of this.payload(request2).components) {
3499
4417
  const { memo } = this.verify(component.snapshot);
3500
- const pass = persistentPass(container.make(Router), persistent, memo.method, memo.path);
4418
+ const pass = persistentPass(container2.make(Router), persistent, memo.method, memo.path);
3501
4419
  const update = () => this.updateComponent(component.snapshot, component.updates, component.calls);
3502
4420
  const result = await pageRequest.run(
3503
4421
  { path: memo.path, method: memo.method },
3504
- () => 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)
3505
4423
  );
3506
4424
  if (result instanceof Response) return result;
3507
4425
  components.push({ snapshot: JSON.stringify(result.snapshot), effects: result.effects });
@@ -3511,19 +4429,19 @@ var LiveComponentManager = class {
3511
4429
  }
3512
4430
  /** A full-page component (Route.get('/counter', Counter)): mounted with the route parameters, inside the layout. */
3513
4431
  async page(component) {
3514
- const container = this.container();
3515
- const params = { ...optional(container, CurrentRoute)?.parameters ?? {} };
4432
+ const container2 = this.container();
4433
+ const params = { ...optional(container2, CurrentRoute)?.parameters ?? {} };
3516
4434
  const cls = component.constructor;
3517
4435
  const result = await this.mountComponent(cls, params, component);
3518
4436
  const redirect2 = result.effects.redirect;
3519
4437
  if (redirect2 !== void 0) {
3520
- 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 } });
3521
4439
  }
3522
- 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";
3523
4441
  const html = result.html;
3524
4442
  const data = { children: { toHtml: () => html } };
3525
4443
  if (cls.title !== void 0) data.title = cls.title;
3526
- return container.make(ViewFactory).make(layout, data);
4444
+ return container2.make(ViewFactory).make(layout, data);
3527
4445
  }
3528
4446
  /** Parses and checks a snapshot: a bad checksum throws before anything is hydrated. */
3529
4447
  verify(snapshotJson) {
@@ -3533,13 +4451,13 @@ var LiveComponentManager = class {
3533
4451
  } catch {
3534
4452
  throw new CorruptComponentPayloadError();
3535
4453
  }
3536
- 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();
3537
4455
  const memo = snapshot.memo;
3538
4456
  const name = typeof memo.name === "string" ? memo.name : "unknown";
3539
- const key = this.key();
4457
+ const key2 = this.key();
3540
4458
  let valid;
3541
4459
  try {
3542
- valid = verifyChecksum(key, { data: snapshot.data, memo: snapshot.memo }, snapshot.checksum);
4460
+ valid = verifyChecksum(key2, { data: snapshot.data, memo: snapshot.memo }, snapshot.checksum);
3543
4461
  } catch {
3544
4462
  valid = false;
3545
4463
  }
@@ -3558,9 +4476,9 @@ var LiveComponentManager = class {
3558
4476
  } catch {
3559
4477
  throw new InvalidLiveRequestError("The Live update request body is not valid JSON.");
3560
4478
  }
3561
- const components = isRecord(body) ? body.components : void 0;
4479
+ const components = isRecord2(body) ? body.components : void 0;
3562
4480
  const valid = Array.isArray(components) && components.every(
3563
- (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))
3564
4482
  );
3565
4483
  if (!valid) throw new InvalidLiveRequestError("The Live update request body is malformed.");
3566
4484
  return body;
@@ -3579,10 +4497,10 @@ var LiveComponentManager = class {
3579
4497
  async applyUpdate(instance, path, value) {
3580
4498
  if (!isUpdatable(instance, path)) throw new PropertyNotUpdatableError(path, stateOf(instance).name);
3581
4499
  const [root, ...rest] = path.split(".");
3582
- const key = rest.length > 0 ? rest.join(".") : void 0;
4500
+ const key2 = rest.length > 0 ? rest.join(".") : void 0;
3583
4501
  const studly = Str.studly(root);
3584
4502
  await hook2(instance, "updating", path, value);
3585
- await hook2(instance, `updating${studly}`, value, key);
4503
+ await hook2(instance, `updating${studly}`, value, key2);
3586
4504
  const target2 = instance;
3587
4505
  if (rest.length === 0) target2[root] = value;
3588
4506
  else {
@@ -3590,20 +4508,20 @@ var LiveComponentManager = class {
3590
4508
  if (Array.isArray(node2) && !isArraySlot(node2, segment)) throw new PropertyNotUpdatableError(path, stateOf(instance).name);
3591
4509
  };
3592
4510
  let node = target2[root];
3593
- if (!isRecord(node) && !Array.isArray(node)) target2[root] = node = {};
4511
+ if (!isRecord2(node) && !Array.isArray(node)) target2[root] = node = {};
3594
4512
  for (const segment of rest.slice(0, -1)) {
3595
4513
  assertSlot(node, segment);
3596
4514
  const next = node[segment];
3597
- node = isRecord(next) || Array.isArray(next) ? next : node[segment] = {};
4515
+ node = isRecord2(next) || Array.isArray(next) ? next : node[segment] = {};
3598
4516
  }
3599
4517
  const last = rest.at(-1);
3600
4518
  assertSlot(node, last);
3601
4519
  Object.defineProperty(node, last, { value, enumerable: true, writable: true, configurable: true });
3602
4520
  }
3603
4521
  await hook2(instance, "updated", path, value);
3604
- await hook2(instance, `updated${studly}`, value, key);
4522
+ await hook2(instance, `updated${studly}`, value, key2);
3605
4523
  }
3606
- /** 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. */
3607
4525
  async call(instance, method, params) {
3608
4526
  const name = stateOf(instance).name;
3609
4527
  switch (method) {
@@ -3613,7 +4531,7 @@ var LiveComponentManager = class {
3613
4531
  return this.applyUpdate(instance, String(params[0]), params[1]);
3614
4532
  case "$toggle": {
3615
4533
  const path = String(params[0]);
3616
- 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);
3617
4535
  return this.applyUpdate(instance, path, !current);
3618
4536
  }
3619
4537
  case "__dispatch": {
@@ -3621,11 +4539,12 @@ var LiveComponentManager = class {
3621
4539
  const listeners = listenersOf(instance.constructor);
3622
4540
  for (const listener of Object.hasOwn(listeners, String(event)) ? listeners[String(event)] : []) {
3623
4541
  if (!isCallable(instance, listener)) throw new MethodNotCallableError(listener, name);
3624
- await instance[listener](isRecord(detail) ? detail : {});
4542
+ await instance[listener](isRecord2(detail) ? detail : {});
3625
4543
  }
3626
4544
  return void 0;
3627
4545
  }
3628
4546
  }
4547
+ if (UPLOAD_CALLS.has(method)) return runUploadCall(instance, method, params, (path, value) => this.applyUpdate(instance, path, value));
3629
4548
  if (!isCallable(instance, method)) throw new MethodNotCallableError(method, name);
3630
4549
  return instance[method](...params);
3631
4550
  }
@@ -3719,9 +4638,9 @@ function registerLiveCommands(kernel, app) {
3719
4638
  const className = segments.at(-1);
3720
4639
  const config = app.bound(ConfigRepository) ? app.make(ConfigRepository) : void 0;
3721
4640
  const directory = config?.get("live.class_path") ?? app.appPath("Live");
3722
- const file = join23(directory, ...segments.slice(0, -1), `${className}.tsx`);
4641
+ const file = join24(directory, ...segments.slice(0, -1), `${className}.tsx`);
3723
4642
  const force = this.option("force") === true;
3724
- if (existsSync22(file) && !force) {
4643
+ if (existsSync23(file) && !force) {
3725
4644
  this.error(`Component [${relative22(app.basePath(), file)}] already exists.`);
3726
4645
  return 1;
3727
4646
  }
@@ -3729,13 +4648,13 @@ function registerLiveCommands(kernel, app) {
3729
4648
  await writeFile2(file, COMPONENT_STUB.replace("{{ class }}", () => className));
3730
4649
  this.info(`Component [${relative22(app.basePath(), file)}] created successfully.`);
3731
4650
  if (this.option("test") === true) {
3732
- const testFile = app.basePath(join23("tests", "Feature", "Live", ...segments.slice(0, -1), `${className}.test.ts`));
3733
- 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) {
3734
4653
  this.error(`Test [${relative22(app.basePath(), testFile)}] already exists.`);
3735
4654
  return 1;
3736
4655
  }
3737
4656
  const name = segments.map((segment) => Str.kebab(segment)).join(".");
3738
- const test = TEST_STUB.replaceAll("{{ class }}", () => className).replace("{{ component }}", () => importPath(testFile, file)).replace("{{ testCase }}", () => importPath(testFile, app.basePath(join23("tests", "TestCase.ts")))).replace("{{ name }}", () => name);
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);
3739
4658
  await mkdir2(dirname2(testFile), { recursive: true });
3740
4659
  await writeFile2(testFile, test);
3741
4660
  this.info(`Test [${relative22(app.basePath(), testFile)}] created successfully.`);
@@ -3748,13 +4667,16 @@ var LiveServiceProvider = class extends ServiceProvider {
3748
4667
  register() {
3749
4668
  this.mergeConfigFrom({ class_path: this.app.appPath("Live"), layout: "components.layouts.app", persistent_middleware: [...PERSISTENT_MIDDLEWARE] }, "live");
3750
4669
  this.app.singleton(LiveComponentManager, () => new LiveComponentManager(this.app));
3751
- this.app.singleton(LiveManager, (container) => container.make(LiveComponentManager));
4670
+ this.app.singleton(LiveManager, (container2) => container2.make(LiveComponentManager));
3752
4671
  this.app.singleton(PaginationState, () => livePaginationState);
3753
4672
  }
3754
4673
  async boot() {
3755
4674
  const manager = this.app.make(LiveComponentManager);
3756
4675
  if (this.app.bound(Router)) {
3757
- 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");
3758
4680
  }
3759
4681
  if (this.app.bound(ConsoleKernel)) registerLiveCommands(this.app.make(ConsoleKernel), this.app);
3760
4682
  const directory = this.app.make(ConfigRepository).get("live.class_path", this.app.appPath("Live"));
@@ -3802,7 +4724,7 @@ function Pagination({ paginator, class: className }) {
3802
4724
  import { AssertionError as AssertionError2 } from "assert";
3803
4725
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
3804
4726
  import { existsSync as existsSync6 } from "fs";
3805
- import { join as join6 } from "path";
4727
+ import { join as join7 } from "path";
3806
4728
  import { createHmac as createHmac2 } from "crypto";
3807
4729
  import { randomUUID } from "crypto";
3808
4730
  import { randomUUID as randomUUID2 } from "crypto";
@@ -3810,6 +4732,129 @@ import { randomUUID as randomUUID3 } from "crypto";
3810
4732
  import { randomBytes as randomBytes3 } from "crypto";
3811
4733
  import { posix } from "path";
3812
4734
  import { deflateSync } from "zlib";
4735
+ var original;
4736
+ var testNow;
4737
+ var realDate = () => original ?? globalThis.Date;
4738
+ function install() {
4739
+ if (original !== void 0) return;
4740
+ const Real = original = globalThis.Date;
4741
+ const current = () => testNow ?? Real.now();
4742
+ const Travelling = new Proxy(Real, {
4743
+ construct: (target2, args, newTarget) => Reflect.construct(target2, args.length === 0 ? [current()] : args, newTarget === Travelling ? target2 : newTarget),
4744
+ apply: (target2) => new target2(current()).toString(),
4745
+ get: (target2, property) => property === "now" ? current : Reflect.get(target2, property, target2)
4746
+ });
4747
+ globalThis.Date = Travelling;
4748
+ }
4749
+ function setTestNow(date) {
4750
+ const time = new (realDate())(date).getTime();
4751
+ if (Number.isNaN(time)) throw new TypeError(`Invalid date: ${String(date)}`);
4752
+ install();
4753
+ testNow = time;
4754
+ return new (realDate())(time);
4755
+ }
4756
+ function thenTravelBack(callback) {
4757
+ let result;
4758
+ try {
4759
+ result = callback();
4760
+ } catch (error) {
4761
+ travelBack();
4762
+ throw error;
4763
+ }
4764
+ if (result instanceof Promise) return result.finally(travelBack);
4765
+ travelBack();
4766
+ return result;
4767
+ }
4768
+ function travelTo(date, callback) {
4769
+ const at = setTestNow(date);
4770
+ if (callback) return thenTravelBack(() => callback(at));
4771
+ }
4772
+ function travelBack() {
4773
+ if (original !== void 0) globalThis.Date = original;
4774
+ original = void 0;
4775
+ testNow = void 0;
4776
+ return /* @__PURE__ */ new Date();
4777
+ }
4778
+ function freezeTime(callback) {
4779
+ const at = new (realDate())(testNow ?? Date.now());
4780
+ if (callback) return travelTo(at, callback);
4781
+ travelTo(at);
4782
+ return at;
4783
+ }
4784
+ function freezeSecond(callback) {
4785
+ const at = new (realDate())(testNow ?? Date.now());
4786
+ at.setMilliseconds(0);
4787
+ if (callback) return travelTo(at, callback);
4788
+ travelTo(at);
4789
+ return at;
4790
+ }
4791
+ function travel(value) {
4792
+ return new Wormhole(value);
4793
+ }
4794
+ var Wormhole = class {
4795
+ constructor(value) {
4796
+ this.value = value;
4797
+ }
4798
+ value;
4799
+ millisecond(callback) {
4800
+ return this.milliseconds(callback);
4801
+ }
4802
+ milliseconds(callback) {
4803
+ return this.#move((date) => date.setTime(date.getTime() + this.value), callback);
4804
+ }
4805
+ second(callback) {
4806
+ return this.seconds(callback);
4807
+ }
4808
+ seconds(callback) {
4809
+ return this.#move((date) => date.setTime(date.getTime() + this.value * 1e3), callback);
4810
+ }
4811
+ minute(callback) {
4812
+ return this.minutes(callback);
4813
+ }
4814
+ minutes(callback) {
4815
+ return this.#move((date) => date.setTime(date.getTime() + this.value * 6e4), callback);
4816
+ }
4817
+ hour(callback) {
4818
+ return this.hours(callback);
4819
+ }
4820
+ hours(callback) {
4821
+ return this.#move((date) => date.setTime(date.getTime() + this.value * 36e5), callback);
4822
+ }
4823
+ day(callback) {
4824
+ return this.days(callback);
4825
+ }
4826
+ days(callback) {
4827
+ return this.#move((date) => date.setUTCDate(date.getUTCDate() + this.value), callback);
4828
+ }
4829
+ week(callback) {
4830
+ return this.weeks(callback);
4831
+ }
4832
+ weeks(callback) {
4833
+ return this.#move((date) => date.setUTCDate(date.getUTCDate() + this.value * 7), callback);
4834
+ }
4835
+ month(callback) {
4836
+ return this.months(callback);
4837
+ }
4838
+ months(callback) {
4839
+ return this.#move((date) => date.setUTCMonth(date.getUTCMonth() + this.value), callback);
4840
+ }
4841
+ year(callback) {
4842
+ return this.years(callback);
4843
+ }
4844
+ years(callback) {
4845
+ return this.#move((date) => date.setUTCFullYear(date.getUTCFullYear() + this.value), callback);
4846
+ }
4847
+ /** Laravel's Wormhole::back(). */
4848
+ static back() {
4849
+ return travelBack();
4850
+ }
4851
+ #move(step, callback) {
4852
+ const date = new (realDate())(testNow ?? Date.now());
4853
+ step(date);
4854
+ setTestNow(date);
4855
+ if (callback) return thenTravelBack(callback);
4856
+ }
4857
+ };
3813
4858
  function fail2(message, details = {}) {
3814
4859
  const error = new AssertionError2({ message, actual: details.actual, expected: details.expected, operator: "fail" });
3815
4860
  delete error.diff;
@@ -3831,7 +4876,7 @@ function format(value) {
3831
4876
  function escapeHtml2(value) {
3832
4877
  return value.replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#039;" })[char]);
3833
4878
  }
3834
- function stripTags(html) {
4879
+ function stripTags2(html) {
3835
4880
  return html.replace(/<[^>]*>/g, "");
3836
4881
  }
3837
4882
  function equals(a, b) {
@@ -3840,7 +4885,7 @@ function equals(a, b) {
3840
4885
  function canonical(value) {
3841
4886
  if (Array.isArray(value)) return value.map(canonical);
3842
4887
  if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype) {
3843
- 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])]));
3844
4889
  }
3845
4890
  return value;
3846
4891
  }
@@ -3848,20 +4893,20 @@ function isSubset(subset, actual) {
3848
4893
  if (typeof subset !== "object" || subset === null) return equals(subset, actual);
3849
4894
  if (typeof actual !== "object" || actual === null) return false;
3850
4895
  if (Array.isArray(subset) !== Array.isArray(actual)) return false;
3851
- 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]));
3852
4897
  }
3853
4898
  function* objectsIn(value) {
3854
4899
  if (typeof value !== "object" || value === null) return;
3855
4900
  if (!Array.isArray(value)) yield value;
3856
4901
  for (const child of Object.values(value)) yield* objectsIn(child);
3857
4902
  }
3858
- function containsPair(tree, key, expected) {
3859
- 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;
3860
4905
  return false;
3861
4906
  }
3862
4907
  function assertStructure(structure, data, path = "") {
3863
- const at = (key) => path === "" ? key : `${path}.${key}`;
3864
- 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);
3865
4910
  if (Array.isArray(structure)) {
3866
4911
  for (const item of structure) {
3867
4912
  if (typeof item === "string") assert(hasKey(item), `Failed asserting that the JSON has the key [${at(item)}].`, { actual: data });
@@ -3869,14 +4914,14 @@ function assertStructure(structure, data, path = "") {
3869
4914
  }
3870
4915
  return;
3871
4916
  }
3872
- for (const [key, nested] of Object.entries(structure)) {
3873
- if (key === "*") {
4917
+ for (const [key2, nested] of Object.entries(structure)) {
4918
+ if (key2 === "*") {
3874
4919
  assert(Array.isArray(data) || typeof data === "object" && data !== null, `Failed asserting that [${path || "the JSON"}] is a list.`, { actual: data });
3875
4920
  for (const [index, element] of Object.entries(data)) assertStructure(nested, element, at(index));
3876
4921
  continue;
3877
4922
  }
3878
- assert(hasKey(key), `Failed asserting that the JSON has the key [${at(key)}].`, { actual: data });
3879
- 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));
3880
4925
  }
3881
4926
  }
3882
4927
  var PendingCommand = class {
@@ -4079,14 +5124,14 @@ var TESTING_ENVIRONMENT = Object.freeze({
4079
5124
  SESSION_DRIVER: "array"
4080
5125
  });
4081
5126
  function applyEnvironment(values, env2 = process.env) {
4082
- for (const [key, value] of Object.entries(values)) env2[key] ??= value;
5127
+ for (const [key2, value] of Object.entries(values)) env2[key2] ??= value;
4083
5128
  }
4084
5129
  function clonePlain2(value) {
4085
5130
  if (Array.isArray(value)) return value.map((item) => clonePlain2(item));
4086
5131
  if (typeof value === "object" && value !== null) {
4087
5132
  const proto = Object.getPrototypeOf(value);
4088
5133
  if (proto !== Object.prototype && proto !== null) return value;
4089
- 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)]));
4090
5135
  }
4091
5136
  return value;
4092
5137
  }
@@ -4121,31 +5166,31 @@ var AssertableJson = class _AssertableJson {
4121
5166
  newScope(props, path) {
4122
5167
  return new _AssertableJson(props, path);
4123
5168
  }
4124
- dotPath(key = "") {
4125
- if (this.path === void 0) return key;
4126
- 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}`;
4127
5172
  }
4128
5173
  /** The value at `key` in this scope, or the whole scope. */
4129
- prop(key) {
4130
- if (key !== void 0 && this.hasOwnKey(key)) return this.props[key];
4131
- 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);
4132
5177
  }
4133
5178
  /** A literal key of this scope wins over a dot path, like Laravel's Arr::get (keys such as 'items.0.name'). */
4134
- hasOwnKey(key) {
4135
- 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);
4136
5181
  }
4137
- exists(key) {
4138
- return this.hasOwnKey(key) || Arr.has(this.props, key);
5182
+ exists(key2) {
5183
+ return this.hasOwnKey(key2) || Arr.has(this.props, key2);
4139
5184
  }
4140
- interactsWith(key) {
4141
- 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];
4142
5187
  if (!this.interactedKeys.includes(prop)) this.interactedKeys.push(prop);
4143
5188
  }
4144
5189
  /** Every property of the scope was checked (called when a scope ends). */
4145
5190
  interacted() {
4146
5191
  const props = this.prop();
4147
5192
  const keys = typeof props === "object" && props !== null ? Object.keys(props) : [];
4148
- const unexpected = keys.filter((key) => !this.interactedKeys.includes(key));
5193
+ const unexpected = keys.filter((key2) => !this.interactedKeys.includes(key2));
4149
5194
  assert(
4150
5195
  unexpected.length === 0,
4151
5196
  this.path === void 0 ? "Unexpected properties were found on the root level." : `Unexpected properties were found in scope [${this.path}].`,
@@ -4160,9 +5205,9 @@ var AssertableJson = class _AssertableJson {
4160
5205
  }
4161
5206
  // Has ------------------------------------------------------------------------------------------------------
4162
5207
  /** has('key'), has('key', 3), has('key', json => …), has('key', 3, first => …), or has(3) for the root size. */
4163
- has(key, length, callback) {
4164
- if (typeof key === "number" && length === void 0) return this.count(key);
4165
- 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);
4166
5211
  assert(this.exists(name), `Property [${this.dotPath(name)}] does not exist.`);
4167
5212
  this.interactsWith(name);
4168
5213
  if (callback !== void 0) {
@@ -4178,105 +5223,105 @@ var AssertableJson = class _AssertableJson {
4178
5223
  /** hasAll(['a', 'b']) or hasAll({ items: 3 }). */
4179
5224
  hasAll(keys) {
4180
5225
  if (typeof keys === "string" || Array.isArray(keys)) {
4181
- for (const key of [keys].flat()) this.has(key);
5226
+ for (const key2 of [keys].flat()) this.has(key2);
4182
5227
  return this;
4183
5228
  }
4184
- 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);
4185
5230
  return this;
4186
5231
  }
4187
5232
  hasAny(keys) {
4188
5233
  const list = [keys].flat();
4189
- const present = list.filter((key) => this.exists(key));
4190
- assert(present.length > 0, `None of properties [${list.map((key) => this.dotPath(key)).join(", ")}] exist.`);
4191
- 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);
4192
5237
  return this;
4193
5238
  }
4194
- missing(key) {
4195
- 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.`);
4196
5241
  return this;
4197
5242
  }
4198
5243
  missingAll(keys) {
4199
- for (const key of [keys].flat()) this.missing(key);
5244
+ for (const key2 of [keys].flat()) this.missing(key2);
4200
5245
  return this;
4201
5246
  }
4202
5247
  /** count(3) checks this scope's size; count('items', 3) a property's. */
4203
- count(key, length) {
4204
- if (typeof key === "number") {
5248
+ count(key2, length) {
5249
+ if (typeof key2 === "number") {
4205
5250
  const size2 = sizeOf(this.prop());
4206
- 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 });
4207
5252
  return this;
4208
5253
  }
4209
- this.has(key);
4210
- const size = sizeOf(this.prop(key));
4211
- 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 });
4212
5257
  return this;
4213
5258
  }
4214
- countBetween(key, min, max) {
4215
- this.has(key);
4216
- const size = sizeOf(this.prop(key));
4217
- 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 });
4218
5263
  return this;
4219
5264
  }
4220
5265
  // Matching -------------------------------------------------------------------------------------------------
4221
5266
  /** where('name', 'Ann'), or where('name', value => value.startsWith('A')). Objects compare ignoring key order. */
4222
- where(key, expected) {
4223
- this.has(key);
4224
- const actual = this.prop(key);
5267
+ where(key2, expected) {
5268
+ this.has(key2);
5269
+ const actual = this.prop(key2);
4225
5270
  if (typeof expected === "function") {
4226
- 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.`);
4227
5272
  return this;
4228
5273
  }
4229
5274
  const normalized = toPlain(expected);
4230
- 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 });
4231
5276
  return this;
4232
5277
  }
4233
- whereNot(key, expected) {
4234
- this.has(key);
4235
- const actual = this.prop(key);
5278
+ whereNot(key2, expected) {
5279
+ this.has(key2);
5280
+ const actual = this.prop(key2);
4236
5281
  if (typeof expected === "function") {
4237
- 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.`);
4238
5283
  return this;
4239
5284
  }
4240
- 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)}]`);
4241
5286
  return this;
4242
5287
  }
4243
- whereNull(key) {
4244
- this.has(key);
4245
- const actual = this.prop(key);
4246
- 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 });
4247
5292
  return this;
4248
5293
  }
4249
- whereNotNull(key) {
4250
- this.has(key);
4251
- 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.`);
4252
5297
  return this;
4253
5298
  }
4254
5299
  whereAll(bindings) {
4255
- 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);
4256
5301
  return this;
4257
5302
  }
4258
5303
  /** whereType('id', 'integer'), whereType('name', 'string|null'), whereType('tags', ['array', 'null']). */
4259
- whereType(key, expected) {
4260
- this.has(key);
5304
+ whereType(key2, expected) {
5305
+ this.has(key2);
4261
5306
  const types = typeof expected === "string" ? expected.split("|") : expected;
4262
- const actual = typeOf(this.prop(key));
5307
+ const actual = typeOf(this.prop(key2));
4263
5308
  assert(
4264
5309
  types.some((type) => actual.includes(type)),
4265
- `Property [${this.dotPath(key)}] is not of expected type [${types.join("|")}].`,
5310
+ `Property [${this.dotPath(key2)}] is not of expected type [${types.join("|")}].`,
4266
5311
  { actual: actual[0], expected: types.join("|") }
4267
5312
  );
4268
5313
  return this;
4269
5314
  }
4270
5315
  whereAllType(bindings) {
4271
- 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);
4272
5317
  return this;
4273
5318
  }
4274
5319
  /** The list contains every expected value (or one passing the callback); a string contains the substring. */
4275
- whereContains(key, expected) {
4276
- this.has(key);
4277
- const actual = this.prop(key);
5320
+ whereContains(key2, expected) {
5321
+ this.has(key2);
5322
+ const actual = this.prop(key2);
4278
5323
  if (typeof actual === "string") {
4279
- 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)}].`);
4280
5325
  return this;
4281
5326
  }
4282
5327
  const values = typeof actual === "object" && actual !== null ? Object.values(actual) : [actual];
@@ -4284,13 +5329,13 @@ var AssertableJson = class _AssertableJson {
4284
5329
  const missing = wanted.filter(
4285
5330
  (item) => typeof item === "function" ? !values.some((value) => item(value)) : !values.some((value) => equals(toPlain(item), value))
4286
5331
  );
4287
- 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 });
4288
5333
  return this;
4289
5334
  }
4290
5335
  // Scoping --------------------------------------------------------------------------------------------------
4291
- scope(key, callback) {
4292
- const props = this.prop(key);
4293
- const path = this.dotPath(key);
5336
+ scope(key2, callback) {
5337
+ const props = this.prop(key2);
5338
+ const path = this.dotPath(key2);
4294
5339
  assert(typeof props === "object" && props !== null, `Property [${path}] is not scopeable.`);
4295
5340
  const scope = this.newScope(props, path);
4296
5341
  callback(scope);
@@ -4312,9 +5357,9 @@ var AssertableJson = class _AssertableJson {
4312
5357
  if (keys.length === 0) {
4313
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.`);
4314
5359
  }
4315
- for (const key of keys) {
4316
- this.interactsWith(key);
4317
- this.scope(key, callback);
5360
+ for (const key2 of keys) {
5361
+ this.interactsWith(key2);
5362
+ this.scope(key2, callback);
4318
5363
  }
4319
5364
  return this;
4320
5365
  }
@@ -4324,8 +5369,8 @@ var AssertableJson = class _AssertableJson {
4324
5369
  else otherwise?.(this);
4325
5370
  return this;
4326
5371
  }
4327
- dump(key) {
4328
- console.log(format(this.prop(key)));
5372
+ dump(key2) {
5373
+ console.log(format(this.prop(key2)));
4329
5374
  return this;
4330
5375
  }
4331
5376
  toArray() {
@@ -4352,7 +5397,7 @@ function inertiaPageOf(response2) {
4352
5397
  } catch {
4353
5398
  page = void 0;
4354
5399
  }
4355
- 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));
4356
5401
  if (!valid) fail2("Not a valid Inertia response.");
4357
5402
  return page;
4358
5403
  }
@@ -4383,7 +5428,7 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
4383
5428
  if (shouldExist === true || shouldExist === void 0 && config?.get("inertia.testing.ensure_pages_exist", true) !== false) {
4384
5429
  const paths = config?.get("inertia.pages.paths", []) ?? [];
4385
5430
  const extensions = config?.get("inertia.pages.extensions", []) ?? [];
4386
- 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}`))));
4387
5432
  if (!exists) fail2(`Inertia page component file [${value}] does not exist.`);
4388
5433
  }
4389
5434
  return this;
@@ -4397,23 +5442,23 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
4397
5442
  return this;
4398
5443
  }
4399
5444
  /** The flash data has `key` (dot paths allowed), equal to `expected` when given. */
4400
- hasFlash(key, ...expected) {
4401
- _AssertableInertia.assertFlashHas(this.page.flash ?? {}, key, ...expected);
5445
+ hasFlash(key2, ...expected) {
5446
+ _AssertableInertia.assertFlashHas(this.page.flash ?? {}, key2, ...expected);
4402
5447
  return this;
4403
5448
  }
4404
- missingFlash(key) {
4405
- _AssertableInertia.assertFlashMissing(this.page.flash ?? {}, key);
5449
+ missingFlash(key2) {
5450
+ _AssertableInertia.assertFlashMissing(this.page.flash ?? {}, key2);
4406
5451
  return this;
4407
5452
  }
4408
- static assertFlashHas(flash, key, ...expected) {
4409
- 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}].`);
4410
5455
  if (expected.length > 0) {
4411
- const actual = Arr.get(flash, key);
4412
- 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] });
4413
5458
  }
4414
5459
  }
4415
- static assertFlashMissing(flash, key) {
4416
- 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}].`);
4417
5462
  }
4418
5463
  /**
4419
5464
  * Requests the page again as a partial reload (only/except) and checks it is the same page. Uses the test's
@@ -4479,8 +5524,8 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
4479
5524
  return this.#response?.context.app;
4480
5525
  }
4481
5526
  };
4482
- function cookiePrefix(name, key) {
4483
- return `${createHmac2("sha1", key).update(`${name}v2`).digest("hex")}|`;
5527
+ function cookiePrefix(name, key2) {
5528
+ return `${createHmac2("sha1", key2).update(`${name}v2`).digest("hex")}|`;
4484
5529
  }
4485
5530
  function encryptCookie(encrypter, name, value) {
4486
5531
  return encrypter.encrypt(cookiePrefix(name, encrypter.getKey()) + value, false);
@@ -4512,15 +5557,15 @@ function parseSetCookie(header) {
4512
5557
  };
4513
5558
  for (const attribute of attributes) {
4514
5559
  const at = attribute.indexOf("=");
4515
- const key = (at === -1 ? attribute : attribute.slice(0, at)).trim().toLowerCase();
5560
+ const key2 = (at === -1 ? attribute : attribute.slice(0, at)).trim().toLowerCase();
4516
5561
  const value = at === -1 ? "" : attribute.slice(at + 1).trim();
4517
- if (key === "path") cookie.path = value;
4518
- else if (key === "domain") cookie.domain = value;
4519
- else if (key === "max-age") cookie.maxAge = Number(value);
4520
- else if (key === "expires") cookie.expires = new Date(value);
4521
- else if (key === "httponly") cookie.httpOnly = true;
4522
- else if (key === "secure") cookie.secure = true;
4523
- 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;
4524
5569
  }
4525
5570
  return cookie;
4526
5571
  }
@@ -4732,7 +5777,7 @@ ${this.excerpt()}`);
4732
5777
  }
4733
5778
  /** Like assertSee, against the content with its tags stripped. */
4734
5779
  assertSeeText(value, escape = true) {
4735
- const text = stripTags(this.content);
5780
+ const text = stripTags2(this.content);
4736
5781
  for (const needle of this.needles(value, escape)) {
4737
5782
  assert(text.includes(needle), `Failed asserting that the response text contains [${needle}].
4738
5783
 
@@ -4741,14 +5786,14 @@ ${this.excerpt(text)}`);
4741
5786
  return this;
4742
5787
  }
4743
5788
  assertDontSeeText(value, escape = true) {
4744
- const text = stripTags(this.content);
5789
+ const text = stripTags2(this.content);
4745
5790
  for (const needle of this.needles(value, escape)) {
4746
5791
  assert(!text.includes(needle), `Failed asserting that the response text does not contain [${needle}].`);
4747
5792
  }
4748
5793
  return this;
4749
5794
  }
4750
5795
  assertSeeTextInOrder(values, escape = true) {
4751
- return this.inOrder(stripTags(this.content), this.needles(values, escape));
5796
+ return this.inOrder(stripTags2(this.content), this.needles(values, escape));
4752
5797
  }
4753
5798
  // JSON --------------------------------------------------------------------------------------------------
4754
5799
  /**
@@ -4777,16 +5822,16 @@ ${this.excerpt(text)}`);
4777
5822
  return inertiaPageOf(this);
4778
5823
  }
4779
5824
  /** The page props, or one prop by dot path. */
4780
- inertiaProps(key) {
4781
- return Arr.get(inertiaPageOf(this).props, key);
5825
+ inertiaProps(key2) {
5826
+ return Arr.get(inertiaPageOf(this).props, key2);
4782
5827
  }
4783
5828
  /** Inertia flash data waiting in the session for the next page (after a redirect). */
4784
- assertInertiaFlash(key, ...expected) {
4785
- 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);
4786
5831
  return this;
4787
5832
  }
4788
- assertInertiaFlashMissing(key) {
4789
- AssertableInertia.assertFlashMissing(this.session().get("inertia.flash_data", {}), key);
5833
+ assertInertiaFlashMissing(key2) {
5834
+ AssertableInertia.assertFlashMissing(this.session().get("inertia.flash_data", {}), key2);
4790
5835
  return this;
4791
5836
  }
4792
5837
  /** The response JSON equals `data` exactly (object key order is ignored). */
@@ -4813,15 +5858,15 @@ ${this.excerpt(text)}`);
4813
5858
  /** Each key/value pair appears somewhere in the JSON (Laravel's assertJsonFragment). */
4814
5859
  assertJsonFragment(data) {
4815
5860
  const actual = this.json();
4816
- for (const [key, value] of Object.entries(data)) {
4817
- 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 });
4818
5863
  }
4819
5864
  return this;
4820
5865
  }
4821
5866
  /** Passes when at least one pair of `data` is absent (Laravel's assertJsonMissing). */
4822
5867
  assertJsonMissing(data) {
4823
5868
  const actual = this.json();
4824
- 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));
4825
5870
  assert(!allPresent, `Found unexpected JSON fragment ${format(data)} in the response.`, { actual });
4826
5871
  return this;
4827
5872
  }
@@ -4832,10 +5877,10 @@ ${this.excerpt(text)}`);
4832
5877
  return this;
4833
5878
  }
4834
5879
  /** The JSON (or the value at `key`) has `count` elements. */
4835
- assertJsonCount(count, key) {
4836
- const value = this.json(key);
5880
+ assertJsonCount(count, key2) {
5881
+ const value = this.json(key2);
4837
5882
  const actual = Array.isArray(value) ? value.length : typeof value === "object" && value !== null ? Object.keys(value).length : -1;
4838
- 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 });
4839
5884
  return this;
4840
5885
  }
4841
5886
  /** assertRedirect() to the URL of a named route (resolved with the app's UrlGenerator). */
@@ -4859,7 +5904,7 @@ ${this.excerpt()}`);
4859
5904
  if (this.content === "") return this;
4860
5905
  const errors = Arr.get(this.json(), responseKey);
4861
5906
  if (errors === void 0 || errors === null) return this;
4862
- 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));
4863
5908
  assert(unexpected.length === 0, `Found unexpected validation error(s) for [${unexpected.join(", ")}]: ${format(errors)}`);
4864
5909
  return this;
4865
5910
  }
@@ -4884,38 +5929,38 @@ ${this.excerpt()}`);
4884
5929
  return session;
4885
5930
  }
4886
5931
  /** A key (with an optional value or predicate), several keys, or a { key: value } record. */
4887
- assertSessionHas(key, value) {
4888
- if (Array.isArray(key)) {
4889
- 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);
4890
5935
  return this;
4891
5936
  }
4892
- if (typeof key === "object") return this.assertSessionHasAll(key);
5937
+ if (typeof key2 === "object") return this.assertSessionHasAll(key2);
4893
5938
  const session = this.session();
4894
5939
  if (value === void 0) {
4895
- assert(session.has(key), `Session is missing expected key [${key}].`);
5940
+ assert(session.has(key2), `Session is missing expected key [${key2}].`);
4896
5941
  } else if (typeof value === "function") {
4897
- 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.`);
4898
5943
  } else {
4899
- 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 });
4900
5945
  }
4901
5946
  return this;
4902
5947
  }
4903
5948
  assertSessionHasAll(bindings) {
4904
- 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);
4905
5950
  return this;
4906
5951
  }
4907
- assertSessionMissing(key, value) {
5952
+ assertSessionMissing(key2, value) {
4908
5953
  const session = this.session();
4909
- for (const k of [key].flat()) {
5954
+ for (const k of [key2].flat()) {
4910
5955
  if (value === void 0) assert(!session.has(k), `Session has unexpected key [${k}].`);
4911
5956
  else assert(!equals(session.get(k), value), `Session has unexpected value for [${k}].`);
4912
5957
  }
4913
5958
  return this;
4914
5959
  }
4915
5960
  /** Old input flashed with the redirect (withInput()). */
4916
- assertSessionHasInput(key, value) {
5961
+ assertSessionHasInput(key2, value) {
4917
5962
  const session = this.session();
4918
- 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);
4919
5964
  for (const [k, expected] of entries) {
4920
5965
  const actual = session.getOldInput(k);
4921
5966
  assert(actual !== void 0, `Session is missing expected key [${k}] in the old input.`);
@@ -4935,10 +5980,10 @@ ${this.excerpt()}`);
4935
5980
  assert(Object.keys(errors).length > 0, `Session has no errors in the [${errorBag}] bag.`);
4936
5981
  return this;
4937
5982
  }
4938
- for (const [key, messages] of normalizeExpected(expected)) {
4939
- 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 });
4940
5985
  for (const message of messages) {
4941
- 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 });
4942
5987
  }
4943
5988
  }
4944
5989
  return this;
@@ -4965,7 +6010,7 @@ ${JSON.stringify(found, null, 2)}`, { actual: found, expected: {} });
4965
6010
  ${JSON.stringify(errors, null, 2)}`, { actual: errors, expected: {} });
4966
6011
  return this;
4967
6012
  }
4968
- 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] });
4969
6014
  return this;
4970
6015
  }
4971
6016
  // Cookies -----------------------------------------------------------------------------------------------
@@ -5016,27 +6061,27 @@ ${JSON.stringify(errors, null, 2)}`, { actual: errors, expected: {} });
5016
6061
  return this;
5017
6062
  }
5018
6063
  /** The view received `key` (dot paths allowed), optionally with this value or passing this predicate. */
5019
- assertViewHas(key, value) {
5020
- if (typeof key !== "string") return this.assertViewHasAll(key);
6064
+ assertViewHas(key2, value) {
6065
+ if (typeof key2 !== "string") return this.assertViewHasAll(key2);
5021
6066
  const data = this.viewDataOf(this.ensureView());
5022
- assert(Arr.has(data, key), `Failed asserting that the view has key [${key}].`, { actual: Object.keys(data) });
5023
- const actual = Arr.get(data, key);
5024
- if (typeof value === "function") assert(value(actual) === true, `View data [${key}] does not pass the given check.`);
5025
- 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 });
5026
6071
  return this;
5027
6072
  }
5028
6073
  assertViewHasAll(bindings) {
5029
- if (Array.isArray(bindings)) for (const key of bindings) this.assertViewHas(key);
5030
- 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);
5031
6076
  return this;
5032
6077
  }
5033
- assertViewMissing(key) {
5034
- 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}].`);
5035
6080
  return this;
5036
6081
  }
5037
6082
  /** The data the view received (dot path), or all of it. */
5038
- viewData(key) {
5039
- return Arr.get(this.viewDataOf(this.ensureView()), key);
6083
+ viewData(key2) {
6084
+ return Arr.get(this.viewDataOf(this.ensureView()), key2);
5040
6085
  }
5041
6086
  // Internals ---------------------------------------------------------------------------------------------
5042
6087
  absolute(url2) {
@@ -5071,15 +6116,15 @@ ${this.excerpt()}`;
5071
6116
  }
5072
6117
  formatted(errors, template) {
5073
6118
  if (template === void 0) return errors;
5074
- 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))]));
5075
6120
  }
5076
6121
  /** Laravel's assertInvalid/assertJsonValidationErrors: each key present; each expected fragment contained in a message. */
5077
6122
  assertErrorsContain(errors, expected, where) {
5078
- for (const [key, fragments] of normalizeExpected(expected)) {
5079
- assert(Object.hasOwn(errors, key), `Failed to find a validation error in the ${where} for key: [${key}]`, { actual: errors });
5080
- 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();
5081
6126
  for (const fragment of fragments) {
5082
- 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 });
5083
6128
  }
5084
6129
  }
5085
6130
  }
@@ -5112,8 +6157,8 @@ ${this.excerpt()}`;
5112
6157
  };
5113
6158
  function normalizeExpected(expected) {
5114
6159
  if (typeof expected === "string") return [[expected, []]];
5115
- if (Array.isArray(expected)) return expected.map((key) => [key, []]);
5116
- 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()]);
5117
6162
  }
5118
6163
  function renderedHtml(view) {
5119
6164
  try {
@@ -5178,6 +6223,11 @@ var HttpClient = class {
5178
6223
  defaultGuard;
5179
6224
  /** Session attributes carried from one request to the next (Laravel keeps one session store per test). */
5180
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 = [];
5181
6231
  /** from(url): the session's previous URL for the next request only (Laravel sets it once). */
5182
6232
  previousUrl;
5183
6233
  /** Requests whose actingAs users reached the guards (the AuthFactory they went through). */
@@ -5213,6 +6263,7 @@ var HttpClient = class {
5213
6263
  const context = { request: request2, app: this.app, client: this };
5214
6264
  const result = await scope.run(async () => {
5215
6265
  this.beforeRequest(scope, context);
6266
+ if (context.session !== void 0) this.applyPending(context.session);
5216
6267
  scope.instance(HttpRequest, new IncomingRequest(request2));
5217
6268
  if (scope.bound(AuthFactory)) {
5218
6269
  try {
@@ -5253,6 +6304,7 @@ var HttpClient = class {
5253
6304
  const session = this.scopeSession(scope);
5254
6305
  if (session !== void 0) {
5255
6306
  if (this.session !== void 0) session.put(clonePlain2(this.session));
6307
+ if (this.sessionChanges.length > 0) this.applyOnStart(session);
5256
6308
  if (this.previousUrl !== void 0) session.setPreviousUrl(this.previousUrl);
5257
6309
  context.session = session;
5258
6310
  }
@@ -5285,6 +6337,20 @@ var HttpClient = class {
5285
6337
  else this.users.set(guard, user);
5286
6338
  }
5287
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
+ }
5288
6354
  /** The request's (scoped) Session, created now so the carried attributes are in place before StartSession. */
5289
6355
  scopeSession(scope) {
5290
6356
  if (!scope.bound(Session)) return void 0;
@@ -5301,14 +6367,14 @@ var HttpClient = class {
5301
6367
  let body;
5302
6368
  const data = options.data ?? {};
5303
6369
  if (verb === "GET" || verb === "HEAD") {
5304
- 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);
5305
6371
  } else if (options.json) {
5306
6372
  body = JSON.stringify(data);
5307
6373
  } else if (Object.keys(data).length > 0) {
5308
6374
  const fields = flatten(data);
5309
6375
  if (fields.some(([, value]) => typeof value !== "string")) {
5310
6376
  const form = new FormData();
5311
- for (const [key, value] of fields) form.append(key, value);
6377
+ for (const [key2, value] of fields) form.append(key2, value);
5312
6378
  body = form;
5313
6379
  } else {
5314
6380
  body = new URLSearchParams(fields);
@@ -5351,8 +6417,8 @@ function setClientAddress2(request2, address) {
5351
6417
  }
5352
6418
  function flatten(data, prefix = "") {
5353
6419
  const out = [];
5354
- for (const [key, value] of Object.entries(data)) {
5355
- const name = prefix === "" ? key : `${prefix}[${key}]`;
6420
+ for (const [key2, value] of Object.entries(data)) {
6421
+ const name = prefix === "" ? key2 : `${prefix}[${key2}]`;
5356
6422
  if (value === void 0) continue;
5357
6423
  if (value === null) out.push([name, ""]);
5358
6424
  else if (value instanceof Blob) out.push([name, value]);
@@ -5550,14 +6616,17 @@ var MakesHttpRequests = class extends InteractsWithApplication {
5550
6616
  this.http.previousUrl = url2;
5551
6617
  return this.withHeader("Referer", url2);
5552
6618
  }
5553
- /** 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()). */
5554
6620
  withSession(data) {
5555
- 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)));
5556
6624
  return this;
5557
6625
  }
5558
- /** Empties the session carried between requests. */
6626
+ /** Empties the session, the stored one included, so the next request starts with none (Laravel's flushSession()). */
5559
6627
  flushSession() {
5560
6628
  this.http.session = {};
6629
+ this.http.sessionChanges.splice(0, Infinity, (session) => session.flush());
5561
6630
  return this;
5562
6631
  }
5563
6632
  /** Every following request is authenticated as `user` on `guard` (which also becomes the default guard). */
@@ -5696,15 +6765,39 @@ var InteractsWithConsole = class extends InteractsWithDatabase {
5696
6765
  return new PendingCommand(this.app, command, parameters);
5697
6766
  }
5698
6767
  };
5699
- var TestCase = class extends InteractsWithConsole {
6768
+ var InteractsWithTime = class extends InteractsWithConsole {
6769
+ travelTo(date, callback) {
6770
+ return callback ? travelTo(date, callback) : travelTo(date);
6771
+ }
6772
+ travel(value) {
6773
+ return travel(value);
6774
+ }
6775
+ travelBack() {
6776
+ return travelBack();
6777
+ }
6778
+ freezeTime(callback) {
6779
+ return callback ? freezeTime(callback) : freezeTime();
6780
+ }
6781
+ freezeSecond(callback) {
6782
+ return callback ? freezeSecond(callback) : freezeSecond();
6783
+ }
6784
+ async tearDown() {
6785
+ try {
6786
+ await super.tearDown();
6787
+ } finally {
6788
+ travelBack();
6789
+ }
6790
+ }
6791
+ };
6792
+ var TestCase = class extends InteractsWithTime {
5700
6793
  };
5701
- function install(token2, fake) {
6794
+ function install2(token2, fake) {
5702
6795
  facade(token2).swap(fake);
5703
6796
  return fake;
5704
6797
  }
5705
6798
  function bound(token2) {
5706
- const container = currentContainer();
5707
- 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;
5708
6801
  }
5709
6802
  function nameOf(target2) {
5710
6803
  if (typeof target2 === "string") return target2;
@@ -5731,6 +6824,23 @@ function isQueueable(job) {
5731
6824
  if (QUEUE_OPTIONS.some((option) => members[option] !== void 0)) return true;
5732
6825
  return typeof members.uniqueId === "function" || typeof members.retryUntil === "function";
5733
6826
  }
6827
+ var isClass = (value) => typeof value === "function" && typeof value.prototype?.handle === "function";
6828
+ var asPair = (listener) => {
6829
+ if (Array.isArray(listener) && listener.length === 2 && typeof listener[1] === "string") return [listener[0], listener[1]];
6830
+ return isClass(listener) ? [listener, "handle"] : void 0;
6831
+ };
6832
+ function listenerMatches(registered, expected) {
6833
+ if (expected === Function) return typeof registered === "function" && !isClass(registered);
6834
+ const [left, right] = [asPair(registered), asPair(expected)];
6835
+ if (left && right) return left[0] === right[0] && left[1] === right[1];
6836
+ return registered === expected;
6837
+ }
6838
+ function listenerName(listener) {
6839
+ const pair = asPair(listener);
6840
+ if (pair === void 0) return "Closure";
6841
+ const [owner, method] = pair;
6842
+ return Array.isArray(listener) ? `${owner.name}@${method}` : owner.name;
6843
+ }
5734
6844
  var EventFake = class _EventFake {
5735
6845
  constructor(dispatcher, eventsToFake = [], eventsToDispatch = []) {
5736
6846
  this.dispatcher = dispatcher;
@@ -5743,11 +6853,11 @@ var EventFake = class _EventFake {
5743
6853
  events = [];
5744
6854
  /** Installs the fake (on the current container) and returns it: `const events = EventFake.fake([OrderShipped])`. */
5745
6855
  static fake(eventsToFake = []) {
5746
- return install(EventDispatcher, new _EventFake(_EventFake.original(), [eventsToFake].flat()));
6856
+ return install2(EventDispatcher, new _EventFake(_EventFake.original(), [eventsToFake].flat()));
5747
6857
  }
5748
6858
  /** Fakes every event except these. */
5749
6859
  static fakeExcept(eventsToDispatch) {
5750
- return install(EventDispatcher, new _EventFake(_EventFake.original(), [], [eventsToDispatch].flat()));
6860
+ return install2(EventDispatcher, new _EventFake(_EventFake.original(), [], [eventsToDispatch].flat()));
5751
6861
  }
5752
6862
  static original() {
5753
6863
  const current = bound(EventDispatcher);
@@ -5804,9 +6914,20 @@ var EventFake = class _EventFake {
5804
6914
 
5805
6915
  - ${names.join("\n- ")}`);
5806
6916
  }
5807
- /** The original dispatcher has a listener for the event. */
5808
- assertListening(event) {
5809
- assert(this.hasListeners(event), `Event [${nameOf(event)}] does not have any listeners.`);
6917
+ /**
6918
+ * Laravel's assertListening(event, listener): the original dispatcher has that listener for the event (a class, a
6919
+ * [class, method] pair, the same closure, or Function for any closure; wildcard listeners count for a string event).
6920
+ * With only the event, any listener will do.
6921
+ */
6922
+ assertListening(event, listener) {
6923
+ if (listener === void 0) {
6924
+ assert(this.hasListeners(event), `Event [${nameOf(event)}] does not have any listeners.`);
6925
+ return;
6926
+ }
6927
+ assert(
6928
+ this.listenersFor(event).some((registered) => listenerMatches(registered, listener)),
6929
+ `Event [${nameOf(event)}] does not have the [${listenerName(listener)}] listener attached to it`
6930
+ );
5810
6931
  }
5811
6932
  /** The recorded events (objects, or names for string events) that match. */
5812
6933
  dispatched(event, callback) {
@@ -5815,6 +6936,17 @@ var EventFake = class _EventFake {
5815
6936
  hasDispatched(event) {
5816
6937
  return this.dispatched(event).length > 0;
5817
6938
  }
6939
+ /** The original dispatcher's listeners for the event, as registered, wildcard ones included for a string event. */
6940
+ listenersFor(event) {
6941
+ const raw = this.dispatcher?.getRawListeners?.();
6942
+ if (raw === void 0) return [];
6943
+ const listeners = [...raw.get(event) ?? []];
6944
+ if (typeof event !== "string") return listeners;
6945
+ for (const [key2, list] of raw) {
6946
+ if (typeof key2 === "string" && key2 !== event && key2.includes("*") && Str.is(key2, event)) listeners.push(...list);
6947
+ }
6948
+ return listeners;
6949
+ }
5818
6950
  shouldFake(event) {
5819
6951
  if (matchesAny(event, this.eventsToDispatch)) return false;
5820
6952
  return this.eventsToFake.length === 0 || matchesAny(event, this.eventsToFake);
@@ -5868,10 +7000,10 @@ var BusFake = class _BusFake {
5868
7000
  /** The rest of the chain, by the first job (Bus.chain([a, b, c]) records a with [b, c]). */
5869
7001
  chains = /* @__PURE__ */ new Map();
5870
7002
  static fake(jobsToFake = []) {
5871
- return install(BusDispatcher, new _BusFake(_BusFake.original(), [jobsToFake].flat()));
7003
+ return install2(BusDispatcher, new _BusFake(_BusFake.original(), [jobsToFake].flat()));
5872
7004
  }
5873
7005
  static fakeExcept(jobsToDispatch) {
5874
- return install(BusDispatcher, new _BusFake(_BusFake.original(), [], [jobsToDispatch].flat()));
7006
+ return install2(BusDispatcher, new _BusFake(_BusFake.original(), [], [jobsToDispatch].flat()));
5875
7007
  }
5876
7008
  static original() {
5877
7009
  const current = bound(BusDispatcher);
@@ -6076,10 +7208,10 @@ var QueueFake = class _QueueFake {
6076
7208
  jobsToPush;
6077
7209
  jobs = [];
6078
7210
  static fake(jobsToFake = []) {
6079
- return install(QueueManager, new _QueueFake(_QueueFake.original(), [jobsToFake].flat()));
7211
+ return install2(QueueManager, new _QueueFake(_QueueFake.original(), [jobsToFake].flat()));
6080
7212
  }
6081
7213
  static fakeExcept(jobsToPush) {
6082
- return install(QueueManager, new _QueueFake(_QueueFake.original(), [], [jobsToPush].flat()));
7214
+ return install2(QueueManager, new _QueueFake(_QueueFake.original(), [], [jobsToPush].flat()));
6083
7215
  }
6084
7216
  static original() {
6085
7217
  const current = bound(QueueManager);
@@ -6258,13 +7390,14 @@ var MailFake = class _MailFake {
6258
7390
  records = [];
6259
7391
  static fake() {
6260
7392
  const current = bound(MailManager);
6261
- return install(MailManager, new _MailFake(current instanceof _MailFake ? current.manager : current));
7393
+ return install2(MailManager, new _MailFake(current instanceof _MailFake ? current.manager : current));
6262
7394
  }
6263
7395
  // MailManager ----------------------------------------------------------------------------------------------
6264
7396
  mailer(name) {
6265
7397
  return {
6266
7398
  to: (recipients) => this.pending(name, recipients),
6267
7399
  send: (mailable) => this.sendVia(name, mailable, empty()),
7400
+ sendNow: (mailable) => this.sendNowVia(name, mailable, empty()),
6268
7401
  queue: (mailable) => this.queueVia(name, mailable, empty(), void 0)
6269
7402
  };
6270
7403
  }
@@ -6274,6 +7407,10 @@ var MailFake = class _MailFake {
6274
7407
  send(mailable) {
6275
7408
  return this.sendVia(void 0, mailable, empty());
6276
7409
  }
7410
+ /** Recorded as sent, never queued, like the real sendNow(). */
7411
+ sendNow(mailable) {
7412
+ return this.sendNowVia(void 0, mailable, empty());
7413
+ }
6277
7414
  queue(mailable) {
6278
7415
  return this.queueVia(void 0, mailable, empty(), void 0);
6279
7416
  }
@@ -6357,6 +7494,7 @@ var MailFake = class _MailFake {
6357
7494
  return pending;
6358
7495
  },
6359
7496
  send: (mailable) => this.sendVia(mailer, mailable, overrides),
7497
+ sendNow: (mailable) => this.sendNowVia(mailer, mailable, overrides),
6360
7498
  queue: (mailable) => this.queueVia(mailer, mailable, overrides, void 0),
6361
7499
  later: (delay, mailable) => this.queueVia(mailer, mailable, overrides, delay)
6362
7500
  };
@@ -6366,6 +7504,10 @@ var MailFake = class _MailFake {
6366
7504
  await this.record(mailer, mailable, overrides, isQueueable(mailable), void 0);
6367
7505
  return void 0;
6368
7506
  }
7507
+ async sendNowVia(mailer, mailable, overrides) {
7508
+ await this.record(mailer, mailable, overrides, false, void 0);
7509
+ return void 0;
7510
+ }
6369
7511
  async queueVia(mailer, mailable, overrides, delay) {
6370
7512
  await this.record(mailer, mailable, overrides, true, delay);
6371
7513
  }
@@ -6425,8 +7567,8 @@ function sameNotifiable(a, b) {
6425
7567
  const model = n;
6426
7568
  return typeof model.getKey === "function" ? model.getKey() : void 0;
6427
7569
  };
6428
- const key = keyOf(a);
6429
- 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);
6430
7572
  }
6431
7573
  var NotificationFake = class _NotificationFake {
6432
7574
  constructor(dispatcher = void 0) {
@@ -6436,7 +7578,7 @@ var NotificationFake = class _NotificationFake {
6436
7578
  notifications = [];
6437
7579
  static fake() {
6438
7580
  const current = bound(NotificationDispatcher);
6439
- return install(NotificationDispatcher, new _NotificationFake(current instanceof _NotificationFake ? current.dispatcher : current));
7581
+ return install2(NotificationDispatcher, new _NotificationFake(current instanceof _NotificationFake ? current.dispatcher : current));
6440
7582
  }
6441
7583
  // NotificationDispatcher -------------------------------------------------------------------------------------
6442
7584
  async send(notifiables, notification) {
@@ -6611,8 +7753,8 @@ var DISK_TYPES = {
6611
7753
  wasm: "application/wasm"
6612
7754
  };
6613
7755
  function diskType(extension) {
6614
- const key = extension.replace(/^\./, "").toLowerCase();
6615
- 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;
6616
7758
  }
6617
7759
  function hashName(file) {
6618
7760
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -6644,8 +7786,8 @@ var FakeDisk = class {
6644
7786
  /** Directories made explicitly (others exist because files are in them). */
6645
7787
  made = /* @__PURE__ */ new Set();
6646
7788
  async exists(path) {
6647
- const key = normalize(path);
6648
- return this.stored.has(key) || this.isDirectory(key);
7789
+ const key2 = normalize(path);
7790
+ return this.stored.has(key2) || this.isDirectory(key2);
6649
7791
  }
6650
7792
  async missing(path) {
6651
7793
  return !await this.exists(path);
@@ -6666,9 +7808,9 @@ var FakeDisk = class {
6666
7808
  });
6667
7809
  }
6668
7810
  async put(path, contents, options = {}) {
6669
- const key = normalize(path);
7811
+ const key2 = normalize(path);
6670
7812
  const bytes = typeof contents === "string" ? new TextEncoder().encode(contents) : contents instanceof Uint8Array ? contents.slice() : await collect(contents);
6671
- this.write(key, bytes, options.mimeType ?? diskType(posix.extname(key)), options);
7813
+ this.write(key2, bytes, options.mimeType ?? diskType(posix.extname(key2)), options);
6672
7814
  return true;
6673
7815
  }
6674
7816
  putFile(directory, file, options = {}) {
@@ -6683,8 +7825,8 @@ var FakeDisk = class {
6683
7825
  this.write(path, new Uint8Array(await file.arrayBuffer()), options.mimeType ?? diskType(file.extension()), options);
6684
7826
  return path;
6685
7827
  }
6686
- write(key, bytes, mimeType, options) {
6687
- 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() });
6688
7830
  }
6689
7831
  async delete(paths) {
6690
7832
  for (const path of [paths].flat()) this.stored.delete(normalize(path));
@@ -6773,8 +7915,8 @@ var FakeDisk = class {
6773
7915
  if (file === void 0) throw new FakeDiskError("FileNotFoundError", `File not found at path: ${path}`, path);
6774
7916
  return file;
6775
7917
  }
6776
- isDirectory(key) {
6777
- 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}/`));
6778
7920
  }
6779
7921
  };
6780
7922
  async function collect(stream) {
@@ -6799,7 +7941,7 @@ var StorageFake = class _StorageFake {
6799
7941
  static fake(disk) {
6800
7942
  const current = bound(FilesystemManager);
6801
7943
  const config = bound(ConfigRepository);
6802
- const fake = current instanceof _StorageFake ? current : install(FilesystemManager, new _StorageFake(current, config?.get("filesystems.default", "local") ?? "local"));
7944
+ const fake = current instanceof _StorageFake ? current : install2(FilesystemManager, new _StorageFake(current, config?.get("filesystems.default", "local") ?? "local"));
6803
7945
  const name = disk ?? fake.defaultDisk;
6804
7946
  const faked = new FakeDisk(name);
6805
7947
  fake.disks.set(name, faked);
@@ -6975,8 +8117,8 @@ function lzwZeros(pixels, minCodeSize) {
6975
8117
  let current = 0;
6976
8118
  out.write(clear, codeSize);
6977
8119
  for (let i = 1; i < pixels; i++) {
6978
- const key = current << 8;
6979
- const known = table.get(key);
8120
+ const key2 = current << 8;
8121
+ const known = table.get(key2);
6980
8122
  if (known !== void 0) {
6981
8123
  current = known;
6982
8124
  continue;
@@ -6989,7 +8131,7 @@ function lzwZeros(pixels, minCodeSize) {
6989
8131
  next = end + 1;
6990
8132
  } else {
6991
8133
  if (next >= 1 << codeSize) codeSize++;
6992
- table.set(key, next++);
8134
+ table.set(key2, next++);
6993
8135
  }
6994
8136
  current = 0;
6995
8137
  }
@@ -7110,6 +8252,7 @@ var FakeFile = {
7110
8252
  };
7111
8253
 
7112
8254
  export {
8255
+ Config,
7113
8256
  Repository,
7114
8257
  env,
7115
8258
  ApplicationBuilder,
@@ -7187,18 +8330,27 @@ export {
7187
8330
  addApiRouting,
7188
8331
  registerHttpCommands,
7189
8332
  HttpServiceProvider,
7190
- On,
7191
- Locked,
7192
- Computed,
7193
- ErrorBag,
7194
- PendingEvent,
7195
- Component,
7196
8333
  LiveError,
7197
8334
  CorruptComponentPayloadError,
7198
8335
  MethodNotCallableError,
7199
8336
  PropertyNotUpdatableError,
7200
8337
  LiveComponentNotFoundError,
7201
8338
  InvalidLiveRequestError,
8339
+ InvalidUploadSignatureError,
8340
+ TemporaryFileNotFoundError,
8341
+ FileNotPreviewableError,
8342
+ UploadValidationError,
8343
+ On,
8344
+ Locked,
8345
+ Computed,
8346
+ TEMPORARY_UPLOAD_DEFAULTS,
8347
+ TemporaryUploadedFile,
8348
+ ErrorBag,
8349
+ PendingEvent,
8350
+ Component,
8351
+ UPLOAD_URI,
8352
+ PREVIEW_URI,
8353
+ WithFileUploads,
7202
8354
  TestRequestScope,
7203
8355
  Testable,
7204
8356
  live,
@@ -7211,6 +8363,12 @@ export {
7211
8363
  UPDATE_URI,
7212
8364
  LiveServiceProvider,
7213
8365
  Pagination,
8366
+ travelTo,
8367
+ travelBack,
8368
+ freezeTime,
8369
+ freezeSecond,
8370
+ travel,
8371
+ Wormhole,
7214
8372
  PendingCommand,
7215
8373
  RefreshDatabaseState,
7216
8374
  runCommand,
@@ -7239,8 +8397,9 @@ export {
7239
8397
  MakesHttpRequests,
7240
8398
  InteractsWithDatabase,
7241
8399
  InteractsWithConsole,
8400
+ InteractsWithTime,
7242
8401
  TestCase,
7243
- install,
8402
+ install2,
7244
8403
  nameOf,
7245
8404
  matches,
7246
8405
  EventFake,
@@ -7256,4 +8415,4 @@ export {
7256
8415
  StorageFake,
7257
8416
  FakeFile
7258
8417
  };
7259
- //# sourceMappingURL=chunk-WNJP46B7.js.map
8418
+ //# sourceMappingURL=chunk-4MWG3B7G.js.map