@orieljs/framework 0.1.0-alpha.5 → 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.
@@ -10,7 +10,7 @@ import {
10
10
  facade,
11
11
  fileType_exports,
12
12
  inject
13
- } from "./chunk-PM5YTG57.js";
13
+ } from "./chunk-POEP4B2M.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-I63ZFMX6.js";
49
49
 
50
50
  // ../Config/dist/index.js
51
51
  import { existsSync, readFileSync } from "fs";
@@ -492,7 +492,7 @@ var Application2 = class _Application extends Container {
492
492
  this.scoped(Vite, () => new Vite(this));
493
493
  }
494
494
  base;
495
- version = "0.1.0-alpha.5";
495
+ version = "0.1.0-alpha.6";
496
496
  providers = [];
497
497
  bootingCallbacks = [];
498
498
  bootedCallbacks = [];
@@ -1290,9 +1290,14 @@ var IncomingRequest = class {
1290
1290
  filled(key) {
1291
1291
  return !isBlank(Arr.get(this.all(), key));
1292
1292
  }
1293
+ /** Laravel's string(): the input as a Stringable (the default when it is missing or null): request().string('q').trim(). */
1293
1294
  string(key, defaultValue = "") {
1294
1295
  const value = this.input(key);
1295
- 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);
1296
1301
  }
1297
1302
  /** PHP's FILTER_VALIDATE_BOOLEAN: '1', 'true', 'on', 'yes' (any case) and true are true. */
1298
1303
  boolean(key, defaultValue = false) {
@@ -2510,6 +2515,48 @@ import { AsyncLocalStorage } from "async_hooks";
2510
2515
  import { existsSync as existsSync22 } from "fs";
2511
2516
  import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
2512
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
+ };
2513
2560
  Symbol.metadata ??= /* @__PURE__ */ Symbol.for("Symbol.metadata");
2514
2561
  var LISTENERS = /* @__PURE__ */ Symbol.for("oriel:live.listeners");
2515
2562
  var LOCKED = /* @__PURE__ */ Symbol.for("oriel:live.locked");
