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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,8 +8,9 @@ import {
8
8
  Str,
9
9
  currentContainer,
10
10
  facade,
11
+ fileType_exports,
11
12
  inject
12
- } from "./chunk-DCI2T7WJ.js";
13
+ } from "./chunk-POEP4B2M.js";
13
14
  import {
14
15
  Application,
15
16
  AuthFactory,
@@ -32,6 +33,7 @@ import {
32
33
  MailManager,
33
34
  MiddlewareCallbacks,
34
35
  NotificationDispatcher,
36
+ PaginationState,
35
37
  PipelineFactory,
36
38
  QueueManager,
37
39
  Redirector,
@@ -43,7 +45,7 @@ import {
43
45
  ValidationFactory,
44
46
  ViewFactory,
45
47
  token
46
- } from "./chunk-WSGWYXAO.js";
48
+ } from "./chunk-I63ZFMX6.js";
47
49
 
48
50
  // ../Config/dist/index.js
49
51
  import { existsSync, readFileSync } from "fs";
@@ -490,7 +492,7 @@ var Application2 = class _Application extends Container {
490
492
  this.scoped(Vite, () => new Vite(this));
491
493
  }
492
494
  base;
493
- version = "0.1.0-alpha.4";
495
+ version = "0.1.0-alpha.6";
494
496
  providers = [];
495
497
  bootingCallbacks = [];
496
498
  bootedCallbacks = [];
@@ -885,20 +887,60 @@ function mimeFromExtension(pathOrExtension) {
885
887
  function extensionFromMime(type) {
886
888
  return EXTENSIONS[type.split(";")[0].trim().toLowerCase()];
887
889
  }
888
- var FormUploadedFile = class {
889
- constructor(file) {
890
+ var NEVER_CLAIMED = /* @__PURE__ */ new Set([
891
+ ...fileType_exports.BINARY_AND_MARKUP_TYPES.flatMap(({ extensions }) => extensions),
892
+ "jpe",
893
+ "svgz",
894
+ "xhtml",
895
+ "xht",
896
+ "shtml",
897
+ "xsl",
898
+ "xslt",
899
+ "mht",
900
+ "mhtml",
901
+ "js",
902
+ "mjs",
903
+ "rss",
904
+ "atom",
905
+ "rdf",
906
+ "mathml",
907
+ "mml",
908
+ "xul"
909
+ ]);
910
+ var PLAIN_EXTENSION = /^[a-z0-9]+$/;
911
+ var FormUploadedFile = class _FormUploadedFile {
912
+ constructor(file, detected) {
890
913
  this.file = file;
914
+ this.detected = detected;
891
915
  this.clientName = file.name ?? "";
892
916
  this.mimeType = file.type === "" ? "application/octet-stream" : file.type;
893
917
  this.size = file.size;
894
918
  }
895
919
  file;
920
+ detected;
896
921
  clientName;
897
922
  mimeType;
898
923
  size;
899
- /** Guessed from the MIME type, falling back to the client's extension (Laravel's extension()). */
924
+ /**
925
+ * Reads the leading bytes first, so extension(), hashName() and store() name the file by its content. The request
926
+ * builds its uploads this way.
927
+ */
928
+ static async fromFile(file) {
929
+ return new _FormUploadedFile(file, fileType_exports.detectFileType(new Uint8Array(await file.slice(0, fileType_exports.SNIFF_BYTES).arrayBuffer())));
930
+ }
931
+ /**
932
+ * Laravel's extension(): the type the bytes show (guessExtension()), when they were read and match a known format.
933
+ * Text and bytes no format matches keep the client's claim (its MIME type, else its file name) when it is a plain
934
+ * extension, unless it names a format the bytes would have shown or one a browser renders or runs: otherwise 'txt'
935
+ * for text, '' for the rest. Unread (built directly), the client's claim when it is a plain extension, else ''.
936
+ */
900
937
  extension() {
901
- return extensionFromMime(this.mimeType) ?? this.clientExtension();
938
+ const claimed = extensionFromMime(this.mimeType) ?? this.clientExtension();
939
+ const detected = this.detected;
940
+ if (detected === void 0) return PLAIN_EXTENSION.test(claimed) ? claimed : "";
941
+ if (detected !== fileType_exports.TEXT && detected.extensions.length > 0) return detected.extensions[0];
942
+ if (PLAIN_EXTENSION.test(claimed) && !NEVER_CLAIMED.has(claimed)) return claimed;
943
+ return detected === fileType_exports.TEXT ? "txt" : "";
902
944
  }
903
945
  clientExtension() {
904
946
  const dot = this.clientName.lastIndexOf(".");
@@ -1078,7 +1120,7 @@ var IncomingRequest = class {
1078
1120
  this.#content = new TextDecoder().decode(bytes);
1079
1121
  this.#body = fromEntries(new URLSearchParams(this.#content).entries());
1080
1122
  } else {
1081
- this.#parseForm(await new Response(bytes, { headers: { "content-type": this.raw.headers.get("content-type") ?? "" } }).formData());
1123
+ await this.#parseForm(await new Response(bytes, { headers: { "content-type": this.raw.headers.get("content-type") ?? "" } }).formData());
1082
1124
  }
1083
1125
  } catch {
1084
1126
  }
@@ -1103,12 +1145,12 @@ var IncomingRequest = class {
1103
1145
  if (Array.isArray(parsed)) return sanitize({ ...parsed });
1104
1146
  return isPlainObject(parsed) ? sanitize(parsed) : {};
1105
1147
  }
1106
- #parseForm(form) {
1148
+ async #parseForm(form) {
1107
1149
  const fields = [];
1108
1150
  const files = [];
1109
1151
  for (const [name, value] of form.entries()) {
1110
1152
  if (typeof value === "string") fields.push([name, value]);
1111
- else if (!((value.name ?? "") === "" && value.size === 0)) files.push([name, new FormUploadedFile(value)]);
1153
+ else if (!((value.name ?? "") === "" && value.size === 0)) files.push([name, await FormUploadedFile.fromFile(value)]);
1112
1154
  }
1113
1155
  this.#body = fromEntries(fields);
1114
1156
  this.#files = fromEntries(files);
@@ -1248,9 +1290,14 @@ var IncomingRequest = class {
1248
1290
  filled(key) {
1249
1291
  return !isBlank(Arr.get(this.all(), key));
1250
1292
  }
1293
+ /** Laravel's string(): the input as a Stringable (the default when it is missing or null): request().string('q').trim(). */
1251
1294
  string(key, defaultValue = "") {
1252
1295
  const value = this.input(key);
1253
- return value === void 0 || value === null ? defaultValue : String(value);
1296
+ return Str.of(value === void 0 || value === null ? defaultValue : value);
1297
+ }
1298
+ /** Laravel's str(): string() under its short name. */
1299
+ str(key, defaultValue = "") {
1300
+ return this.string(key, defaultValue);
1254
1301
  }
1255
1302
  /** PHP's FILTER_VALIDATE_BOOLEAN: '1', 'true', 'on', 'yes' (any case) and true are true. */
1256
1303
  boolean(key, defaultValue = false) {
@@ -2458,15 +2505,58 @@ var HttpServiceProvider = class extends ServiceProvider {
2458
2505
  // ../Live/dist/index.js
2459
2506
  import { AssertionError } from "assert";
2460
2507
  import { isDeepStrictEqual } from "util";
2461
- import { AsyncLocalStorage } from "async_hooks";
2508
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
2462
2509
  import { randomBytes as randomBytes2 } from "crypto";
2463
2510
  import { createHmac, timingSafeEqual } from "crypto";
2464
2511
  import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
2465
2512
  import { join as join5, relative as relative2, sep as sep2 } from "path";
2466
2513
  import { pathToFileURL as pathToFileURL2 } from "url";
2514
+ import { AsyncLocalStorage } from "async_hooks";
2467
2515
  import { existsSync as existsSync22 } from "fs";
2468
2516
  import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
2469
2517
  import { dirname as dirname2, join as join23, relative as relative22, sep as sep22 } from "path";
2518
+ var LiveError = class extends Error {
2519
+ name = "LiveError";
2520
+ kind = "Server";
2521
+ status = 500;
2522
+ };
2523
+ var CorruptComponentPayloadError = class extends LiveError {
2524
+ name = "CorruptComponentPayloadError";
2525
+ kind = "Authorization";
2526
+ status = 403;
2527
+ constructor(component = "unknown") {
2528
+ super(`Live encountered corrupt data when trying to hydrate the [${component}] component.`);
2529
+ }
2530
+ };
2531
+ var MethodNotCallableError = class extends LiveError {
2532
+ name = "MethodNotCallableError";
2533
+ kind = "Authorization";
2534
+ status = 403;
2535
+ constructor(method, component) {
2536
+ super(`Unable to call component method. Public method [${method}] not found on component [${component}].`);
2537
+ }
2538
+ };
2539
+ var PropertyNotUpdatableError = class extends LiveError {
2540
+ name = "PropertyNotUpdatableError";
2541
+ kind = "Authorization";
2542
+ status = 403;
2543
+ constructor(path, component) {
2544
+ super(`Cannot update property [${path}] on component [${component}]: it is not a public, unlocked property.`);
2545
+ }
2546
+ };
2547
+ var LiveComponentNotFoundError = class extends LiveError {
2548
+ name = "LiveComponentNotFoundError";
2549
+ kind = "Http";
2550
+ status = 404;
2551
+ constructor(component) {
2552
+ super(`Unable to find component: [${component}].`);
2553
+ }
2554
+ };
2555
+ var InvalidLiveRequestError = class extends LiveError {
2556
+ name = "InvalidLiveRequestError";
2557
+ kind = "Http";
2558
+ status = 400;
2559
+ };
2470
2560
  Symbol.metadata ??= /* @__PURE__ */ Symbol.for("Symbol.metadata");
2471
2561
  var LISTENERS = /* @__PURE__ */ Symbol.for("oriel:live.listeners");
2472
2562
  var LOCKED = /* @__PURE__ */ Symbol.for("oriel:live.locked");
@@ -2538,6 +2628,145 @@ function lockedOf(cls) {
2538
2628
  function computedOf(cls) {
2539
2629
  return /* @__PURE__ */ new Set([...metadataOf(cls)[COMPUTED] ?? [], ...staticsOf(cls, "computed").flat()]);
2540
2630
  }
2631
+ function isModel(value) {
2632
+ if (typeof value !== "object" || value === null) return false;
2633
+ const candidate = value;
2634
+ return typeof candidate.getKey === "function" && typeof candidate.getKeyName === "function" && typeof candidate.getTable === "function" && typeof candidate.exists === "boolean";
2635
+ }
2636
+ var isPlainObject2 = (value) => {
2637
+ const proto = Object.getPrototypeOf(value);
2638
+ return proto === Object.prototype || proto === null;
2639
+ };
2640
+ 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";
2641
+ function classNameOf(registry, cls) {
2642
+ return registry.nameOf(cls) ?? registry.register(cls);
2643
+ }
2644
+ function dehydrate(value, registry, path, seen = /* @__PURE__ */ new Set()) {
2645
+ switch (typeof value) {
2646
+ case "undefined":
2647
+ return [null, { s: "u" }];
2648
+ case "string":
2649
+ case "boolean":
2650
+ return value;
2651
+ case "number":
2652
+ if (!Number.isFinite(value)) throw new LiveError(`Property [${path}] is ${value}: Live state must be JSON (finite numbers only).`);
2653
+ return value;
2654
+ case "bigint":
2655
+ case "symbol":
2656
+ case "function":
2657
+ throw new LiveError(`Property [${path}] holds a ${typeof value}: Live state may only hold primitives, arrays, plain objects, Dates, models and collections.`);
2658
+ }
2659
+ if (value === null) return null;
2660
+ const object = value;
2661
+ if (seen.has(object)) throw new LiveError(`Property [${path}] contains a circular reference.`);
2662
+ seen.add(object);
2663
+ try {
2664
+ if (object instanceof Date) return [object.toISOString(), { s: "dt" }];
2665
+ if (Array.isArray(object)) return [object.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), { s: "arr" }];
2666
+ if (isModel(object)) {
2667
+ const name = classNameOf(registry, object.constructor);
2668
+ if (!object.exists) throw new LiveError(`Property [${path}] holds an unsaved [${name}] model: only saved models can be kept between requests.`);
2669
+ return [null, { s: "mdl", class: name, key: object.getKey() }];
2670
+ }
2671
+ if (object instanceof Collection) {
2672
+ const items = object.all();
2673
+ const first = items[0];
2674
+ if (items.length > 0 && isModel(first) && items.every((item) => isModel(item) && item.constructor === first.constructor)) {
2675
+ for (const [i, item] of items.entries()) {
2676
+ if (!item.exists) throw new LiveError(`Property [${path}.${i}] holds an unsaved model.`);
2677
+ }
2678
+ const name = classNameOf(registry, first.constructor);
2679
+ return [null, { s: "elcln", class: name, keys: items.map((item) => item.getKey()) }];
2680
+ }
2681
+ return [items.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), { s: "clctn" }];
2682
+ }
2683
+ if (isPlainObject2(object)) {
2684
+ const out = {};
2685
+ for (const [key, item] of Object.entries(object)) out[key] = dehydrate(item, registry, `${path}.${key}`, seen);
2686
+ return [out, { s: "obj" }];
2687
+ }
2688
+ const type = object.constructor?.name || "object";
2689
+ throw new LiveError(
2690
+ `Property [${path}] holds a [${type}] instance: Live state may only hold primitives, arrays, plain objects, Dates, models and collections.`
2691
+ );
2692
+ } finally {
2693
+ seen.delete(object);
2694
+ }
2695
+ }
2696
+ function modelClass(registry, name) {
2697
+ if (typeof name !== "string" || !registry.has(name)) throw new LiveError(`Unable to hydrate the [${String(name)}] model: it is not registered.`);
2698
+ return registry.resolve(name);
2699
+ }
2700
+ async function hydrate(value, registry) {
2701
+ if (!isTuple(value)) return value;
2702
+ const [inner, meta] = value;
2703
+ switch (meta.s) {
2704
+ case "u":
2705
+ return void 0;
2706
+ case "dt":
2707
+ return new Date(String(inner));
2708
+ case "arr":
2709
+ return Promise.all(inner.map((item) => hydrate(item, registry)));
2710
+ case "clctn":
2711
+ return new Collection(await Promise.all(inner.map((item) => hydrate(item, registry))));
2712
+ case "obj": {
2713
+ const out = {};
2714
+ for (const [key, item] of Object.entries(inner)) {
2715
+ Object.defineProperty(out, key, { value: await hydrate(item, registry), enumerable: true, writable: true, configurable: true });
2716
+ }
2717
+ return out;
2718
+ }
2719
+ case "mdl":
2720
+ return modelClass(registry, meta.class).findOrFail(meta.key);
2721
+ case "elcln": {
2722
+ const cls = modelClass(registry, meta.class);
2723
+ const keys = meta.keys ?? [];
2724
+ const keyName = new cls().getKeyName();
2725
+ const found = [...await cls.query().whereIn(keyName, keys).get()];
2726
+ const byKey = new Map(found.map((model) => [String(model.getKey()), model]));
2727
+ return new Collection(keys.map((key) => byKey.get(String(key))).filter((model) => model !== void 0));
2728
+ }
2729
+ }
2730
+ throw new LiveError(`Unknown snapshot value type [${String(meta.s)}].`);
2731
+ }
2732
+ var HOOKS = /* @__PURE__ */ new Set(["constructor", "render", "mount", "boot", "booted", "hydrate", "dehydrate", "updating", "updated", "rendering", "rendered", "exception"]);
2733
+ var HOOK_PREFIX = /^(updating|updated|hydrate|dehydrate)[A-Z_]/;
2734
+ var UNSAFE_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
2735
+ var baseApi;
2736
+ var isBaseApi = (name) => (baseApi ??= new Set(Object.getOwnPropertyNames(Component.prototype))).has(name);
2737
+ var INDEX = /^(?:0|[1-9]\d*)$/;
2738
+ function isArraySlot(array, segment) {
2739
+ return INDEX.test(segment) && Number(segment) <= array.length;
2740
+ }
2741
+ function isCallable(instance, method) {
2742
+ if (method === "" || method.startsWith("_") || method.startsWith("$") || method.startsWith("#")) return false;
2743
+ if (HOOKS.has(method) || HOOK_PREFIX.test(method) || isBaseApi(method)) return false;
2744
+ if (lockedOf(instance.constructor).has(method)) return false;
2745
+ const isBase = (proto) => proto === Component.prototype || proto === Object.prototype;
2746
+ for (let proto = Object.getPrototypeOf(instance); proto !== null && !isBase(proto); proto = Object.getPrototypeOf(proto)) {
2747
+ const descriptor = Object.getOwnPropertyDescriptor(proto, method);
2748
+ if (descriptor !== void 0) return typeof descriptor.value === "function";
2749
+ }
2750
+ return false;
2751
+ }
2752
+ function isUpdatable(instance, path) {
2753
+ const segments = path.split(".");
2754
+ const root = segments[0];
2755
+ if (root === "" || root.startsWith("_") || segments.some((segment) => segment === "" || UNSAFE_SEGMENTS.has(segment))) return false;
2756
+ if (!Object.hasOwn(instance, root)) return false;
2757
+ const descriptor = Object.getOwnPropertyDescriptor(instance, root);
2758
+ if (!descriptor.enumerable || descriptor.get !== void 0) return false;
2759
+ const cls = instance.constructor;
2760
+ if (lockedOf(cls).has(root) || computedOf(cls).has(root)) return false;
2761
+ let node = descriptor.value;
2762
+ for (const segment of segments.slice(1)) {
2763
+ if (isModel(node) || node instanceof Collection) return false;
2764
+ if (Array.isArray(node) && !isArraySlot(node, segment)) return false;
2765
+ if (typeof node !== "object" || node === null || !Object.hasOwn(node, segment)) return true;
2766
+ node = node[segment];
2767
+ }
2768
+ return !isModel(node) && !(node instanceof Collection);
2769
+ }
2541
2770
  var COMPONENT = /* @__PURE__ */ Symbol.for("oriel:live.component");
2542
2771
  var states = /* @__PURE__ */ new WeakMap();
2543
2772
  function stateOf(component) {
@@ -2668,57 +2897,49 @@ var Component = class {
2668
2897
  skipRender() {
2669
2898
  stateOf(this).skipRender = true;
2670
2899
  }
2900
+ /**
2901
+ * An action for an `o:` directive, typed against its method: `o:click={this.$action(this.delete, tag.id)}` writes
2902
+ * `delete(5)`, and a string where `delete` takes a number is a type error. The arguments are written as JSON, the
2903
+ * trailing `undefined` ones left out so the method's defaults apply. The method must be one of this component's
2904
+ * actions (a public method of its class, not a hook and not @Locked); anything else throws while rendering.
2905
+ */
2906
+ $action(method, ...params) {
2907
+ const name = typeof method === "function" ? method.name : "";
2908
+ if (name === "" || this[name] !== method || !isCallable(this, name)) {
2909
+ throw new LiveError(
2910
+ `[${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).`
2911
+ );
2912
+ }
2913
+ let count = params.length;
2914
+ while (count > 0 && params[count - 1] === void 0) count--;
2915
+ return count === 0 ? name : `${name}(${params.slice(0, count).map((param) => JSON.stringify(param) ?? "null").join(", ")})`;
2916
+ }
2671
2917
  /** A full-page component as a route action: Route.get('/counter', Counter). Route parameters go to mount(). */
2672
2918
  async __invoke() {
2673
2919
  return inject(LiveManager).page(this);
2674
2920
  }
2675
2921
  };
2676
- var LiveError = class extends Error {
2677
- name = "LiveError";
2678
- kind = "Server";
2679
- status = 500;
2680
- };
2681
- var CorruptComponentPayloadError = class extends LiveError {
2682
- name = "CorruptComponentPayloadError";
2683
- kind = "Authorization";
2684
- status = 403;
2685
- constructor(component = "unknown") {
2686
- super(`Live encountered corrupt data when trying to hydrate the [${component}] component.`);
2687
- }
2688
- };
2689
- var MethodNotCallableError = class extends LiveError {
2690
- name = "MethodNotCallableError";
2691
- kind = "Authorization";
2692
- status = 403;
2693
- constructor(method, component) {
2694
- super(`Unable to call component method. Public method [${method}] not found on component [${component}].`);
2695
- }
2696
- };
2697
- var PropertyNotUpdatableError = class extends LiveError {
2698
- name = "PropertyNotUpdatableError";
2699
- kind = "Authorization";
2700
- status = 403;
2701
- constructor(path, component) {
2702
- super(`Cannot update property [${path}] on component [${component}]: it is not a public, unlocked property.`);
2703
- }
2704
- };
2705
- var LiveComponentNotFoundError = class extends LiveError {
2706
- name = "LiveComponentNotFoundError";
2707
- kind = "Http";
2708
- status = 404;
2709
- constructor(component) {
2710
- super(`Unable to find component: [${component}].`);
2711
- }
2712
- };
2713
- var InvalidLiveRequestError = class extends LiveError {
2714
- name = "InvalidLiveRequestError";
2715
- kind = "Http";
2716
- status = 400;
2717
- };
2922
+ function trackFlash(session, { saved = true } = {}) {
2923
+ if (session === void 0) return () => void 0;
2924
+ const before = new Set(session.get("_flash.new", []));
2925
+ return (redirected) => {
2926
+ if (redirected) return;
2927
+ const flashed = session.get("_flash.new", []);
2928
+ const fresh = flashed.filter((key) => !before.has(key));
2929
+ if (fresh.length === 0) return;
2930
+ session.put("_flash.new", flashed.filter((key) => before.has(key)));
2931
+ if (!saved) return session.forget(...fresh);
2932
+ const old = session.get("_flash.old", []);
2933
+ session.put("_flash.old", [...old, ...fresh.filter((key) => !old.includes(key))]);
2934
+ };
2935
+ }
2718
2936
  var ESCAPES = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#039;" };
2719
2937
  function escapeAttribute2(value) {
2720
2938
  return value.replace(/[&<>"']/g, (char) => ESCAPES[char]);
2721
2939
  }
2940
+ function stripTags(html) {
2941
+ return html.replace(/<[^>]*>/g, "");
2942
+ }
2722
2943
  var VOID = /* @__PURE__ */ new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr"]);
2723
2944
  var RAW_TEXT = /* @__PURE__ */ new Set(["script", "style", "textarea", "title"]);
2724
2945
  var TOKEN = /<\/([A-Za-z][\w:-]*)\s*>|<([A-Za-z][\w:-]*)(?:\s+[^\s"'<>/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*\s*(\/?)>/g;
@@ -2773,6 +2994,15 @@ function requestScope2() {
2773
2994
  const container = currentContainer() ?? Container.getInstance();
2774
2995
  return container?.bound(TestRequestScope) === true ? container.make(TestRequestScope) : (run) => run();
2775
2996
  }
2997
+ function currentSession() {
2998
+ const container = currentContainer();
2999
+ if (container?.bound(Session) !== true) return void 0;
3000
+ try {
3001
+ return container.make(Session);
3002
+ } catch {
3003
+ return void 0;
3004
+ }
3005
+ }
2776
3006
  var Testable = class _Testable {
2777
3007
  constructor(manager, scope, result) {
2778
3008
  this.manager = manager;
@@ -2827,7 +3057,12 @@ var Testable = class _Testable {
2827
3057
  return this.#update({}, [{ method: "$refresh", params: [] }]);
2828
3058
  }
2829
3059
  async #update(updates, calls) {
2830
- const result = await this.scope(() => this.manager.updateComponent(this.#snapshot, updates, calls));
3060
+ const result = await this.scope(async () => {
3061
+ const flashed = trackFlash(currentSession(), { saved: false });
3062
+ const updated = await this.manager.updateComponent(this.#snapshot, updates, calls);
3063
+ flashed(updated.effects.redirect !== void 0);
3064
+ return updated;
3065
+ });
2831
3066
  this.#snapshot = JSON.stringify(result.snapshot);
2832
3067
  if (result.html !== void 0) this.#html = result.html;
2833
3068
  this.#effects = result.effects;
@@ -2857,6 +3092,17 @@ var Testable = class _Testable {
2857
3092
  for (const value of values) if (!this.html().includes(value)) fail(`Failed asserting that [${value}] is in the rendered HTML.`, this.html(), value);
2858
3093
  return this;
2859
3094
  }
3095
+ /** Each value, HTML-escaped, appears in the rendered text: the HTML with its tags stripped (Livewire's assertSeeText). */
3096
+ assertSeeText(...values) {
3097
+ const text = stripTags(this.html());
3098
+ for (const value of values) if (!text.includes(escapeAttribute2(value))) fail(`Failed asserting that [${value}] is in the rendered text.`, text, value);
3099
+ return this;
3100
+ }
3101
+ assertDontSeeText(...values) {
3102
+ const text = stripTags(this.html());
3103
+ for (const value of values) if (text.includes(escapeAttribute2(value))) fail(`Failed asserting that [${value}] is not in the rendered text.`, text, value);
3104
+ return this;
3105
+ }
2860
3106
  assertDispatched(name, params) {
2861
3107
  const dispatched = (this.#effects.dispatches ?? []).filter((event) => event.name === name);
2862
3108
  if (dispatched.length === 0) fail(`Failed asserting that an event [${name}] was dispatched.`, this.#effects.dispatches ?? [], name);
@@ -2958,10 +3204,11 @@ async function discover(directory) {
2958
3204
  }
2959
3205
  return found;
2960
3206
  }
2961
- var PERSISTENT_MIDDLEWARE = ["auth", "verified", "guest"];
3207
+ var PERSISTENT_MIDDLEWARE = ["auth", "verified", "guest", "can"];
3208
+ var ROUTER_METHODS = ["getRoutesByMatchOrder", "getPatterns", "getPatternVersion", "getMiddleware", "getMiddlewareGroups", "substituteBindings"];
2962
3209
  function canMatch(router) {
2963
3210
  const methods = router;
2964
- return ["getRoutesByMatchOrder", "getPatterns", "getPatternVersion", "getMiddleware", "getMiddlewareGroups"].every((name) => typeof methods[name] === "function");
3211
+ return ROUTER_METHODS.every((name) => typeof methods[name] === "function");
2965
3212
  }
2966
3213
  function isMatchingRoute(route2) {
2967
3214
  const candidate = route2;
@@ -2990,7 +3237,7 @@ function expand(references, aliases, groups, out = [], visiting = /* @__PURE__ *
2990
3237
  }
2991
3238
  return out;
2992
3239
  }
2993
- function persistentMiddleware(router, persistent, method, path) {
3240
+ function persistentPass(router, persistent, method, path) {
2994
3241
  if (!canMatch(router)) throw new LiveError("Live re-applies the page route's middleware to updates, which needs Oriel's router.");
2995
3242
  const routes = router.getRoutesByMatchOrder();
2996
3243
  if (!Array.isArray(routes) || !routes.every(isMatchingRoute)) throw new LiveError("Live re-applies the page route's middleware to updates, which needs Oriel's routes.");
@@ -2998,177 +3245,139 @@ function persistentMiddleware(router, persistent, method, path) {
2998
3245
  const target2 = routePath(path);
2999
3246
  const patterns = router.getPatterns();
3000
3247
  const version = router.getPatternVersion();
3001
- const route2 = routes.find((candidate) => candidate.methods.includes(verb) && candidate.match(target2, patterns, version) !== void 0);
3002
- if (route2 === void 0) return [];
3248
+ let matched;
3249
+ for (const candidate of routes) {
3250
+ const values2 = candidate.methods.includes(verb) ? candidate.match(target2, patterns, version) : void 0;
3251
+ if (values2 !== void 0) {
3252
+ matched = { route: candidate, values: values2 };
3253
+ break;
3254
+ }
3255
+ }
3256
+ if (matched === void 0) return void 0;
3257
+ const { route: route2, values } = matched;
3003
3258
  const aliases = router.getMiddleware();
3004
3259
  const groups = router.getMiddlewareGroups();
3005
3260
  const wanted = new Set(persistent.flatMap((name) => Object.hasOwn(aliases, name) ? [aliases[name]] : []));
3006
3261
  const excluded = new Set(expand(route2.excludedMiddleware(), aliases, groups).map((entry) => entry.cls));
3007
3262
  const ids = /* @__PURE__ */ new Map();
3008
3263
  const seen = /* @__PURE__ */ new Set();
3009
- return expand(route2.gatherMiddleware(), aliases, groups).filter((entry) => {
3264
+ const middleware = expand(route2.gatherMiddleware(), aliases, groups).filter((entry) => {
3010
3265
  if (!ids.has(entry.cls)) ids.set(entry.cls, ids.size);
3011
3266
  const key = `${ids.get(entry.cls)}:${entry.parameters.join(",")}`;
3012
3267
  if (!wanted.has(entry.cls) || excluded.has(entry.cls) || seen.has(key)) return false;
3013
3268
  seen.add(key);
3014
3269
  return true;
3015
- });
3016
- }
3017
- async function throughMiddleware(container, request2, middleware, destination) {
3018
- let ran;
3019
- const run = async (index) => {
3020
- if (index === middleware.length) {
3021
- ran = { result: await destination() };
3022
- return new Response(null, { status: 204 });
3023
- }
3024
- const { cls, parameters } = middleware[index];
3025
- const instance = container.make(cls);
3026
- const next = () => run(index + 1);
3027
- return instance.handle(request2, next, ...parameters);
3028
- };
3029
- const response2 = await run(0);
3030
- return ran ? ran.result : response2;
3031
- }
3032
- function isModel(value) {
3033
- if (typeof value !== "object" || value === null) return false;
3034
- const candidate = value;
3035
- return typeof candidate.getKey === "function" && typeof candidate.getKeyName === "function" && typeof candidate.getTable === "function" && typeof candidate.exists === "boolean";
3036
- }
3037
- var isPlainObject2 = (value) => {
3038
- const proto = Object.getPrototypeOf(value);
3039
- return proto === Object.prototype || proto === null;
3040
- };
3041
- 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";
3042
- function classNameOf(registry, cls) {
3043
- return registry.nameOf(cls) ?? registry.register(cls);
3044
- }
3045
- function dehydrate(value, registry, path, seen = /* @__PURE__ */ new Set()) {
3046
- switch (typeof value) {
3047
- case "undefined":
3048
- return [null, { s: "u" }];
3049
- case "string":
3050
- case "boolean":
3051
- return value;
3052
- case "number":
3053
- if (!Number.isFinite(value)) throw new LiveError(`Property [${path}] is ${value}: Live state must be JSON (finite numbers only).`);
3054
- return value;
3055
- case "bigint":
3056
- case "symbol":
3057
- case "function":
3058
- throw new LiveError(`Property [${path}] holds a ${typeof value}: Live state may only hold primitives, arrays, plain objects, Dates, models and collections.`);
3059
- }
3060
- if (value === null) return null;
3061
- const object = value;
3062
- if (seen.has(object)) throw new LiveError(`Property [${path}] contains a circular reference.`);
3063
- seen.add(object);
3064
- try {
3065
- if (object instanceof Date) return [object.toISOString(), { s: "dt" }];
3066
- if (Array.isArray(object)) return [object.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), { s: "arr" }];
3067
- if (isModel(object)) {
3068
- const name = classNameOf(registry, object.constructor);
3069
- if (!object.exists) throw new LiveError(`Property [${path}] holds an unsaved [${name}] model: only saved models can be kept between requests.`);
3070
- return [null, { s: "mdl", class: name, key: object.getKey() }];
3071
- }
3072
- if (object instanceof Collection) {
3073
- const items = object.all();
3074
- const first = items[0];
3075
- if (items.length > 0 && isModel(first) && items.every((item) => isModel(item) && item.constructor === first.constructor)) {
3076
- for (const [i, item] of items.entries()) {
3077
- if (!item.exists) throw new LiveError(`Property [${path}.${i}] holds an unsaved model.`);
3078
- }
3079
- const name = classNameOf(registry, first.constructor);
3080
- return [null, { s: "elcln", class: name, keys: items.map((item) => item.getKey()) }];
3081
- }
3082
- return [items.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), { s: "clctn" }];
3083
- }
3084
- if (isPlainObject2(object)) {
3085
- const out = {};
3086
- for (const [key, item] of Object.entries(object)) out[key] = dehydrate(item, registry, `${path}.${key}`, seen);
3087
- return [out, { s: "obj" }];
3270
+ });
3271
+ const early = new Set(expand(["web", "api"], aliases, groups).map((entry) => entry.cls));
3272
+ for (const name of ["auth", "guest"]) if (Object.hasOwn(aliases, name)) early.add(aliases[name]);
3273
+ const before = middleware.filter((entry) => early.has(entry.cls));
3274
+ const after = middleware.filter((entry) => !early.has(entry.cls));
3275
+ return { router, route: route2, values, middleware: [...before, ...after], at: before.length };
3276
+ }
3277
+ function persistentMiddleware(router, persistent, method, path) {
3278
+ return [...persistentPass(router, persistent, method, path)?.middleware ?? []];
3279
+ }
3280
+ function currentRoute(container) {
3281
+ if (!container.bound(CurrentRoute)) return void 0;
3282
+ try {
3283
+ return container.make(CurrentRoute);
3284
+ } catch {
3285
+ return void 0;
3286
+ }
3287
+ }
3288
+ async function throughMiddleware(container, request2, pass, destination) {
3289
+ const original2 = currentRoute(container);
3290
+ const page = { route: pass.route, parameters: { ...pass.values }, middleware: [] };
3291
+ let swapped = false;
3292
+ const show = () => {
3293
+ container.instance(CurrentRoute, page);
3294
+ swapped = true;
3295
+ };
3296
+ const restore = () => {
3297
+ if (!swapped) return;
3298
+ if (original2 === void 0) container.forgetInstance(CurrentRoute);
3299
+ else container.instance(CurrentRoute, original2);
3300
+ swapped = false;
3301
+ };
3302
+ let ran;
3303
+ const run = async (index) => {
3304
+ if (index === pass.at) Object.assign(page.parameters, await pass.router.substituteBindings(pass.route, pass.values, container));
3305
+ if (index === pass.middleware.length) {
3306
+ restore();
3307
+ ran = { result: await destination() };
3308
+ return new Response(null, { status: 204 });
3088
3309
  }
3089
- const type = object.constructor?.name || "object";
3090
- throw new LiveError(
3091
- `Property [${path}] holds a [${type}] instance: Live state may only hold primitives, arrays, plain objects, Dates, models and collections.`
3092
- );
3310
+ const { cls, parameters } = pass.middleware[index];
3311
+ const instance = container.make(cls);
3312
+ const next = () => run(index + 1);
3313
+ return instance.handle(request2, next, ...parameters);
3314
+ };
3315
+ show();
3316
+ try {
3317
+ const response2 = await run(0);
3318
+ return ran ? ran.result : response2;
3093
3319
  } finally {
3094
- seen.delete(object);
3320
+ restore();
3095
3321
  }
3096
3322
  }
3097
- function modelClass(registry, name) {
3098
- if (typeof name !== "string" || !registry.has(name)) throw new LiveError(`Unable to hydrate the [${String(name)}] model: it is not registered.`);
3099
- return registry.resolve(name);
3323
+ var PAGINATED = /* @__PURE__ */ Symbol.for("oriel:live.pagination");
3324
+ function toPage(value) {
3325
+ const page = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : Number.NaN;
3326
+ return Number.isFinite(page) && page >= 1 ? Math.min(Math.floor(page), Number.MAX_SAFE_INTEGER) : 1;
3100
3327
  }
3101
- async function hydrate(value, registry) {
3102
- if (!isTuple(value)) return value;
3103
- const [inner, meta] = value;
3104
- switch (meta.s) {
3105
- case "u":
3106
- return void 0;
3107
- case "dt":
3108
- return new Date(String(inner));
3109
- case "arr":
3110
- return Promise.all(inner.map((item) => hydrate(item, registry)));
3111
- case "clctn":
3112
- return new Collection(await Promise.all(inner.map((item) => hydrate(item, registry))));
3113
- case "obj": {
3114
- const out = {};
3115
- for (const [key, item] of Object.entries(inner)) {
3116
- Object.defineProperty(out, key, { value: await hydrate(item, registry), enumerable: true, writable: true, configurable: true });
3117
- }
3118
- return out;
3328
+ async function hook(instance, name, ...args) {
3329
+ const method = instance[name];
3330
+ if (typeof method === "function") await method.apply(instance, args);
3331
+ }
3332
+ function WithPagination(Base) {
3333
+ class WithPaginationMixin extends Base {
3334
+ page = 1;
3335
+ nextPage() {
3336
+ return this.setPage(toPage(this.page) + 1);
3119
3337
  }
3120
- case "mdl":
3121
- return modelClass(registry, meta.class).findOrFail(meta.key);
3122
- case "elcln": {
3123
- const cls = modelClass(registry, meta.class);
3124
- const keys = meta.keys ?? [];
3125
- const keyName = new cls().getKeyName();
3126
- const found = [...await cls.query().whereIn(keyName, keys).get()];
3127
- const byKey = new Map(found.map((model) => [String(model.getKey()), model]));
3128
- return new Collection(keys.map((key) => byKey.get(String(key))).filter((model) => model !== void 0));
3338
+ previousPage() {
3339
+ return this.setPage(Math.max(toPage(this.page) - 1, 1));
3340
+ }
3341
+ gotoPage(page) {
3342
+ return this.setPage(page);
3343
+ }
3344
+ resetPage() {
3345
+ return this.setPage(1);
3346
+ }
3347
+ async setPage(page) {
3348
+ const next = toPage(page);
3349
+ await hook(this, "updatingPage", next);
3350
+ this.page = next;
3351
+ await hook(this, "updatedPage", next);
3129
3352
  }
3130
3353
  }
3131
- throw new LiveError(`Unknown snapshot value type [${String(meta.s)}].`);
3354
+ Object.defineProperty(WithPaginationMixin.prototype, PAGINATED, { value: true });
3355
+ return WithPaginationMixin;
3132
3356
  }
3133
- var HOOKS = /* @__PURE__ */ new Set(["constructor", "render", "mount", "boot", "booted", "hydrate", "dehydrate", "updating", "updated", "rendering", "rendered", "exception"]);
3134
- var HOOK_PREFIX = /^(updating|updated|hydrate|dehydrate)[A-Z_]/;
3135
- var UNSAFE_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
3136
- var BASE_API = new Set(Object.getOwnPropertyNames(Component.prototype));
3137
- var INDEX = /^(?:0|[1-9]\d*)$/;
3138
- function isArraySlot(array, segment) {
3139
- return INDEX.test(segment) && Number(segment) <= array.length;
3357
+ function isPaginated(component) {
3358
+ return component[PAGINATED] === true;
3140
3359
  }
3141
- function isCallable(instance, method) {
3142
- if (method === "" || method.startsWith("_") || method.startsWith("$") || method.startsWith("#")) return false;
3143
- if (HOOKS.has(method) || HOOK_PREFIX.test(method) || BASE_API.has(method)) return false;
3144
- if (lockedOf(instance.constructor).has(method)) return false;
3145
- const isBase = (proto) => proto === Component.prototype || proto === Object.prototype;
3146
- for (let proto = Object.getPrototypeOf(instance); proto !== null && !isBase(proto); proto = Object.getPrototypeOf(proto)) {
3147
- const descriptor = Object.getOwnPropertyDescriptor(proto, method);
3148
- if (descriptor !== void 0) return typeof descriptor.value === "function";
3149
- }
3150
- return false;
3360
+ var rendering = new AsyncLocalStorage();
3361
+ function renderPaginated(component, url2, run) {
3362
+ return rendering.run(isPaginated(component) ? { component, url: url2 } : void 0, run);
3151
3363
  }
3152
- function isUpdatable(instance, path) {
3153
- const segments = path.split(".");
3154
- const root = segments[0];
3155
- if (root === "" || root.startsWith("_") || segments.some((segment) => segment === "" || UNSAFE_SEGMENTS.has(segment))) return false;
3156
- if (!Object.hasOwn(instance, root)) return false;
3157
- const descriptor = Object.getOwnPropertyDescriptor(instance, root);
3158
- if (!descriptor.enumerable || descriptor.get !== void 0) return false;
3159
- const cls = instance.constructor;
3160
- if (lockedOf(cls).has(root) || computedOf(cls).has(root)) return false;
3161
- let node = descriptor.value;
3162
- for (const segment of segments.slice(1)) {
3163
- if (isModel(node) || node instanceof Collection) return false;
3164
- if (Array.isArray(node) && !isArraySlot(node, segment)) return false;
3165
- if (typeof node !== "object" || node === null || !Object.hasOwn(node, segment)) return true;
3166
- node = node[segment];
3167
- }
3168
- return !isModel(node) && !(node instanceof Collection);
3364
+ function outsidePagination(run) {
3365
+ return rendering.run(void 0, run);
3169
3366
  }
3170
- var renderContext = new AsyncLocalStorage();
3171
- var pageRequest = new AsyncLocalStorage();
3367
+ var livePaginationState = {
3368
+ currentPage(pageName) {
3369
+ const current = rendering.getStore();
3370
+ if (current === void 0 || pageName !== "page") return void 0;
3371
+ const page = toPage(current.component.page);
3372
+ current.component.page = page;
3373
+ return page;
3374
+ },
3375
+ currentPath() {
3376
+ return rendering.getStore()?.url;
3377
+ }
3378
+ };
3379
+ var renderContext = new AsyncLocalStorage2();
3380
+ var pageRequest = new AsyncLocalStorage2();
3172
3381
  function optional(container, token2) {
3173
3382
  if (!container.bound(token2)) return void 0;
3174
3383
  try {
@@ -3181,7 +3390,7 @@ var isRecord = (value) => typeof value === "object" && value !== null && !Array.
3181
3390
  function setOwn(object, key, value) {
3182
3391
  Object.defineProperty(object, key, { value, enumerable: true, writable: true, configurable: true });
3183
3392
  }
3184
- async function hook(instance, name, ...args) {
3393
+ async function hook2(instance, name, ...args) {
3185
3394
  const method = instance[name];
3186
3395
  if (typeof method === "function") await method.apply(instance, args);
3187
3396
  }
@@ -3232,7 +3441,7 @@ var LiveComponentManager = class {
3232
3441
  setOwn(context.children, childKey, existing);
3233
3442
  return `<${existing[0]} o:id="${escapeAttribute2(existing[1])}"></${existing[0]}>`;
3234
3443
  }
3235
- const result = await this.mountComponent(component, params);
3444
+ const result = await outsidePagination(() => this.mountComponent(component, params));
3236
3445
  setOwn(context.children, childKey, [result.tag, result.snapshot.memo.id]);
3237
3446
  return result.html;
3238
3447
  }
@@ -3244,14 +3453,14 @@ var LiveComponentManager = class {
3244
3453
  const state = stateOf(instance);
3245
3454
  state.id = randomBytes2(15).toString("base64url");
3246
3455
  state.name = name;
3247
- await hook(instance, "boot");
3456
+ await hook2(instance, "boot");
3248
3457
  for (const [property, value] of Object.entries(params)) {
3249
3458
  if (Object.hasOwn(instance, property)) instance[property] = value;
3250
3459
  }
3251
3460
  this.prepareComputed(instance);
3252
- await hook(instance, "mount", params);
3253
3461
  const request2 = optional(this.container(), HttpRequest);
3254
3462
  const page = pageRequest.getStore() ?? { path: request2?.path() ?? "", method: request2?.method() ?? "GET" };
3463
+ await renderPaginated(instance, this.pageUrl(page.path), () => hook2(instance, "mount", params));
3255
3464
  const memo = { id: state.id, name, path: page.path, method: page.method, children: {}, errors: {} };
3256
3465
  const { html, tag } = await this.render(instance, memo, {});
3257
3466
  const effects = this.effects(instance);
@@ -3279,22 +3488,24 @@ var LiveComponentManager = class {
3279
3488
  state.id = snapshot.memo.id;
3280
3489
  state.name = snapshot.memo.name;
3281
3490
  state.errors = { ...snapshot.memo.errors };
3282
- await hook(instance, "boot");
3491
+ await hook2(instance, "boot");
3283
3492
  const registry = this.registry();
3284
3493
  for (const [property, value] of Object.entries(snapshot.data)) {
3285
3494
  Object.defineProperty(instance, property, { value: await hydrate(value, registry), enumerable: true, writable: true, configurable: true });
3286
3495
  }
3287
3496
  this.prepareComputed(instance);
3288
- await hook(instance, "hydrate");
3289
3497
  const returns = [];
3290
- try {
3291
- for (const [path, value] of Object.entries(updates)) await this.applyUpdate(instance, path, value);
3292
- for (const call of calls) returns.push(await this.call(instance, call.method, call.params));
3293
- } catch (error) {
3294
- const errors = error.errors;
3295
- if (error.kind !== "Validation" || !isRecord(errors)) throw error;
3296
- state.errors = { ...errors };
3297
- }
3498
+ await renderPaginated(instance, this.pageUrl(snapshot.memo.path), async () => {
3499
+ await hook2(instance, "hydrate");
3500
+ try {
3501
+ for (const [path, value] of Object.entries(updates)) await this.applyUpdate(instance, path, value);
3502
+ for (const call of calls) returns.push(await this.call(instance, call.method, call.params));
3503
+ } catch (error) {
3504
+ const errors = error.errors;
3505
+ if (error.kind !== "Validation" || !isRecord(errors)) throw error;
3506
+ state.errors = { ...errors };
3507
+ }
3508
+ });
3298
3509
  const memo = { ...snapshot.memo, children: { ...snapshot.memo.children } };
3299
3510
  const effects = this.effects(instance);
3300
3511
  let html;
@@ -3309,25 +3520,30 @@ var LiveComponentManager = class {
3309
3520
  return { instance, snapshot: await this.snapshot(instance, memo), effects, html, tag };
3310
3521
  }
3311
3522
  /**
3312
- * The route action of POST /oriel/live/update. Each component's update runs behind the auth middleware of the route
3313
- * that rendered its page (Livewire's persistent middleware): a signed-out tab gets a 401, never the action. When a
3314
- * middleware answers instead (a redirect), that is the response.
3523
+ * The route action of POST /oriel/live/update. Each component's update runs behind the persistent middleware of the
3524
+ * route that rendered its page (Livewire's): `auth`, `verified`, `guest` and `can`, with the page route's parameters
3525
+ * bound again. A signed-out tab gets a 401, a revoked ability a 403 and a deleted model a 404, before the component
3526
+ * hydrates. When a middleware answers instead (a redirect), that is the response. A key first flashed during the
3527
+ * update shows in this response only, unless the update redirects.
3315
3528
  */
3316
3529
  async handle(request2) {
3317
3530
  if (!this.isLiveRequest(request2)) throw new InvalidLiveRequestError("Live update requests need the X-Oriel-Live header.");
3318
3531
  const container = this.container();
3319
3532
  const persistent = optional(container, ConfigRepository)?.get("live.persistent_middleware", PERSISTENT_MIDDLEWARE) ?? PERSISTENT_MIDDLEWARE;
3320
3533
  const components = [];
3534
+ const flashed = trackFlash(optional(container, Session));
3321
3535
  for (const component of this.payload(request2).components) {
3322
3536
  const { memo } = this.verify(component.snapshot);
3323
- const middleware = persistentMiddleware(container.make(Router), persistent, memo.method, memo.path);
3537
+ const pass = persistentPass(container.make(Router), persistent, memo.method, memo.path);
3538
+ const update = () => this.updateComponent(component.snapshot, component.updates, component.calls);
3324
3539
  const result = await pageRequest.run(
3325
3540
  { path: memo.path, method: memo.method },
3326
- () => throughMiddleware(container, request2, middleware, () => this.updateComponent(component.snapshot, component.updates, component.calls))
3541
+ () => pass === void 0 || pass.middleware.length === 0 ? update() : throughMiddleware(container, request2, pass, update)
3327
3542
  );
3328
3543
  if (result instanceof Response) return result;
3329
3544
  components.push({ snapshot: JSON.stringify(result.snapshot), effects: result.effects });
3330
3545
  }
3546
+ flashed(components.some((component) => component.effects.redirect !== void 0));
3331
3547
  return { components };
3332
3548
  }
3333
3549
  /** A full-page component (Route.get('/counter', Counter)): mounted with the route parameters, inside the layout. */
@@ -3402,8 +3618,8 @@ var LiveComponentManager = class {
3402
3618
  const [root, ...rest] = path.split(".");
3403
3619
  const key = rest.length > 0 ? rest.join(".") : void 0;
3404
3620
  const studly = Str.studly(root);
3405
- await hook(instance, "updating", path, value);
3406
- await hook(instance, `updating${studly}`, value, key);
3621
+ await hook2(instance, "updating", path, value);
3622
+ await hook2(instance, `updating${studly}`, value, key);
3407
3623
  const target2 = instance;
3408
3624
  if (rest.length === 0) target2[root] = value;
3409
3625
  else {
@@ -3421,8 +3637,8 @@ var LiveComponentManager = class {
3421
3637
  assertSlot(node, last);
3422
3638
  Object.defineProperty(node, last, { value, enumerable: true, writable: true, configurable: true });
3423
3639
  }
3424
- await hook(instance, "updated", path, value);
3425
- await hook(instance, `updated${studly}`, value, key);
3640
+ await hook2(instance, "updated", path, value);
3641
+ await hook2(instance, `updated${studly}`, value, key);
3426
3642
  }
3427
3643
  /** Runs one call from the browser: a magic action, an event, or a public method. Returns its return value. */
3428
3644
  async call(instance, method, params) {
@@ -3450,18 +3666,35 @@ var LiveComponentManager = class {
3450
3666
  if (!isCallable(instance, method)) throw new MethodNotCallableError(method, name);
3451
3667
  return instance[method](...params);
3452
3668
  }
3453
- /** Renders the component (rendering/rendered hooks, nested children tracked) and checks for a single root. */
3669
+ /**
3670
+ * Renders the component (rendering/rendered hooks, nested children tracked) and checks for a single root. A
3671
+ * WithPagination component's page and page URL are what paginate() reads while it renders, as in its mount() and in
3672
+ * its update's hooks and actions.
3673
+ */
3454
3674
  async render(instance, memo, previous) {
3455
- await hook(instance, "rendering");
3675
+ await hook2(instance, "rendering");
3456
3676
  const context = { previous, children: {}, index: 0 };
3457
- const html = await renderContext.run(context, async () => {
3458
- const output = await instance.render();
3459
- return typeof output === "string" ? output : this.container().make(ViewFactory).renderToString(output);
3460
- });
3461
- await hook(instance, "rendered", html);
3677
+ const html = await renderContext.run(
3678
+ context,
3679
+ () => renderPaginated(instance, this.pageUrl(memo.path), async () => {
3680
+ const output = await instance.render();
3681
+ return typeof output === "string" ? output : this.container().make(ViewFactory).renderToString(output);
3682
+ })
3683
+ );
3684
+ await hook2(instance, "rendered", html);
3462
3685
  memo.children = context.children;
3463
3686
  return { html: html.trim(), tag: rootTag(html, memo.name) };
3464
3687
  }
3688
+ /** The absolute URL of the page a memo path belongs to, on the current request's origin ('/' + path without one). */
3689
+ pageUrl(path) {
3690
+ const relative3 = `/${path.replace(/^\/+/, "")}`;
3691
+ const url2 = optional(this.container(), HttpRequest)?.url();
3692
+ try {
3693
+ return url2 === void 0 ? relative3 : new URL(relative3, url2).href;
3694
+ } catch {
3695
+ return relative3;
3696
+ }
3697
+ }
3465
3698
  /** Dispatches, redirects: the effects every response carries. */
3466
3699
  effects(instance) {
3467
3700
  const state = stateOf(instance);
@@ -3480,7 +3713,7 @@ var LiveComponentManager = class {
3480
3713
  }
3481
3714
  /** dehydrate() hook, then the state as snapshot data, the error bag, and the checksum over both. */
3482
3715
  async snapshot(instance, memo) {
3483
- await hook(instance, "dehydrate");
3716
+ await hook2(instance, "dehydrate");
3484
3717
  const registry = this.registry();
3485
3718
  const data = {};
3486
3719
  for (const [property, value] of Object.entries(instance)) data[property] = dehydrate(value, registry, `${memo.name}.${property}`);
@@ -3553,6 +3786,7 @@ var LiveServiceProvider = class extends ServiceProvider {
3553
3786
  this.mergeConfigFrom({ class_path: this.app.appPath("Live"), layout: "components.layouts.app", persistent_middleware: [...PERSISTENT_MIDDLEWARE] }, "live");
3554
3787
  this.app.singleton(LiveComponentManager, () => new LiveComponentManager(this.app));
3555
3788
  this.app.singleton(LiveManager, (container) => container.make(LiveComponentManager));
3789
+ this.app.singleton(PaginationState, () => livePaginationState);
3556
3790
  }
3557
3791
  async boot() {
3558
3792
  const manager = this.app.make(LiveComponentManager);
@@ -3566,6 +3800,40 @@ var LiveServiceProvider = class extends ServiceProvider {
3566
3800
  }
3567
3801
  }
3568
3802
  };
3803
+ function urlWindow(current, last, onEachSide) {
3804
+ const range = (from, to) => Array.from({ length: Math.max(to - from + 1, 0) }, (_, index) => from + index);
3805
+ if (last < onEachSide * 2 + 8) return range(1, last);
3806
+ const size = onEachSide + 4;
3807
+ if (current <= size) return [...range(1, size + onEachSide), "...", ...range(last - 1, last)];
3808
+ if (current > last - size) return [...range(1, 2), "...", ...range(last - (size + (onEachSide - 1)), last)];
3809
+ return [...range(1, 2), "...", ...range(current - onEachSide, current + onEachSide), "...", ...range(last - 1, last)];
3810
+ }
3811
+ function button(label, action, attributes = "") {
3812
+ const live2 = action === void 0 ? " disabled" : ` o:click="${escapeAttribute2(action)}"`;
3813
+ return `<button type="button" class="btn btn-secondary"${attributes}${live2}>${escapeAttribute2(label)}</button>`;
3814
+ }
3815
+ function Pagination({ paginator, class: className }) {
3816
+ if (typeof paginator.nextCursor === "function") {
3817
+ throw new LiveError("<Pagination> needs paginate() or simplePaginate(): a cursor paginator has no page numbers.");
3818
+ }
3819
+ const current = paginator.currentPage();
3820
+ const onFirst = paginator.onFirstPage();
3821
+ const hasMore = paginator.hasMorePages();
3822
+ if (onFirst && !hasMore) return { toHtml: () => "" };
3823
+ const parts = [button("\xAB Previous", onFirst ? void 0 : "previousPage")];
3824
+ if (typeof paginator.lastPage === "function") {
3825
+ const onEachSide = Number.isInteger(paginator.onEachSideCount) && paginator.onEachSideCount >= 0 ? paginator.onEachSideCount : 3;
3826
+ for (const page of urlWindow(current, paginator.lastPage(), onEachSide)) {
3827
+ if (page === "...") parts.push('<span aria-hidden="true">...</span>');
3828
+ else if (page === current) parts.push(`<button type="button" class="btn" aria-current="page" disabled>${page}</button>`);
3829
+ else parts.push(button(String(page), `gotoPage(${page})`, ` aria-label="Go to page ${page}"`));
3830
+ }
3831
+ }
3832
+ parts.push(button("Next \xBB", hasMore ? "nextPage" : void 0));
3833
+ const classes = className === void 0 || className === "" ? "" : ` class="${escapeAttribute2(className)}"`;
3834
+ const html = `<nav aria-label="Pagination"${classes}>${parts.join("")}</nav>`;
3835
+ return { toHtml: () => html };
3836
+ }
3569
3837
 
3570
3838
  // ../Testing/dist/index.js
3571
3839
  import { AssertionError as AssertionError2 } from "assert";
@@ -3577,7 +3845,131 @@ import { randomUUID } from "crypto";
3577
3845
  import { randomUUID as randomUUID2 } from "crypto";
3578
3846
  import { randomUUID as randomUUID3 } from "crypto";
3579
3847
  import { randomBytes as randomBytes3 } from "crypto";
3848
+ import { posix } from "path";
3580
3849
  import { deflateSync } from "zlib";
3850
+ var original;
3851
+ var testNow;
3852
+ var realDate = () => original ?? globalThis.Date;
3853
+ function install() {
3854
+ if (original !== void 0) return;
3855
+ const Real = original = globalThis.Date;
3856
+ const current = () => testNow ?? Real.now();
3857
+ const Travelling = new Proxy(Real, {
3858
+ construct: (target2, args, newTarget) => Reflect.construct(target2, args.length === 0 ? [current()] : args, newTarget === Travelling ? target2 : newTarget),
3859
+ apply: (target2) => new target2(current()).toString(),
3860
+ get: (target2, property) => property === "now" ? current : Reflect.get(target2, property, target2)
3861
+ });
3862
+ globalThis.Date = Travelling;
3863
+ }
3864
+ function setTestNow(date) {
3865
+ const time = new (realDate())(date).getTime();
3866
+ if (Number.isNaN(time)) throw new TypeError(`Invalid date: ${String(date)}`);
3867
+ install();
3868
+ testNow = time;
3869
+ return new (realDate())(time);
3870
+ }
3871
+ function thenTravelBack(callback) {
3872
+ let result;
3873
+ try {
3874
+ result = callback();
3875
+ } catch (error) {
3876
+ travelBack();
3877
+ throw error;
3878
+ }
3879
+ if (result instanceof Promise) return result.finally(travelBack);
3880
+ travelBack();
3881
+ return result;
3882
+ }
3883
+ function travelTo(date, callback) {
3884
+ const at = setTestNow(date);
3885
+ if (callback) return thenTravelBack(() => callback(at));
3886
+ }
3887
+ function travelBack() {
3888
+ if (original !== void 0) globalThis.Date = original;
3889
+ original = void 0;
3890
+ testNow = void 0;
3891
+ return /* @__PURE__ */ new Date();
3892
+ }
3893
+ function freezeTime(callback) {
3894
+ const at = new (realDate())(testNow ?? Date.now());
3895
+ if (callback) return travelTo(at, callback);
3896
+ travelTo(at);
3897
+ return at;
3898
+ }
3899
+ function freezeSecond(callback) {
3900
+ const at = new (realDate())(testNow ?? Date.now());
3901
+ at.setMilliseconds(0);
3902
+ if (callback) return travelTo(at, callback);
3903
+ travelTo(at);
3904
+ return at;
3905
+ }
3906
+ function travel(value) {
3907
+ return new Wormhole(value);
3908
+ }
3909
+ var Wormhole = class {
3910
+ constructor(value) {
3911
+ this.value = value;
3912
+ }
3913
+ value;
3914
+ millisecond(callback) {
3915
+ return this.milliseconds(callback);
3916
+ }
3917
+ milliseconds(callback) {
3918
+ return this.#move((date) => date.setTime(date.getTime() + this.value), callback);
3919
+ }
3920
+ second(callback) {
3921
+ return this.seconds(callback);
3922
+ }
3923
+ seconds(callback) {
3924
+ return this.#move((date) => date.setTime(date.getTime() + this.value * 1e3), callback);
3925
+ }
3926
+ minute(callback) {
3927
+ return this.minutes(callback);
3928
+ }
3929
+ minutes(callback) {
3930
+ return this.#move((date) => date.setTime(date.getTime() + this.value * 6e4), callback);
3931
+ }
3932
+ hour(callback) {
3933
+ return this.hours(callback);
3934
+ }
3935
+ hours(callback) {
3936
+ return this.#move((date) => date.setTime(date.getTime() + this.value * 36e5), callback);
3937
+ }
3938
+ day(callback) {
3939
+ return this.days(callback);
3940
+ }
3941
+ days(callback) {
3942
+ return this.#move((date) => date.setUTCDate(date.getUTCDate() + this.value), callback);
3943
+ }
3944
+ week(callback) {
3945
+ return this.weeks(callback);
3946
+ }
3947
+ weeks(callback) {
3948
+ return this.#move((date) => date.setUTCDate(date.getUTCDate() + this.value * 7), callback);
3949
+ }
3950
+ month(callback) {
3951
+ return this.months(callback);
3952
+ }
3953
+ months(callback) {
3954
+ return this.#move((date) => date.setUTCMonth(date.getUTCMonth() + this.value), callback);
3955
+ }
3956
+ year(callback) {
3957
+ return this.years(callback);
3958
+ }
3959
+ years(callback) {
3960
+ return this.#move((date) => date.setUTCFullYear(date.getUTCFullYear() + this.value), callback);
3961
+ }
3962
+ /** Laravel's Wormhole::back(). */
3963
+ static back() {
3964
+ return travelBack();
3965
+ }
3966
+ #move(step, callback) {
3967
+ const date = new (realDate())(testNow ?? Date.now());
3968
+ step(date);
3969
+ setTestNow(date);
3970
+ if (callback) return thenTravelBack(callback);
3971
+ }
3972
+ };
3581
3973
  function fail2(message, details = {}) {
3582
3974
  const error = new AssertionError2({ message, actual: details.actual, expected: details.expected, operator: "fail" });
3583
3975
  delete error.diff;
@@ -3599,7 +3991,7 @@ function format(value) {
3599
3991
  function escapeHtml2(value) {
3600
3992
  return value.replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#039;" })[char]);
3601
3993
  }
3602
- function stripTags(html) {
3994
+ function stripTags2(html) {
3603
3995
  return html.replace(/<[^>]*>/g, "");
3604
3996
  }
3605
3997
  function equals(a, b) {
@@ -4500,7 +4892,7 @@ ${this.excerpt()}`);
4500
4892
  }
4501
4893
  /** Like assertSee, against the content with its tags stripped. */
4502
4894
  assertSeeText(value, escape = true) {
4503
- const text = stripTags(this.content);
4895
+ const text = stripTags2(this.content);
4504
4896
  for (const needle of this.needles(value, escape)) {
4505
4897
  assert(text.includes(needle), `Failed asserting that the response text contains [${needle}].
4506
4898
 
@@ -4509,14 +4901,14 @@ ${this.excerpt(text)}`);
4509
4901
  return this;
4510
4902
  }
4511
4903
  assertDontSeeText(value, escape = true) {
4512
- const text = stripTags(this.content);
4904
+ const text = stripTags2(this.content);
4513
4905
  for (const needle of this.needles(value, escape)) {
4514
4906
  assert(!text.includes(needle), `Failed asserting that the response text does not contain [${needle}].`);
4515
4907
  }
4516
4908
  return this;
4517
4909
  }
4518
4910
  assertSeeTextInOrder(values, escape = true) {
4519
- return this.inOrder(stripTags(this.content), this.needles(values, escape));
4911
+ return this.inOrder(stripTags2(this.content), this.needles(values, escape));
4520
4912
  }
4521
4913
  // JSON --------------------------------------------------------------------------------------------------
4522
4914
  /**
@@ -5464,9 +5856,33 @@ var InteractsWithConsole = class extends InteractsWithDatabase {
5464
5856
  return new PendingCommand(this.app, command, parameters);
5465
5857
  }
5466
5858
  };
5467
- var TestCase = class extends InteractsWithConsole {
5859
+ var InteractsWithTime = class extends InteractsWithConsole {
5860
+ travelTo(date, callback) {
5861
+ return callback ? travelTo(date, callback) : travelTo(date);
5862
+ }
5863
+ travel(value) {
5864
+ return travel(value);
5865
+ }
5866
+ travelBack() {
5867
+ return travelBack();
5868
+ }
5869
+ freezeTime(callback) {
5870
+ return callback ? freezeTime(callback) : freezeTime();
5871
+ }
5872
+ freezeSecond(callback) {
5873
+ return callback ? freezeSecond(callback) : freezeSecond();
5874
+ }
5875
+ async tearDown() {
5876
+ try {
5877
+ await super.tearDown();
5878
+ } finally {
5879
+ travelBack();
5880
+ }
5881
+ }
5882
+ };
5883
+ var TestCase = class extends InteractsWithTime {
5468
5884
  };
5469
- function install(token2, fake) {
5885
+ function install2(token2, fake) {
5470
5886
  facade(token2).swap(fake);
5471
5887
  return fake;
5472
5888
  }
@@ -5495,10 +5911,27 @@ var QUEUE_OPTIONS = ["connection", "queue", "delay", "tries", "backoff", "timeou
5495
5911
  function isQueueable(job) {
5496
5912
  if (typeof job !== "object" || job === null) return false;
5497
5913
  const members = job;
5498
- if (members[SHOULD_QUEUE] === true) return true;
5914
+ if (members.shouldQueue === true || members[SHOULD_QUEUE] === true) return true;
5499
5915
  if (QUEUE_OPTIONS.some((option) => members[option] !== void 0)) return true;
5500
5916
  return typeof members.uniqueId === "function" || typeof members.retryUntil === "function";
5501
5917
  }
5918
+ var isClass = (value) => typeof value === "function" && typeof value.prototype?.handle === "function";
5919
+ var asPair = (listener) => {
5920
+ if (Array.isArray(listener) && listener.length === 2 && typeof listener[1] === "string") return [listener[0], listener[1]];
5921
+ return isClass(listener) ? [listener, "handle"] : void 0;
5922
+ };
5923
+ function listenerMatches(registered, expected) {
5924
+ if (expected === Function) return typeof registered === "function" && !isClass(registered);
5925
+ const [left, right] = [asPair(registered), asPair(expected)];
5926
+ if (left && right) return left[0] === right[0] && left[1] === right[1];
5927
+ return registered === expected;
5928
+ }
5929
+ function listenerName(listener) {
5930
+ const pair = asPair(listener);
5931
+ if (pair === void 0) return "Closure";
5932
+ const [owner, method] = pair;
5933
+ return Array.isArray(listener) ? `${owner.name}@${method}` : owner.name;
5934
+ }
5502
5935
  var EventFake = class _EventFake {
5503
5936
  constructor(dispatcher, eventsToFake = [], eventsToDispatch = []) {
5504
5937
  this.dispatcher = dispatcher;
@@ -5511,11 +5944,11 @@ var EventFake = class _EventFake {
5511
5944
  events = [];
5512
5945
  /** Installs the fake (on the current container) and returns it: `const events = EventFake.fake([OrderShipped])`. */
5513
5946
  static fake(eventsToFake = []) {
5514
- return install(EventDispatcher, new _EventFake(_EventFake.original(), [eventsToFake].flat()));
5947
+ return install2(EventDispatcher, new _EventFake(_EventFake.original(), [eventsToFake].flat()));
5515
5948
  }
5516
5949
  /** Fakes every event except these. */
5517
5950
  static fakeExcept(eventsToDispatch) {
5518
- return install(EventDispatcher, new _EventFake(_EventFake.original(), [], [eventsToDispatch].flat()));
5951
+ return install2(EventDispatcher, new _EventFake(_EventFake.original(), [], [eventsToDispatch].flat()));
5519
5952
  }
5520
5953
  static original() {
5521
5954
  const current = bound(EventDispatcher);
@@ -5572,9 +6005,20 @@ var EventFake = class _EventFake {
5572
6005
 
5573
6006
  - ${names.join("\n- ")}`);
5574
6007
  }
5575
- /** The original dispatcher has a listener for the event. */
5576
- assertListening(event) {
5577
- assert(this.hasListeners(event), `Event [${nameOf(event)}] does not have any listeners.`);
6008
+ /**
6009
+ * Laravel's assertListening(event, listener): the original dispatcher has that listener for the event (a class, a
6010
+ * [class, method] pair, the same closure, or Function for any closure; wildcard listeners count for a string event).
6011
+ * With only the event, any listener will do.
6012
+ */
6013
+ assertListening(event, listener) {
6014
+ if (listener === void 0) {
6015
+ assert(this.hasListeners(event), `Event [${nameOf(event)}] does not have any listeners.`);
6016
+ return;
6017
+ }
6018
+ assert(
6019
+ this.listenersFor(event).some((registered) => listenerMatches(registered, listener)),
6020
+ `Event [${nameOf(event)}] does not have the [${listenerName(listener)}] listener attached to it`
6021
+ );
5578
6022
  }
5579
6023
  /** The recorded events (objects, or names for string events) that match. */
5580
6024
  dispatched(event, callback) {
@@ -5583,6 +6027,17 @@ var EventFake = class _EventFake {
5583
6027
  hasDispatched(event) {
5584
6028
  return this.dispatched(event).length > 0;
5585
6029
  }
6030
+ /** The original dispatcher's listeners for the event, as registered, wildcard ones included for a string event. */
6031
+ listenersFor(event) {
6032
+ const raw = this.dispatcher?.getRawListeners?.();
6033
+ if (raw === void 0) return [];
6034
+ const listeners = [...raw.get(event) ?? []];
6035
+ if (typeof event !== "string") return listeners;
6036
+ for (const [key, list] of raw) {
6037
+ if (typeof key === "string" && key !== event && key.includes("*") && Str.is(key, event)) listeners.push(...list);
6038
+ }
6039
+ return listeners;
6040
+ }
5586
6041
  shouldFake(event) {
5587
6042
  if (matchesAny(event, this.eventsToDispatch)) return false;
5588
6043
  return this.eventsToFake.length === 0 || matchesAny(event, this.eventsToFake);
@@ -5636,10 +6091,10 @@ var BusFake = class _BusFake {
5636
6091
  /** The rest of the chain, by the first job (Bus.chain([a, b, c]) records a with [b, c]). */
5637
6092
  chains = /* @__PURE__ */ new Map();
5638
6093
  static fake(jobsToFake = []) {
5639
- return install(BusDispatcher, new _BusFake(_BusFake.original(), [jobsToFake].flat()));
6094
+ return install2(BusDispatcher, new _BusFake(_BusFake.original(), [jobsToFake].flat()));
5640
6095
  }
5641
6096
  static fakeExcept(jobsToDispatch) {
5642
- return install(BusDispatcher, new _BusFake(_BusFake.original(), [], [jobsToDispatch].flat()));
6097
+ return install2(BusDispatcher, new _BusFake(_BusFake.original(), [], [jobsToDispatch].flat()));
5643
6098
  }
5644
6099
  static original() {
5645
6100
  const current = bound(BusDispatcher);
@@ -5844,10 +6299,10 @@ var QueueFake = class _QueueFake {
5844
6299
  jobsToPush;
5845
6300
  jobs = [];
5846
6301
  static fake(jobsToFake = []) {
5847
- return install(QueueManager, new _QueueFake(_QueueFake.original(), [jobsToFake].flat()));
6302
+ return install2(QueueManager, new _QueueFake(_QueueFake.original(), [jobsToFake].flat()));
5848
6303
  }
5849
6304
  static fakeExcept(jobsToPush) {
5850
- return install(QueueManager, new _QueueFake(_QueueFake.original(), [], [jobsToPush].flat()));
6305
+ return install2(QueueManager, new _QueueFake(_QueueFake.original(), [], [jobsToPush].flat()));
5851
6306
  }
5852
6307
  static original() {
5853
6308
  const current = bound(QueueManager);
@@ -6026,13 +6481,14 @@ var MailFake = class _MailFake {
6026
6481
  records = [];
6027
6482
  static fake() {
6028
6483
  const current = bound(MailManager);
6029
- return install(MailManager, new _MailFake(current instanceof _MailFake ? current.manager : current));
6484
+ return install2(MailManager, new _MailFake(current instanceof _MailFake ? current.manager : current));
6030
6485
  }
6031
6486
  // MailManager ----------------------------------------------------------------------------------------------
6032
6487
  mailer(name) {
6033
6488
  return {
6034
6489
  to: (recipients) => this.pending(name, recipients),
6035
6490
  send: (mailable) => this.sendVia(name, mailable, empty()),
6491
+ sendNow: (mailable) => this.sendNowVia(name, mailable, empty()),
6036
6492
  queue: (mailable) => this.queueVia(name, mailable, empty(), void 0)
6037
6493
  };
6038
6494
  }
@@ -6042,6 +6498,10 @@ var MailFake = class _MailFake {
6042
6498
  send(mailable) {
6043
6499
  return this.sendVia(void 0, mailable, empty());
6044
6500
  }
6501
+ /** Recorded as sent, never queued, like the real sendNow(). */
6502
+ sendNow(mailable) {
6503
+ return this.sendNowVia(void 0, mailable, empty());
6504
+ }
6045
6505
  queue(mailable) {
6046
6506
  return this.queueVia(void 0, mailable, empty(), void 0);
6047
6507
  }
@@ -6125,6 +6585,7 @@ var MailFake = class _MailFake {
6125
6585
  return pending;
6126
6586
  },
6127
6587
  send: (mailable) => this.sendVia(mailer, mailable, overrides),
6588
+ sendNow: (mailable) => this.sendNowVia(mailer, mailable, overrides),
6128
6589
  queue: (mailable) => this.queueVia(mailer, mailable, overrides, void 0),
6129
6590
  later: (delay, mailable) => this.queueVia(mailer, mailable, overrides, delay)
6130
6591
  };
@@ -6134,6 +6595,10 @@ var MailFake = class _MailFake {
6134
6595
  await this.record(mailer, mailable, overrides, isQueueable(mailable), void 0);
6135
6596
  return void 0;
6136
6597
  }
6598
+ async sendNowVia(mailer, mailable, overrides) {
6599
+ await this.record(mailer, mailable, overrides, false, void 0);
6600
+ return void 0;
6601
+ }
6137
6602
  async queueVia(mailer, mailable, overrides, delay) {
6138
6603
  await this.record(mailer, mailable, overrides, true, delay);
6139
6604
  }
@@ -6204,7 +6669,7 @@ var NotificationFake = class _NotificationFake {
6204
6669
  notifications = [];
6205
6670
  static fake() {
6206
6671
  const current = bound(NotificationDispatcher);
6207
- return install(NotificationDispatcher, new _NotificationFake(current instanceof _NotificationFake ? current.dispatcher : current));
6672
+ return install2(NotificationDispatcher, new _NotificationFake(current instanceof _NotificationFake ? current.dispatcher : current));
6208
6673
  }
6209
6674
  // NotificationDispatcher -------------------------------------------------------------------------------------
6210
6675
  async send(notifiables, notification) {
@@ -6313,8 +6778,74 @@ var MIME = {
6313
6778
  mp4: "video/mp4"
6314
6779
  };
6315
6780
  function guessMimeType(path) {
6316
- const extension = path.split(".").pop()?.toLowerCase() ?? "";
6317
- return MIME[extension];
6781
+ const base = path.slice(path.lastIndexOf("/") + 1);
6782
+ const dot = base.lastIndexOf(".");
6783
+ const extension = dot === -1 ? "" : base.slice(dot + 1).toLowerCase();
6784
+ return Object.hasOwn(MIME, extension) ? MIME[extension] : void 0;
6785
+ }
6786
+ var DISK_TYPES = {
6787
+ txt: "text/plain",
6788
+ html: "text/html",
6789
+ htm: "text/html",
6790
+ css: "text/css",
6791
+ csv: "text/csv",
6792
+ md: "text/markdown",
6793
+ js: "text/javascript",
6794
+ mjs: "text/javascript",
6795
+ json: "application/json",
6796
+ xml: "application/xml",
6797
+ pdf: "application/pdf",
6798
+ zip: "application/zip",
6799
+ gz: "application/gzip",
6800
+ tar: "application/x-tar",
6801
+ "7z": "application/x-7z-compressed",
6802
+ rar: "application/vnd.rar",
6803
+ epub: "application/epub+zip",
6804
+ rtf: "application/rtf",
6805
+ doc: "application/msword",
6806
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
6807
+ xls: "application/vnd.ms-excel",
6808
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
6809
+ ppt: "application/vnd.ms-powerpoint",
6810
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
6811
+ odt: "application/vnd.oasis.opendocument.text",
6812
+ ods: "application/vnd.oasis.opendocument.spreadsheet",
6813
+ odp: "application/vnd.oasis.opendocument.presentation",
6814
+ png: "image/png",
6815
+ jpg: "image/jpeg",
6816
+ jpeg: "image/jpeg",
6817
+ gif: "image/gif",
6818
+ webp: "image/webp",
6819
+ avif: "image/avif",
6820
+ svg: "image/svg+xml",
6821
+ ico: "image/x-icon",
6822
+ bmp: "image/bmp",
6823
+ tif: "image/tiff",
6824
+ tiff: "image/tiff",
6825
+ heic: "image/heic",
6826
+ heif: "image/heif",
6827
+ mp3: "audio/mpeg",
6828
+ m4a: "audio/mp4",
6829
+ wav: "audio/wav",
6830
+ ogg: "audio/ogg",
6831
+ oga: "audio/ogg",
6832
+ opus: "audio/ogg",
6833
+ flac: "audio/flac",
6834
+ aac: "audio/aac",
6835
+ mp4: "video/mp4",
6836
+ mov: "video/quicktime",
6837
+ webm: "video/webm",
6838
+ ogv: "video/ogg",
6839
+ avi: "video/x-msvideo",
6840
+ woff: "font/woff",
6841
+ woff2: "font/woff2",
6842
+ ttf: "font/ttf",
6843
+ otf: "font/otf",
6844
+ wasm: "application/wasm"
6845
+ };
6846
+ function diskType(extension) {
6847
+ const key = extension.replace(/^\./, "").toLowerCase();
6848
+ return Object.hasOwn(DISK_TYPES, key) ? DISK_TYPES[key] : void 0;
6318
6849
  }
6319
6850
  function hashName(file) {
6320
6851
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -6370,17 +6901,24 @@ var FakeDisk = class {
6370
6901
  async put(path, contents, options = {}) {
6371
6902
  const key = normalize(path);
6372
6903
  const bytes = typeof contents === "string" ? new TextEncoder().encode(contents) : contents instanceof Uint8Array ? contents.slice() : await collect(contents);
6373
- this.stored.set(key, { bytes, visibility: options.visibility ?? "private", mimeType: options.mimeType ?? guessMimeType(key), modified: /* @__PURE__ */ new Date() });
6904
+ this.write(key, bytes, options.mimeType ?? diskType(posix.extname(key)), options);
6374
6905
  return true;
6375
6906
  }
6376
6907
  putFile(directory, file, options = {}) {
6377
6908
  return this.putFileAs(directory, file, hashName(file), options);
6378
6909
  }
6910
+ /**
6911
+ * The upload's own type, as the S3 disk sends it: its extension(), never the MIME type the client sent as such nor
6912
+ * the stored name's type.
6913
+ */
6379
6914
  async putFileAs(directory, file, name, options = {}) {
6380
6915
  const path = normalize(`${directory}/${name}`);
6381
- await this.put(path, new Uint8Array(await file.arrayBuffer()), { ...options, mimeType: options.mimeType ?? file.mimeType });
6916
+ this.write(path, new Uint8Array(await file.arrayBuffer()), options.mimeType ?? diskType(file.extension()), options);
6382
6917
  return path;
6383
6918
  }
6919
+ write(key, bytes, mimeType, options) {
6920
+ this.stored.set(key, { bytes, visibility: options.visibility ?? "private", mimeType, modified: /* @__PURE__ */ new Date() });
6921
+ }
6384
6922
  async delete(paths) {
6385
6923
  for (const path of [paths].flat()) this.stored.delete(normalize(path));
6386
6924
  return true;
@@ -6494,7 +7032,7 @@ var StorageFake = class _StorageFake {
6494
7032
  static fake(disk) {
6495
7033
  const current = bound(FilesystemManager);
6496
7034
  const config = bound(ConfigRepository);
6497
- const fake = current instanceof _StorageFake ? current : install(FilesystemManager, new _StorageFake(current, config?.get("filesystems.default", "local") ?? "local"));
7035
+ const fake = current instanceof _StorageFake ? current : install2(FilesystemManager, new _StorageFake(current, config?.get("filesystems.default", "local") ?? "local"));
6498
7036
  const name = disk ?? fake.defaultDisk;
6499
7037
  const faked = new FakeDisk(name);
6500
7038
  fake.disks.set(name, faked);
@@ -6574,6 +7112,37 @@ var StorageFake = class _StorageFake {
6574
7112
  return this.disk().path(path);
6575
7113
  }
6576
7114
  };
7115
+ var ascii = (text) => [...text].map((char) => char.charCodeAt(0));
7116
+ var u16be = (n) => [n >>> 8 & 255, n & 255];
7117
+ var u16le = (n) => [n & 255, n >>> 8 & 255];
7118
+ var u32le = (n) => [n & 255, n >>> 8 & 255, n >>> 16 & 255, n >>> 24 & 255];
7119
+ function concat(...parts) {
7120
+ const out = new Uint8Array(parts.reduce((n, part) => n + part.length, 0));
7121
+ let offset = 0;
7122
+ for (const part of parts) {
7123
+ out.set(part, offset);
7124
+ offset += part.length;
7125
+ }
7126
+ return out;
7127
+ }
7128
+ var BitWriter = class {
7129
+ bytes = [];
7130
+ buffer = 0;
7131
+ count = 0;
7132
+ write(value, bits) {
7133
+ this.buffer |= value << this.count;
7134
+ this.count += bits;
7135
+ while (this.count >= 8) {
7136
+ this.bytes.push(this.buffer & 255);
7137
+ this.buffer >>>= 8;
7138
+ this.count -= 8;
7139
+ }
7140
+ }
7141
+ finish() {
7142
+ if (this.count > 0) this.bytes.push(this.buffer & 255);
7143
+ return Uint8Array.from(this.bytes);
7144
+ }
7145
+ };
6577
7146
  var CRC_TABLE = Array.from({ length: 256 }, (_, n) => {
6578
7147
  let c = n;
6579
7148
  for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
@@ -6601,23 +7170,175 @@ function png(width, height) {
6601
7170
  header.set([8, 2, 0, 0, 0], 8);
6602
7171
  const rows = new Uint8Array((width * 3 + 1) * height).fill(255);
6603
7172
  for (let y = 0; y < height; y++) rows[y * (width * 3 + 1)] = 0;
6604
- const parts = [new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]), chunk("IHDR", header), chunk("IDAT", deflateSync(rows)), chunk("IEND", new Uint8Array())];
6605
- const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
6606
- let offset = 0;
6607
- for (const part of parts) {
6608
- out.set(part, offset);
6609
- offset += part.length;
6610
- }
7173
+ return concat([137, 80, 78, 71, 13, 10, 26, 10], chunk("IHDR", header), chunk("IDAT", deflateSync(rows)), chunk("IEND", new Uint8Array()));
7174
+ }
7175
+ var oneCodeTable = (tableClass) => [255, 196, ...u16be(20), tableClass, 1, ...new Array(15).fill(0), 0];
7176
+ function jpeg(width, height) {
7177
+ const bits = Math.ceil(width / 8) * Math.ceil(height / 8) * 2;
7178
+ const scan = new Uint8Array(Math.ceil(bits / 8));
7179
+ const padding = scan.length * 8 - bits;
7180
+ if (padding > 0) scan[scan.length - 1] = (1 << padding) - 1;
7181
+ return concat(
7182
+ [255, 216],
7183
+ // SOI
7184
+ [255, 224, ...u16be(16), ...ascii("JFIF"), 0, 1, 1, 0, 0, 1, 0, 1, 0, 0],
7185
+ // APP0
7186
+ [255, 219, ...u16be(67), 0, ...new Array(64).fill(1)],
7187
+ // DQT: table 0, all ones
7188
+ [255, 192, ...u16be(11), 8, ...u16be(height), ...u16be(width), 1, 1, 17, 0],
7189
+ // SOF0: one component
7190
+ oneCodeTable(0),
7191
+ // DHT: DC table 0
7192
+ oneCodeTable(16),
7193
+ // DHT: AC table 0
7194
+ [255, 218, ...u16be(8), 1, 1, 0, 0, 63, 0],
7195
+ // SOS
7196
+ scan,
7197
+ [255, 217]
7198
+ // EOI
7199
+ );
7200
+ }
7201
+ function lzwZeros(pixels, minCodeSize) {
7202
+ const clear = 1 << minCodeSize;
7203
+ const end = clear + 1;
7204
+ const out = new BitWriter();
7205
+ const table = /* @__PURE__ */ new Map();
7206
+ let codeSize = minCodeSize + 1;
7207
+ let next = end + 1;
7208
+ let current = 0;
7209
+ out.write(clear, codeSize);
7210
+ for (let i = 1; i < pixels; i++) {
7211
+ const key = current << 8;
7212
+ const known = table.get(key);
7213
+ if (known !== void 0) {
7214
+ current = known;
7215
+ continue;
7216
+ }
7217
+ out.write(current, codeSize);
7218
+ if (next === 4096) {
7219
+ out.write(clear, codeSize);
7220
+ table.clear();
7221
+ codeSize = minCodeSize + 1;
7222
+ next = end + 1;
7223
+ } else {
7224
+ if (next >= 1 << codeSize) codeSize++;
7225
+ table.set(key, next++);
7226
+ }
7227
+ current = 0;
7228
+ }
7229
+ out.write(current, codeSize);
7230
+ out.write(end, codeSize);
7231
+ return out.finish();
7232
+ }
7233
+ function gif(width, height) {
7234
+ const data = lzwZeros(width * height, 2);
7235
+ const blocks = [];
7236
+ for (let offset = 0; offset < data.length; offset += 255) {
7237
+ const block = data.subarray(offset, offset + 255);
7238
+ blocks.push(block.length, ...block);
7239
+ }
7240
+ return concat(
7241
+ ascii("GIF89a"),
7242
+ [...u16le(width), ...u16le(height), 128, 0, 0],
7243
+ // a global palette of two colours
7244
+ [255, 255, 255, 0, 0, 0],
7245
+ [44, 0, 0, 0, 0, ...u16le(width), ...u16le(height), 0],
7246
+ // the image descriptor
7247
+ [2],
7248
+ blocks,
7249
+ [0, 59]
7250
+ // the end of the data, the trailer
7251
+ );
7252
+ }
7253
+ function webp(width, height) {
7254
+ const bits = new BitWriter();
7255
+ bits.write(47, 8);
7256
+ bits.write(width - 1, 14);
7257
+ bits.write(height - 1, 14);
7258
+ bits.write(0, 4);
7259
+ bits.write(0, 3);
7260
+ for (const symbol of [255, 255, 255, 255, 0]) {
7261
+ bits.write(1, 1);
7262
+ bits.write(0, 1);
7263
+ bits.write(1, 1);
7264
+ bits.write(symbol, 8);
7265
+ }
7266
+ const data = bits.finish();
7267
+ const padded = data.length % 2 === 0 ? data : concat(data, [0]);
7268
+ return concat(ascii("RIFF"), u32le(12 + padded.length), ascii("WEBP"), ascii("VP8L"), u32le(data.length), padded);
7269
+ }
7270
+ function bmp(width, height) {
7271
+ const row = Math.ceil(width * 3 / 4) * 4;
7272
+ const out = new Uint8Array(54 + row * height);
7273
+ const view = new DataView(out.buffer);
7274
+ out.set(ascii("BM"));
7275
+ view.setUint32(2, out.length, true);
7276
+ view.setUint32(10, 54, true);
7277
+ view.setUint32(14, 40, true);
7278
+ view.setInt32(18, width, true);
7279
+ view.setInt32(22, height, true);
7280
+ view.setUint16(26, 1, true);
7281
+ view.setUint16(28, 24, true);
7282
+ view.setUint32(34, row * height, true);
7283
+ for (let y = 0; y < height; y++) out.fill(255, 54 + y * row, 54 + y * row + width * 3);
7284
+ return out;
7285
+ }
7286
+ var IMAGE_FORMATS = {
7287
+ png: { type: "image/png", encode: png },
7288
+ jpg: { type: "image/jpeg", encode: jpeg },
7289
+ jpeg: { type: "image/jpeg", encode: jpeg },
7290
+ gif: { type: "image/gif", encode: gif },
7291
+ webp: { type: "image/webp", encode: webp },
7292
+ bmp: { type: "image/bmp", encode: bmp }
7293
+ };
7294
+ var extensionOf = (name) => name.includes(".") ? name.slice(name.lastIndexOf(".") + 1).toLowerCase() : "";
7295
+ var MORE_MIME_TYPES = {
7296
+ bmp: "image/bmp",
7297
+ avif: "image/avif",
7298
+ xml: "text/xml",
7299
+ htm: "text/html",
7300
+ md: "text/markdown",
7301
+ tsv: "text/tab-separated-values",
7302
+ text: "text/plain"
7303
+ };
7304
+ var LEADING_BYTES = {
7305
+ "image/jpeg": { bytes: [255, 216, 255, 224, ...u16be(16), ...ascii("JFIF"), 0, 1, 1, 0, 0, 1, 0, 1, 0, 0] },
7306
+ "image/png": { bytes: [137, 80, 78, 71, 13, 10, 26, 10] },
7307
+ "image/gif": { bytes: "GIF89a" },
7308
+ "image/webp": { bytes: "RIFF\0\0\0\0WEBP" },
7309
+ "image/avif": { bytes: [0, 0, 0, 20, ...ascii("ftypavif"), 0, 0, 0, 0, ...ascii("avifmif1")] },
7310
+ "image/bmp": { bytes: [...ascii("BM"), 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 40, 0, 0, 0] },
7311
+ "application/pdf": { bytes: "%PDF-1.4\n" },
7312
+ "image/svg+xml": { bytes: '<svg xmlns="http://www.w3.org/2000/svg"/>\n', text: true },
7313
+ "text/html": { bytes: "<!doctype html>\n", text: true },
7314
+ "text/xml": { bytes: '<?xml version="1.0"?>\n', text: true }
7315
+ };
7316
+ function contentFor(type, size) {
7317
+ const text = type.startsWith("text/") || type === "application/json";
7318
+ const leading = LEADING_BYTES[type] ?? { bytes: text ? "\n" : [], text };
7319
+ const head = typeof leading.bytes === "string" ? new TextEncoder().encode(leading.bytes) : Uint8Array.from(leading.bytes);
7320
+ const out = new Uint8Array(Math.max(size, head.length, 1)).fill(leading.text ? 32 : 0);
7321
+ out.set(head);
6611
7322
  return out;
6612
7323
  }
6613
7324
  var FakeFile = {
6614
- /** A file of `kilobytes` KiB (zero bytes), typed by `mimeType` or the extension. */
7325
+ /**
7326
+ * A file of `kilobytes` KiB typed by `mimeType` or the name's extension. Validation reads a file's bytes, so the
7327
+ * file starts like its type (a PDF's `%PDF-`, a PNG's signature, text for text types) and passes `mimes` for its
7328
+ * name, as Laravel's fakes do. It is at least its type's leading bytes and never empty, whatever the size asked for.
7329
+ * An image made this way has no readable size: use image() for `dimensions`.
7330
+ */
6615
7331
  create(name, kilobytes = 0, mimeType) {
6616
- return new File([new Uint8Array(Math.round(kilobytes * 1024))], name, { type: mimeType ?? guessMimeType(name) ?? "application/octet-stream" });
7332
+ const type = mimeType ?? guessMimeType(name) ?? MORE_MIME_TYPES[extensionOf(name)] ?? "application/octet-stream";
7333
+ return new File([contentFor(type, Math.round(kilobytes * 1024))], name, { type });
6617
7334
  },
6618
- /** A real PNG image of the given size (typed by the name's extension, like Laravel's image()). */
7335
+ /**
7336
+ * A real white image of the given size in the format the name's extension asks for: png, jpg/jpeg, gif, webp or
7337
+ * bmp (any other name gets a JPEG, as Laravel's fakes do), so image, mimes and dimensions pass on it.
7338
+ */
6619
7339
  image(name, width = 10, height = 10) {
6620
- return new File([png(width, height)], name, { type: guessMimeType(name) ?? "image/png" });
7340
+ const format2 = IMAGE_FORMATS[extensionOf(name)] ?? IMAGE_FORMATS.jpeg;
7341
+ return new File([format2.encode(width, height)], name, { type: guessMimeType(name) ?? format2.type });
6621
7342
  }
6622
7343
  };
6623
7344
 
@@ -6699,18 +7420,18 @@ export {
6699
7420
  addApiRouting,
6700
7421
  registerHttpCommands,
6701
7422
  HttpServiceProvider,
6702
- On,
6703
- Locked,
6704
- Computed,
6705
- ErrorBag,
6706
- PendingEvent,
6707
- Component,
6708
7423
  LiveError,
6709
7424
  CorruptComponentPayloadError,
6710
7425
  MethodNotCallableError,
6711
7426
  PropertyNotUpdatableError,
6712
7427
  LiveComponentNotFoundError,
6713
7428
  InvalidLiveRequestError,
7429
+ On,
7430
+ Locked,
7431
+ Computed,
7432
+ ErrorBag,
7433
+ PendingEvent,
7434
+ Component,
6714
7435
  TestRequestScope,
6715
7436
  Testable,
6716
7437
  live,
@@ -6718,9 +7439,17 @@ export {
6718
7439
  componentName,
6719
7440
  PERSISTENT_MIDDLEWARE,
6720
7441
  persistentMiddleware,
7442
+ WithPagination,
6721
7443
  LiveComponentManager,
6722
7444
  UPDATE_URI,
6723
7445
  LiveServiceProvider,
7446
+ Pagination,
7447
+ travelTo,
7448
+ travelBack,
7449
+ freezeTime,
7450
+ freezeSecond,
7451
+ travel,
7452
+ Wormhole,
6724
7453
  PendingCommand,
6725
7454
  RefreshDatabaseState,
6726
7455
  runCommand,
@@ -6749,8 +7478,9 @@ export {
6749
7478
  MakesHttpRequests,
6750
7479
  InteractsWithDatabase,
6751
7480
  InteractsWithConsole,
7481
+ InteractsWithTime,
6752
7482
  TestCase,
6753
- install,
7483
+ install2,
6754
7484
  nameOf,
6755
7485
  matches,
6756
7486
  EventFake,
@@ -6766,4 +7496,4 @@ export {
6766
7496
  StorageFake,
6767
7497
  FakeFile
6768
7498
  };
6769
- //# sourceMappingURL=chunk-SBZQ6L3Z.js.map
7499
+ //# sourceMappingURL=chunk-ZOEPKBVF.js.map