@@ -2581,6 +2628,145 @@ function lockedOf(cls) {
2581
2628
  function computedOf(cls) {
2582
2629
  return /* @__PURE__ */ new Set([...metadataOf(cls)[COMPUTED] ?? [], ...staticsOf(cls, "computed").flat()]);
2583
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
+ }
2584
2770
  var COMPONENT = /* @__PURE__ */ Symbol.for("oriel:live.component");
2585
2771
  var states = /* @__PURE__ */ new WeakMap();
2586
2772
  function stateOf(component) {
@@ -2711,6 +2897,23 @@ var Component = class {
2711
2897
  skipRender() {
2712
2898
  stateOf(this).skipRender = true;
2713
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
+ }
2714
2917
  /** A full-page component as a route action: Route.get('/counter', Counter). Route parameters go to mount(). */
2715
2918
  async __invoke() {
2716
2919
  return inject(LiveManager).page(this);
@@ -2730,52 +2933,13 @@ function trackFlash(session, { saved = true } = {}) {
2730
2933
  session.put("_flash.old", [...old, ...fresh.filter((key) => !old.includes(key))]);
2731
2934
  };
2732
2935
  }
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
2936
  var ESCAPES = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#039;" };
2776
2937
  function escapeAttribute2(value) {
2777
2938
  return value.replace(/[&<>"']/g, (char) => ESCAPES[char]);
2778
2939
  }
2940
+ function stripTags(html) {
2941
+ return html.replace(/<[^>]*>/g, "");
2942
+ }
2779
2943
  var VOID = /* @__PURE__ */ new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr"]);
2780
2944
  var RAW_TEXT = /* @__PURE__ */ new Set(["script", "style", "textarea", "title"]);
2781
2945
  var TOKEN = /<\/([A-Za-z][\w:-]*)\s*>|<([A-Za-z][\w:-]*)(?:\s+[^\s"'<>/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*\s*(\/?)>/g;
@@ -2928,6 +3092,17 @@ var Testable = class _Testable {
2928
3092
  for (const value of values) if (!this.html().includes(value)) fail(`Failed asserting that [${value}] is in the rendered HTML.`, this.html(), value);
2929
3093
  return this;
2930
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
+ }
2931
3106
  assertDispatched(name, params) {
2932
3107
  const dispatched = (this.#effects.dispatches ?? []).filter((event) => event.name === name);
2933
3108
  if (dispatched.length === 0) fail(`Failed asserting that an event [${name}] was dispatched.`, this.#effects.dispatches ?? [], name);
@@ -3111,7 +3286,7 @@ function currentRoute(container) {
3111
3286
  }
3112
3287
  }
3113
3288
  async function throughMiddleware(container, request2, pass, destination) {
3114
- const original = currentRoute(container);
3289
+ const original2 = currentRoute(container);
3115
3290
  const page = { route: pass.route, parameters: { ...pass.values }, middleware: [] };
3116
3291
  let swapped = false;
3117
3292
  const show = () => {
@@ -3120,8 +3295,8 @@ async function throughMiddleware(container, request2, pass, destination) {
3120
3295
  };
3121
3296
  const restore = () => {
3122
3297
  if (!swapped) return;
3123
- if (original === void 0) container.forgetInstance(CurrentRoute);
3124
- else container.instance(CurrentRoute, original);
3298
+ if (original2 === void 0) container.forgetInstance(CurrentRoute);
3299
+ else container.instance(CurrentRoute, original2);
3125
3300
  swapped = false;
3126
3301
  };
3127
3302
  let ran;
@@ -3145,144 +3320,6 @@ async function throughMiddleware(container, request2, pass, destination) {
3145
3320
  restore();
3146
3321
  }
3147
3322
  }
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
3323
  var PAGINATED = /* @__PURE__ */ Symbol.for("oriel:live.pagination");
3287
3324
  function toPage(value) {
3288
3325
  const page = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : Number.NaN;
@@ -3810,6 +3847,129 @@ import { randomUUID as randomUUID3 } from "crypto";
3810
3847
  import { randomBytes as randomBytes3 } from "crypto";
3811
3848
  import { posix } from "path";
3812
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
+ };
3813
3973
  function fail2(message, details = {}) {
3814
3974
  const error = new AssertionError2({ message, actual: details.actual, expected: details.expected, operator: "fail" });
3815
3975
  delete error.diff;
@@ -3831,7 +3991,7 @@ function format(value) {
3831
3991
  function escapeHtml2(value) {
3832
3992
  return value.replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#039;" })[char]);
3833
3993
  }
3834
- function stripTags(html) {
3994
+ function stripTags2(html) {
3835
3995
  return html.replace(/<[^>]*>/g, "");
3836
3996
  }
3837
3997
  function equals(a, b) {
@@ -4732,7 +4892,7 @@ ${this.excerpt()}`);
4732
4892
  }
4733
4893
  /** Like assertSee, against the content with its tags stripped. */
4734
4894
  assertSeeText(value, escape = true) {
4735
- const text = stripTags(this.content);
4895
+ const text = stripTags2(this.content);
4736
4896
  for (const needle of this.needles(value, escape)) {
4737
4897
  assert(text.includes(needle), `Failed asserting that the response text contains [${needle}].
4738
4898
 
@@ -4741,14 +4901,14 @@ ${this.excerpt(text)}`);
4741
4901
  return this;
4742
4902
  }
4743
4903
  assertDontSeeText(value, escape = true) {
4744
- const text = stripTags(this.content);
4904
+ const text = stripTags2(this.content);
4745
4905
  for (const needle of this.needles(value, escape)) {
4746
4906
  assert(!text.includes(needle), `Failed asserting that the response text does not contain [${needle}].`);
4747
4907
  }
4748
4908
  return this;
4749
4909
  }
4750
4910
  assertSeeTextInOrder(values, escape = true) {
4751
- return this.inOrder(stripTags(this.content), this.needles(values, escape));
4911
+ return this.inOrder(stripTags2(this.content), this.needles(values, escape));
4752
4912
  }
4753
4913
  // JSON --------------------------------------------------------------------------------------------------
4754
4914
  /**
@@ -5696,9 +5856,33 @@ var InteractsWithConsole = class extends InteractsWithDatabase {
5696
5856
  return new PendingCommand(this.app, command, parameters);
5697
5857
  }
5698
5858
  };
5699
- 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 {
5700
5884
  };
5701
- function install(token2, fake) {
5885
+ function install2(token2, fake) {
5702
5886
  facade(token2).swap(fake);
5703
5887
  return fake;
5704
5888
  }
@@ -5731,6 +5915,23 @@ function isQueueable(job) {
5731
5915
  if (QUEUE_OPTIONS.some((option) => members[option] !== void 0)) return true;
5732
5916
  return typeof members.uniqueId === "function" || typeof members.retryUntil === "function";
5733
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
+ }
5734
5935
  var EventFake = class _EventFake {
5735
5936
  constructor(dispatcher, eventsToFake = [], eventsToDispatch = []) {
5736
5937
  this.dispatcher = dispatcher;
@@ -5743,11 +5944,11 @@ var EventFake = class _EventFake {
5743
5944
  events = [];
5744
5945
  /** Installs the fake (on the current container) and returns it: `const events = EventFake.fake([OrderShipped])`. */
5745
5946
  static fake(eventsToFake = []) {
5746
- return install(EventDispatcher, new _EventFake(_EventFake.original(), [eventsToFake].flat()));
5947
+ return install2(EventDispatcher, new _EventFake(_EventFake.original(), [eventsToFake].flat()));
5747
5948
  }
5748
5949
  /** Fakes every event except these. */
5749
5950
  static fakeExcept(eventsToDispatch) {
5750
- return install(EventDispatcher, new _EventFake(_EventFake.original(), [], [eventsToDispatch].flat()));
5951
+ return install2(EventDispatcher, new _EventFake(_EventFake.original(), [], [eventsToDispatch].flat()));
5751
5952
  }
5752
5953
  static original() {
5753
5954
  const current = bound(EventDispatcher);
@@ -5804,9 +6005,20 @@ var EventFake = class _EventFake {
5804
6005
 
5805
6006
  - ${names.join("\n- ")}`);
5806
6007
  }
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.`);
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
+ );
5810
6022
  }
5811
6023
  /** The recorded events (objects, or names for string events) that match. */
5812
6024
  dispatched(event, callback) {
@@ -5815,6 +6027,17 @@ var EventFake = class _EventFake {
5815
6027
  hasDispatched(event) {
5816
6028
  return this.dispatched(event).length > 0;
5817
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
+ }
5818
6041
  shouldFake(event) {
5819
6042
  if (matchesAny(event, this.eventsToDispatch)) return false;
5820
6043
  return this.eventsToFake.length === 0 || matchesAny(event, this.eventsToFake);
@@ -5868,10 +6091,10 @@ var BusFake = class _BusFake {
5868
6091
  /** The rest of the chain, by the first job (Bus.chain([a, b, c]) records a with [b, c]). */
5869
6092
  chains = /* @__PURE__ */ new Map();
5870
6093
  static fake(jobsToFake = []) {
5871
- return install(BusDispatcher, new _BusFake(_BusFake.original(), [jobsToFake].flat()));
6094
+ return install2(BusDispatcher, new _BusFake(_BusFake.original(), [jobsToFake].flat()));
5872
6095
  }
5873
6096
  static fakeExcept(jobsToDispatch) {
5874
- return install(BusDispatcher, new _BusFake(_BusFake.original(), [], [jobsToDispatch].flat()));
6097
+ return install2(BusDispatcher, new _BusFake(_BusFake.original(), [], [jobsToDispatch].flat()));
5875
6098
  }
5876
6099
  static original() {
5877
6100
  const current = bound(BusDispatcher);
@@ -6076,10 +6299,10 @@ var QueueFake = class _QueueFake {
6076
6299
  jobsToPush;
6077
6300
  jobs = [];
6078
6301
  static fake(jobsToFake = []) {
6079
- return install(QueueManager, new _QueueFake(_QueueFake.original(), [jobsToFake].flat()));
6302
+ return install2(QueueManager, new _QueueFake(_QueueFake.original(), [jobsToFake].flat()));
6080
6303
  }
6081
6304
  static fakeExcept(jobsToPush) {
6082
- return install(QueueManager, new _QueueFake(_QueueFake.original(), [], [jobsToPush].flat()));
6305
+ return install2(QueueManager, new _QueueFake(_QueueFake.original(), [], [jobsToPush].flat()));
6083
6306
  }
6084
6307
  static original() {
6085
6308
  const current = bound(QueueManager);
@@ -6258,13 +6481,14 @@ var MailFake = class _MailFake {
6258
6481
  records = [];
6259
6482
  static fake() {
6260
6483
  const current = bound(MailManager);
6261
- return install(MailManager, new _MailFake(current instanceof _MailFake ? current.manager : current));
6484
+ return install2(MailManager, new _MailFake(current instanceof _MailFake ? current.manager : current));
6262
6485
  }
6263
6486
  // MailManager ----------------------------------------------------------------------------------------------
6264
6487
  mailer(name) {
6265
6488
  return {
6266
6489
  to: (recipients) => this.pending(name, recipients),
6267
6490
  send: (mailable) => this.sendVia(name, mailable, empty()),
6491
+ sendNow: (mailable) => this.sendNowVia(name, mailable, empty()),
6268
6492
  queue: (mailable) => this.queueVia(name, mailable, empty(), void 0)
6269
6493
  };
6270
6494
  }
@@ -6274,6 +6498,10 @@ var MailFake = class _MailFake {
6274
6498
  send(mailable) {
6275
6499
  return this.sendVia(void 0, mailable, empty());
6276
6500
  }
6501
+ /** Recorded as sent, never queued, like the real sendNow(). */
6502
+ sendNow(mailable) {
6503
+ return this.sendNowVia(void 0, mailable, empty());
6504
+ }
6277
6505
  queue(mailable) {
6278
6506
  return this.queueVia(void 0, mailable, empty(), void 0);
6279
6507
  }
@@ -6357,6 +6585,7 @@ var MailFake = class _MailFake {
6357
6585
  return pending;
6358
6586
  },
6359
6587
  send: (mailable) => this.sendVia(mailer, mailable, overrides),
6588
+ sendNow: (mailable) => this.sendNowVia(mailer, mailable, overrides),
6360
6589
  queue: (mailable) => this.queueVia(mailer, mailable, overrides, void 0),
6361
6590
  later: (delay, mailable) => this.queueVia(mailer, mailable, overrides, delay)
6362
6591
  };
@@ -6366,6 +6595,10 @@ var MailFake = class _MailFake {
6366
6595
  await this.record(mailer, mailable, overrides, isQueueable(mailable), void 0);
6367
6596
  return void 0;
6368
6597
  }
6598
+ async sendNowVia(mailer, mailable, overrides) {
6599
+ await this.record(mailer, mailable, overrides, false, void 0);
6600
+ return void 0;
6601
+ }
6369
6602
  async queueVia(mailer, mailable, overrides, delay) {
6370
6603
  await this.record(mailer, mailable, overrides, true, delay);
6371
6604
  }
@@ -6436,7 +6669,7 @@ var NotificationFake = class _NotificationFake {
6436
6669
  notifications = [];
6437
6670
  static fake() {
6438
6671
  const current = bound(NotificationDispatcher);
6439
- return install(NotificationDispatcher, new _NotificationFake(current instanceof _NotificationFake ? current.dispatcher : current));
6672
+ return install2(NotificationDispatcher, new _NotificationFake(current instanceof _NotificationFake ? current.dispatcher : current));
6440
6673
  }
6441
6674
  // NotificationDispatcher -------------------------------------------------------------------------------------
6442
6675
  async send(notifiables, notification) {
@@ -6799,7 +7032,7 @@ var StorageFake = class _StorageFake {
6799
7032
  static fake(disk) {
6800
7033
  const current = bound(FilesystemManager);
6801
7034
  const config = bound(ConfigRepository);
6802
- 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"));
6803
7036
  const name = disk ?? fake.defaultDisk;
6804
7037
  const faked = new FakeDisk(name);
6805
7038
  fake.disks.set(name, faked);
@@ -7187,18 +7420,18 @@ export {
7187
7420
  addApiRouting,
7188
7421
  registerHttpCommands,
7189
7422
  HttpServiceProvider,
7190
- On,
7191
- Locked,
7192
- Computed,
7193
- ErrorBag,
7194
- PendingEvent,
7195
- Component,
7196
7423
  LiveError,
7197
7424
  CorruptComponentPayloadError,
7198
7425
  MethodNotCallableError,
7199
7426
  PropertyNotUpdatableError,
7200
7427
  LiveComponentNotFoundError,
7201
7428
  InvalidLiveRequestError,
7429
+ On,
7430
+ Locked,
7431
+ Computed,
7432
+ ErrorBag,
7433
+ PendingEvent,
7434
+ Component,
7202
7435
  TestRequestScope,
7203
7436
  Testable,
7204
7437
  live,
@@ -7211,6 +7444,12 @@ export {
7211
7444
  UPDATE_URI,
7212
7445
  LiveServiceProvider,
7213
7446
  Pagination,
7447
+ travelTo,
7448
+ travelBack,
7449
+ freezeTime,
7450
+ freezeSecond,
7451
+ travel,
7452
+ Wormhole,
7214
7453
  PendingCommand,
7215
7454
  RefreshDatabaseState,
7216
7455
  runCommand,
@@ -7239,8 +7478,9 @@ export {
7239
7478
  MakesHttpRequests,
7240
7479
  InteractsWithDatabase,
7241
7480
  InteractsWithConsole,
7481
+ InteractsWithTime,
7242
7482
  TestCase,
7243
- install,
7483
+ install2,
7244
7484
  nameOf,
7245
7485
  matches,
7246
7486
  EventFake,
@@ -7256,4 +7496,4 @@ export {
7256
7496
  StorageFake,
7257
7497
  FakeFile
7258
7498
  };
7259
- //# sourceMappingURL=chunk-WNJP46B7.js.map
7499
+ //# sourceMappingURL=chunk-ZOEPKBVF.js.map