@orieljs/framework 0.1.0-alpha.5 → 0.1.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +7 -0
- package/README.md +10 -1
- package/TRADEMARKS.md +33 -0
- package/dist/{chunk-WNJP46B7.js → chunk-4MWG3B7G.js} +1785 -626
- package/dist/chunk-4MWG3B7G.js.map +1 -0
- package/dist/chunk-7ZQZYDOC.js +3253 -0
- package/dist/chunk-7ZQZYDOC.js.map +1 -0
- package/dist/{chunk-BSQKOLSK.js → chunk-GVNLZZTN.js} +1 -1
- package/dist/chunk-GVNLZZTN.js.map +1 -0
- package/dist/{chunk-A5XRLEWM.js → chunk-VOYW3R5J.js} +4 -4
- package/dist/chunk-VOYW3R5J.js.map +1 -0
- package/dist/contracts.d.ts +535 -17
- package/dist/contracts.js +1 -1
- package/dist/index.d-WQpfdIbG.d.ts +630 -0
- package/dist/index.d.ts +2202 -5952
- package/dist/index.js +4900 -944
- package/dist/index.js.map +1 -1
- package/dist/{jsx-runtime-WKTl3EEw.d.ts → jsx-runtime-6um4Mwfb.d.ts} +97 -51
- package/dist/jsx-runtime.d.ts +2 -2
- package/dist/jsx-runtime.js +3 -3
- package/dist/{testing-BErengYY.d.ts → testing-CKwfWJGn.d.ts} +313 -994
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +19 -5
- package/docs/authentication.md +487 -0
- package/docs/authorization.md +435 -0
- package/docs/cache.md +647 -0
- package/docs/collections.md +2270 -0
- package/docs/configuration.md +334 -0
- package/docs/console.md +651 -0
- package/docs/container.md +430 -0
- package/docs/controllers.md +406 -0
- package/docs/csrf.md +126 -0
- package/docs/database-testing.md +144 -0
- package/docs/database.md +236 -0
- package/docs/deployment.md +276 -0
- package/docs/eloquent-collections.md +218 -0
- package/docs/eloquent-factories.md +255 -0
- package/docs/eloquent-mutators.md +254 -0
- package/docs/eloquent-observers.md +145 -0
- package/docs/eloquent-relationships.md +557 -0
- package/docs/eloquent.md +494 -0
- package/docs/encryption.md +104 -0
- package/docs/errors.md +318 -0
- package/docs/events.md +336 -0
- package/docs/facades.md +278 -0
- package/docs/hashing.md +118 -0
- package/docs/helpers.md +1281 -0
- package/docs/http-tests.md +386 -0
- package/docs/inertia.md +485 -0
- package/docs/installation.md +209 -0
- package/docs/lifecycle.md +234 -0
- package/docs/live-tests.md +226 -0
- package/docs/live.md +910 -0
- package/docs/logging.md +255 -0
- package/docs/mail.md +619 -0
- package/docs/middleware.md +384 -0
- package/docs/migrations.md +416 -0
- package/docs/mocking.md +264 -0
- package/docs/navigation.json +273 -0
- package/docs/notifications.md +225 -0
- package/docs/pagination.md +247 -0
- package/docs/passwords.md +207 -0
- package/docs/providers.md +187 -0
- package/docs/queries.md +600 -0
- package/docs/queues.md +330 -0
- package/docs/requests.md +533 -0
- package/docs/responses.md +444 -0
- package/docs/routing.md +738 -0
- package/docs/scheduling.md +277 -0
- package/docs/seeding.md +122 -0
- package/docs/session.md +409 -0
- package/docs/starter-kits.md +396 -0
- package/docs/storage.md +192 -0
- package/docs/structure.md +264 -0
- package/docs/testing.md +165 -0
- package/docs/time-travel.md +138 -0
- package/docs/validation.md +1792 -0
- package/docs/verification.md +152 -0
- package/docs/views.md +566 -0
- package/docs/vite.md +322 -0
- package/package.json +34 -32
- package/dist/chunk-A5XRLEWM.js.map +0 -1
- package/dist/chunk-BSQKOLSK.js.map +0 -1
- package/dist/chunk-PM5YTG57.js +0 -880
- package/dist/chunk-PM5YTG57.js.map +0 -1
- package/dist/chunk-WNJP46B7.js.map +0 -1
- package/dist/index.d-DOlTeG-8.d.ts +0 -99
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
facade,
|
|
11
11
|
fileType_exports,
|
|
12
12
|
inject
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-7ZQZYDOC.js";
|
|
14
14
|
import {
|
|
15
15
|
Application,
|
|
16
16
|
AuthFactory,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
ValidationFactory,
|
|
46
46
|
ViewFactory,
|
|
47
47
|
token
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-GVNLZZTN.js";
|
|
49
49
|
|
|
50
50
|
// ../Config/dist/index.js
|
|
51
51
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -54,24 +54,62 @@ import { parse } from "dotenv";
|
|
|
54
54
|
import { existsSync as existsSync2, readdirSync } from "fs";
|
|
55
55
|
import { join as join2 } from "path";
|
|
56
56
|
import { pathToFileURL } from "url";
|
|
57
|
+
var kind = (value) => value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
58
|
+
var isPlainObject = (value) => {
|
|
59
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
60
|
+
const proto = Object.getPrototypeOf(value);
|
|
61
|
+
return proto === Object.prototype || proto === null;
|
|
62
|
+
};
|
|
63
|
+
function typed(repository, key2, defaultValue, expected, check) {
|
|
64
|
+
const value = repository.get(key2, defaultValue);
|
|
65
|
+
if (!check(value)) throw new TypeError(`Configuration value for key [${key2}] must be ${expected}, ${kind(value)} given.`);
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
var Config = facade(ConfigRepository, {
|
|
69
|
+
/** Several keys at once: a list of keys, or an object of keys and their defaults. */
|
|
70
|
+
getMany(keys) {
|
|
71
|
+
const entries = Array.isArray(keys) ? keys.map((key2) => [key2, void 0]) : Object.entries(keys);
|
|
72
|
+
return Object.fromEntries(entries.map(([key2, fallback]) => [key2, this.get(key2, fallback)]));
|
|
73
|
+
},
|
|
74
|
+
/** Puts a value first in the array at `key` (creating it when missing). */
|
|
75
|
+
prepend(key2, value) {
|
|
76
|
+
const current = this.get(key2, []);
|
|
77
|
+
this.set(key2, [value, ...Array.isArray(current) ? current : [current]]);
|
|
78
|
+
},
|
|
79
|
+
string(key2, defaultValue) {
|
|
80
|
+
return typed(this, key2, defaultValue, "a string", (value) => typeof value === "string");
|
|
81
|
+
},
|
|
82
|
+
integer(key2, defaultValue) {
|
|
83
|
+
return typed(this, key2, defaultValue, "an integer", (value) => Number.isInteger(value));
|
|
84
|
+
},
|
|
85
|
+
float(key2, defaultValue) {
|
|
86
|
+
return typed(this, key2, defaultValue, "a float", (value) => typeof value === "number");
|
|
87
|
+
},
|
|
88
|
+
boolean(key2, defaultValue) {
|
|
89
|
+
return typed(this, key2, defaultValue, "a boolean", (value) => typeof value === "boolean");
|
|
90
|
+
},
|
|
91
|
+
array(key2, defaultValue) {
|
|
92
|
+
return typed(this, key2, defaultValue, "an array", (value) => Array.isArray(value) || isPlainObject(value));
|
|
93
|
+
}
|
|
94
|
+
});
|
|
57
95
|
var Repository = class {
|
|
58
96
|
constructor(items = {}) {
|
|
59
97
|
this.items = items;
|
|
60
98
|
}
|
|
61
99
|
items;
|
|
62
|
-
has(
|
|
63
|
-
return Arr.has(this.items,
|
|
100
|
+
has(key2) {
|
|
101
|
+
return Arr.has(this.items, key2);
|
|
64
102
|
}
|
|
65
|
-
get(
|
|
66
|
-
return Arr.get(this.items,
|
|
103
|
+
get(key2, defaultValue) {
|
|
104
|
+
return Arr.get(this.items, key2, defaultValue);
|
|
67
105
|
}
|
|
68
106
|
set(keyOrValues, value) {
|
|
69
107
|
const entries = typeof keyOrValues === "string" ? [[keyOrValues, value]] : Object.entries(keyOrValues);
|
|
70
108
|
for (const [k, v] of entries) Arr.set(this.items, k, v);
|
|
71
109
|
}
|
|
72
|
-
push(
|
|
73
|
-
const current = this.get(
|
|
74
|
-
this.set(
|
|
110
|
+
push(key2, value) {
|
|
111
|
+
const current = this.get(key2, []);
|
|
112
|
+
this.set(key2, [...Array.isArray(current) ? current : [current], value]);
|
|
75
113
|
}
|
|
76
114
|
all() {
|
|
77
115
|
return this.items;
|
|
@@ -87,8 +125,8 @@ var CASTS = {
|
|
|
87
125
|
"empty": "",
|
|
88
126
|
"(empty)": ""
|
|
89
127
|
};
|
|
90
|
-
function env(
|
|
91
|
-
const raw = process.env[
|
|
128
|
+
function env(key2, defaultValue) {
|
|
129
|
+
const raw = process.env[key2];
|
|
92
130
|
if (raw === void 0) return typeof defaultValue === "function" ? defaultValue() : defaultValue;
|
|
93
131
|
const lower = raw.toLowerCase();
|
|
94
132
|
if (Object.hasOwn(CASTS, lower)) return CASTS[lower];
|
|
@@ -107,9 +145,9 @@ function loadEnvironment(basePath) {
|
|
|
107
145
|
if (!existsSync(file)) return void 0;
|
|
108
146
|
const source = readFileSync(file, "utf8");
|
|
109
147
|
const literal = singleQuoted(source);
|
|
110
|
-
for (const [
|
|
111
|
-
const value = literal.has(
|
|
112
|
-
if (process.env[
|
|
148
|
+
for (const [key2, raw] of Object.entries(parse(source))) {
|
|
149
|
+
const value = literal.has(key2) ? raw : raw.replace(/\$\{([A-Za-z_][A-Za-z0-9_.]*)\}/g, (match, name) => process.env[name] ?? match);
|
|
150
|
+
if (process.env[key2] === void 0) process.env[key2] = value;
|
|
113
151
|
}
|
|
114
152
|
return file;
|
|
115
153
|
}
|
|
@@ -119,7 +157,7 @@ function clonePlain(value) {
|
|
|
119
157
|
if (typeof value === "object" && value !== null) {
|
|
120
158
|
const proto = Object.getPrototypeOf(value);
|
|
121
159
|
if (proto !== Object.prototype && proto !== null) return value;
|
|
122
|
-
return Object.fromEntries(Object.entries(value).map(([
|
|
160
|
+
return Object.fromEntries(Object.entries(value).map(([key2, item]) => [key2, clonePlain(item)]));
|
|
123
161
|
}
|
|
124
162
|
return value;
|
|
125
163
|
}
|
|
@@ -227,6 +265,7 @@ var ViteHtml = class {
|
|
|
227
265
|
return this.html;
|
|
228
266
|
}
|
|
229
267
|
};
|
|
268
|
+
var TRACKED = { "data-navigate-track": true };
|
|
230
269
|
var CSS_PATH = /\.(css|less|sass|scss|styl|stylus|pcss|postcss)(\?[^.]*)?$/;
|
|
231
270
|
var manifests = /* @__PURE__ */ new Map();
|
|
232
271
|
var escapeAttribute = (value) => value.replace(/&/g, "&").replace(/"/g, """);
|
|
@@ -254,8 +293,8 @@ var Vite = class {
|
|
|
254
293
|
cspNonce() {
|
|
255
294
|
return this.#nonce;
|
|
256
295
|
}
|
|
257
|
-
useIntegrityKey(
|
|
258
|
-
this.#integrityKey =
|
|
296
|
+
useIntegrityKey(key2) {
|
|
297
|
+
this.#integrityKey = key2;
|
|
259
298
|
return this;
|
|
260
299
|
}
|
|
261
300
|
withEntryPoints(entryPoints) {
|
|
@@ -323,16 +362,16 @@ var Vite = class {
|
|
|
323
362
|
const imported = manifest[name];
|
|
324
363
|
preloads.push([name, this.#assetPath(`${buildDirectory}/${imported.file}`), imported]);
|
|
325
364
|
for (const css of imported.css ?? []) {
|
|
326
|
-
const [
|
|
327
|
-
preloads.push([
|
|
328
|
-
tags.push(this.#tagForChunk(
|
|
365
|
+
const [key2, partial] = cssChunk(css);
|
|
366
|
+
preloads.push([key2, this.#assetPath(`${buildDirectory}/${css}`), partial]);
|
|
367
|
+
tags.push(this.#tagForChunk(key2, this.#assetPath(`${buildDirectory}/${css}`), partial, manifest));
|
|
329
368
|
}
|
|
330
369
|
}
|
|
331
370
|
tags.push(this.#tagForChunk(entry, this.#assetPath(`${buildDirectory}/${chunk2.file}`), chunk2, manifest));
|
|
332
371
|
for (const css of chunk2.css ?? []) {
|
|
333
|
-
const [
|
|
334
|
-
preloads.push([
|
|
335
|
-
tags.push(this.#tagForChunk(
|
|
372
|
+
const [key2, partial] = cssChunk(css);
|
|
373
|
+
preloads.push([key2, this.#assetPath(`${buildDirectory}/${css}`), partial]);
|
|
374
|
+
tags.push(this.#tagForChunk(key2, this.#assetPath(`${buildDirectory}/${css}`), partial, manifest));
|
|
336
375
|
}
|
|
337
376
|
}
|
|
338
377
|
const uniqueTags = [...new Set(tags)];
|
|
@@ -428,10 +467,10 @@ var Vite = class {
|
|
|
428
467
|
}
|
|
429
468
|
#tagForChunk(src, url2, chunk2, manifest) {
|
|
430
469
|
if (CSS_PATH.test(url2)) {
|
|
431
|
-
const attributes2 = this.#parseAttributes({ rel: "stylesheet", href: url2, nonce: this.#nonce ?? false, ...this.#styleAttributes(src, url2, chunk2, manifest) });
|
|
470
|
+
const attributes2 = this.#parseAttributes({ rel: "stylesheet", href: url2, nonce: this.#nonce ?? false, ...TRACKED, ...this.#styleAttributes(src, url2, chunk2, manifest) });
|
|
432
471
|
return `<link ${attributes2.join(" ")} />`;
|
|
433
472
|
}
|
|
434
|
-
const attributes = this.#parseAttributes({ type: "module", src: url2, nonce: this.#nonce ?? false, ...this.#scriptAttributes(src, url2, chunk2, manifest) });
|
|
473
|
+
const attributes = this.#parseAttributes({ type: "module", src: url2, nonce: this.#nonce ?? false, ...TRACKED, ...this.#scriptAttributes(src, url2, chunk2, manifest) });
|
|
435
474
|
return `<script ${attributes.join(" ")}></script>`;
|
|
436
475
|
}
|
|
437
476
|
#preloadTag(src, url2, chunk2, manifest) {
|
|
@@ -469,10 +508,10 @@ var Vite = class {
|
|
|
469
508
|
}
|
|
470
509
|
};
|
|
471
510
|
function currentVite() {
|
|
472
|
-
const
|
|
473
|
-
if (
|
|
474
|
-
if (
|
|
475
|
-
return new Vite(
|
|
511
|
+
const container2 = currentContainer();
|
|
512
|
+
if (container2 === void 0) throw new ContainerNotAvailableError("Vite");
|
|
513
|
+
if (container2.bound(HttpRequest)) return container2.make(Vite);
|
|
514
|
+
return new Vite(container2.make(Application));
|
|
476
515
|
}
|
|
477
516
|
function vite(entryPoints, buildDirectory) {
|
|
478
517
|
const instance = currentVite();
|
|
@@ -481,6 +520,7 @@ function vite(entryPoints, buildDirectory) {
|
|
|
481
520
|
function viteReactRefresh() {
|
|
482
521
|
return currentVite().reactRefresh();
|
|
483
522
|
}
|
|
523
|
+
var SHUTDOWN_TIMEOUT_MS = 1e4;
|
|
484
524
|
var Application2 = class _Application extends Container {
|
|
485
525
|
constructor(base) {
|
|
486
526
|
super();
|
|
@@ -492,11 +532,13 @@ var Application2 = class _Application extends Container {
|
|
|
492
532
|
this.scoped(Vite, () => new Vite(this));
|
|
493
533
|
}
|
|
494
534
|
base;
|
|
495
|
-
version = "0.1.0-alpha.
|
|
535
|
+
version = "0.1.0-alpha.7";
|
|
496
536
|
providers = [];
|
|
497
537
|
bootingCallbacks = [];
|
|
498
538
|
bootedCallbacks = [];
|
|
499
539
|
terminatingCallbacks = [];
|
|
540
|
+
shutdownCallbacks = [];
|
|
541
|
+
shutdownPromise;
|
|
500
542
|
bootPromise;
|
|
501
543
|
hasBooted = false;
|
|
502
544
|
inConsole = false;
|
|
@@ -590,6 +632,44 @@ var Application2 = class _Application extends Container {
|
|
|
590
632
|
async terminate() {
|
|
591
633
|
for (const callback of this.terminatingCallbacks) await callback();
|
|
592
634
|
}
|
|
635
|
+
/**
|
|
636
|
+
* A callback for the end of the process (the CLI after its command, the server on SIGTERM): close DB pools, Redis
|
|
637
|
+
* clients and log files. Not in Laravel, whose PHP processes end with each request.
|
|
638
|
+
*/
|
|
639
|
+
onShutdown(callback) {
|
|
640
|
+
this.shutdownCallbacks.push(callback);
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Runs the shutdown callbacks once (a second call returns the same promise), newest first: an app's own callbacks
|
|
644
|
+
* run before the framework closes what they use, then the framework's in the reverse of its providers' order (the
|
|
645
|
+
* queue, database and cache connections close, then the log is written, then the session store closes). A failing
|
|
646
|
+
* callback is printed with console.error (the log may be closed already) and the others still run; after 10 seconds
|
|
647
|
+
* the rest are given up on, so the process can still exit. Never rejects.
|
|
648
|
+
*/
|
|
649
|
+
shutdown() {
|
|
650
|
+
return this.shutdownPromise ??= this.runShutdown();
|
|
651
|
+
}
|
|
652
|
+
async runShutdown() {
|
|
653
|
+
if (this.shutdownCallbacks.length === 0) return;
|
|
654
|
+
const callbacks = [...this.shutdownCallbacks].reverse();
|
|
655
|
+
const run = (async () => {
|
|
656
|
+
for (const callback of callbacks) {
|
|
657
|
+
try {
|
|
658
|
+
await callback();
|
|
659
|
+
} catch (error) {
|
|
660
|
+
console.error(error);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return "done";
|
|
664
|
+
})();
|
|
665
|
+
let timer;
|
|
666
|
+
const deadline = new Promise((resolve2) => timer = setTimeout(() => resolve2("timeout"), SHUTDOWN_TIMEOUT_MS));
|
|
667
|
+
const outcome = await Promise.race([run, deadline]);
|
|
668
|
+
clearTimeout(timer);
|
|
669
|
+
if (outcome === "timeout") {
|
|
670
|
+
console.error(`The shutdown callbacks did not finish within ${SHUTDOWN_TIMEOUT_MS / 1e3} seconds; carrying on with the exit.`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
593
673
|
async runBoot() {
|
|
594
674
|
for (const callback of this.bootingCallbacks) await callback(this);
|
|
595
675
|
for (let i = 0; i < this.providers.length; i++) await this.bootProvider(this.providers[i]);
|
|
@@ -637,9 +717,16 @@ import { relative, resolve, sep } from "path";
|
|
|
637
717
|
import { createServer } from "http";
|
|
638
718
|
import { Readable as Readable3 } from "stream";
|
|
639
719
|
import { pipeline } from "stream/promises";
|
|
640
|
-
import { existsSync as
|
|
720
|
+
import { existsSync as existsSync32 } from "fs";
|
|
641
721
|
import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
642
722
|
import { dirname } from "path";
|
|
723
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
724
|
+
import { join as join5 } from "path";
|
|
725
|
+
import * as util from "util";
|
|
726
|
+
import { existsSync as existsSync22, statSync as statSync2, watch as watchPath } from "fs";
|
|
727
|
+
import { join as join23 } from "path";
|
|
728
|
+
import { createServer as createServer2 } from "net";
|
|
729
|
+
import { spawn as spawnProcess } from "child_process";
|
|
643
730
|
var HttpException = class extends Error {
|
|
644
731
|
constructor(status, message = "", headers = {}) {
|
|
645
732
|
super(message);
|
|
@@ -711,8 +798,8 @@ async function readBody(raw, limit) {
|
|
|
711
798
|
return out;
|
|
712
799
|
}
|
|
713
800
|
function requestScope() {
|
|
714
|
-
const
|
|
715
|
-
return
|
|
801
|
+
const container2 = currentContainer();
|
|
802
|
+
return container2 !== void 0 && container2.bound(HttpRequest) ? container2 : void 0;
|
|
716
803
|
}
|
|
717
804
|
function currentRequest() {
|
|
718
805
|
return requestScope()?.make(HttpRequest);
|
|
@@ -744,11 +831,11 @@ var HttpRedirectResponse = class {
|
|
|
744
831
|
targetUrl;
|
|
745
832
|
status;
|
|
746
833
|
#headers;
|
|
747
|
-
with(
|
|
834
|
+
with(key2, value) {
|
|
748
835
|
const session = optionalSession();
|
|
749
836
|
if (session === void 0) return this;
|
|
750
|
-
if (typeof
|
|
751
|
-
else for (const [name, item] of Object.entries(
|
|
837
|
+
if (typeof key2 === "string") session.flash(key2, value);
|
|
838
|
+
else for (const [name, item] of Object.entries(key2)) session.flash(name, item);
|
|
752
839
|
return this;
|
|
753
840
|
}
|
|
754
841
|
/** Flashes `errors` as { [bag]: { field: string[] } }, keeping the other bags already flashed. */
|
|
@@ -969,7 +1056,7 @@ var FormUploadedFile = class _FormUploadedFile {
|
|
|
969
1056
|
};
|
|
970
1057
|
var FORBIDDEN = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
971
1058
|
var INTEGER_KEY = /^(?:0|[1-9]\d*)$/;
|
|
972
|
-
var
|
|
1059
|
+
var isPlainObject2 = (value) => {
|
|
973
1060
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
974
1061
|
const prototype = Object.getPrototypeOf(value);
|
|
975
1062
|
return prototype === Object.prototype || prototype === null;
|
|
@@ -986,11 +1073,11 @@ function parseBracketKey(name) {
|
|
|
986
1073
|
}
|
|
987
1074
|
function normalizeLists(value) {
|
|
988
1075
|
if (Array.isArray(value)) return value.map(normalizeLists);
|
|
989
|
-
if (!
|
|
1076
|
+
if (!isPlainObject2(value)) return value;
|
|
990
1077
|
const keys = Object.keys(value);
|
|
991
1078
|
const out = {};
|
|
992
|
-
for (const
|
|
993
|
-
if (keys.length > 0 && keys.every((
|
|
1079
|
+
for (const key2 of keys) out[key2] = normalizeLists(value[key2]);
|
|
1080
|
+
if (keys.length > 0 && keys.every((key2, index) => key2 === String(index))) return keys.map((key2) => out[key2]);
|
|
994
1081
|
return out;
|
|
995
1082
|
}
|
|
996
1083
|
function fromEntries(entries) {
|
|
@@ -1013,7 +1100,7 @@ function fromEntries(entries) {
|
|
|
1013
1100
|
node[segment] = value;
|
|
1014
1101
|
break;
|
|
1015
1102
|
}
|
|
1016
|
-
if (!
|
|
1103
|
+
if (!isPlainObject2(node[segment])) node[segment] = {};
|
|
1017
1104
|
node = node[segment];
|
|
1018
1105
|
}
|
|
1019
1106
|
}
|
|
@@ -1021,17 +1108,17 @@ function fromEntries(entries) {
|
|
|
1021
1108
|
}
|
|
1022
1109
|
function sanitize(value) {
|
|
1023
1110
|
if (Array.isArray(value)) return value.map(sanitize);
|
|
1024
|
-
if (!
|
|
1111
|
+
if (!isPlainObject2(value)) return value;
|
|
1025
1112
|
const out = {};
|
|
1026
|
-
for (const [
|
|
1113
|
+
for (const [key2, item] of Object.entries(value)) if (!FORBIDDEN.has(key2)) out[key2] = sanitize(item);
|
|
1027
1114
|
return out;
|
|
1028
1115
|
}
|
|
1029
1116
|
function mergeRecursive(base, override) {
|
|
1030
1117
|
const out = { ...base };
|
|
1031
|
-
for (const [
|
|
1032
|
-
if (FORBIDDEN.has(
|
|
1033
|
-
const current = out[
|
|
1034
|
-
out[
|
|
1118
|
+
for (const [key2, value] of Object.entries(override)) {
|
|
1119
|
+
if (FORBIDDEN.has(key2)) continue;
|
|
1120
|
+
const current = out[key2];
|
|
1121
|
+
out[key2] = (isPlainObject2(current) || Array.isArray(current)) && (isPlainObject2(value) || Array.isArray(value)) ? normalizeLists(mergeRecursive({ ...current }, { ...value })) : value;
|
|
1035
1122
|
}
|
|
1036
1123
|
return out;
|
|
1037
1124
|
}
|
|
@@ -1039,17 +1126,17 @@ function isBlank(value) {
|
|
|
1039
1126
|
if (value === null || value === void 0) return true;
|
|
1040
1127
|
if (typeof value === "string") return value.trim() === "";
|
|
1041
1128
|
if (Array.isArray(value)) return value.length === 0;
|
|
1042
|
-
if (
|
|
1129
|
+
if (isPlainObject2(value)) return Object.keys(value).length === 0;
|
|
1043
1130
|
return false;
|
|
1044
1131
|
}
|
|
1045
1132
|
function transformLeaves(tree, transform, prefix = "") {
|
|
1046
1133
|
const out = {};
|
|
1047
|
-
for (const [
|
|
1134
|
+
for (const [key2, value] of Object.entries(tree)) out[key2] = transformValue(`${prefix}${key2}`, value, transform);
|
|
1048
1135
|
return out;
|
|
1049
1136
|
}
|
|
1050
1137
|
function transformValue(path, value, transform) {
|
|
1051
1138
|
if (Array.isArray(value)) return value.map((item, index) => transformValue(`${path}.${index}`, item, transform));
|
|
1052
|
-
if (
|
|
1139
|
+
if (isPlainObject2(value)) return transformLeaves(value, transform, `${path}.`);
|
|
1053
1140
|
return transform(path, value);
|
|
1054
1141
|
}
|
|
1055
1142
|
var TRUE_VALUES = /* @__PURE__ */ new Set(["1", "true", "on", "yes"]);
|
|
@@ -1143,7 +1230,7 @@ var IncomingRequest = class {
|
|
|
1143
1230
|
return {};
|
|
1144
1231
|
}
|
|
1145
1232
|
if (Array.isArray(parsed)) return sanitize({ ...parsed });
|
|
1146
|
-
return
|
|
1233
|
+
return isPlainObject2(parsed) ? sanitize(parsed) : {};
|
|
1147
1234
|
}
|
|
1148
1235
|
async #parseForm(form) {
|
|
1149
1236
|
const fields = [];
|
|
@@ -1254,12 +1341,12 @@ var IncomingRequest = class {
|
|
|
1254
1341
|
content() {
|
|
1255
1342
|
return this.#content;
|
|
1256
1343
|
}
|
|
1257
|
-
query(
|
|
1258
|
-
return Arr.get(this.#query,
|
|
1344
|
+
query(key2, defaultValue) {
|
|
1345
|
+
return Arr.get(this.#query, key2, defaultValue);
|
|
1259
1346
|
}
|
|
1260
1347
|
/** Body wins over the query string (PHP's $body + $query). Files are not input. */
|
|
1261
|
-
input(
|
|
1262
|
-
return Arr.get({ ...this.#query, ...this.#body },
|
|
1348
|
+
input(key2, defaultValue) {
|
|
1349
|
+
return Arr.get({ ...this.#query, ...this.#body }, key2, defaultValue);
|
|
1263
1350
|
}
|
|
1264
1351
|
/** Input plus files (array_replace_recursive). */
|
|
1265
1352
|
all() {
|
|
@@ -1268,43 +1355,48 @@ var IncomingRequest = class {
|
|
|
1268
1355
|
only(...keys) {
|
|
1269
1356
|
const all = this.all();
|
|
1270
1357
|
const out = {};
|
|
1271
|
-
for (const
|
|
1358
|
+
for (const key2 of keys.flat()) if (Arr.has(all, key2)) Arr.set(out, key2, Arr.get(all, key2));
|
|
1272
1359
|
return out;
|
|
1273
1360
|
}
|
|
1274
1361
|
except(...keys) {
|
|
1275
1362
|
const all = structuredCloneInput(this.all());
|
|
1276
|
-
for (const
|
|
1363
|
+
for (const key2 of keys.flat()) Arr.forget(all, key2);
|
|
1277
1364
|
return all;
|
|
1278
1365
|
}
|
|
1279
1366
|
has(...keys) {
|
|
1280
1367
|
const all = this.all();
|
|
1281
|
-
return keys.flat().every((
|
|
1368
|
+
return keys.flat().every((key2) => Arr.has(all, key2));
|
|
1282
1369
|
}
|
|
1283
1370
|
hasAny(...keys) {
|
|
1284
1371
|
const all = this.all();
|
|
1285
|
-
return keys.flat().some((
|
|
1372
|
+
return keys.flat().some((key2) => Arr.has(all, key2));
|
|
1286
1373
|
}
|
|
1287
1374
|
missing(...keys) {
|
|
1288
1375
|
return !this.has(...keys);
|
|
1289
1376
|
}
|
|
1290
|
-
filled(
|
|
1291
|
-
return !isBlank(Arr.get(this.all(),
|
|
1377
|
+
filled(key2) {
|
|
1378
|
+
return !isBlank(Arr.get(this.all(), key2));
|
|
1379
|
+
}
|
|
1380
|
+
/** Laravel's string(): the input as a Stringable (the default when it is missing or null): request().string('q').trim(). */
|
|
1381
|
+
string(key2, defaultValue = "") {
|
|
1382
|
+
const value = this.input(key2);
|
|
1383
|
+
return Str.of(value === void 0 || value === null ? defaultValue : value);
|
|
1292
1384
|
}
|
|
1293
|
-
string(
|
|
1294
|
-
|
|
1295
|
-
return
|
|
1385
|
+
/** Laravel's str(): string() under its short name. */
|
|
1386
|
+
str(key2, defaultValue = "") {
|
|
1387
|
+
return this.string(key2, defaultValue);
|
|
1296
1388
|
}
|
|
1297
1389
|
/** PHP's FILTER_VALIDATE_BOOLEAN: '1', 'true', 'on', 'yes' (any case) and true are true. */
|
|
1298
|
-
boolean(
|
|
1299
|
-
const value = this.input(
|
|
1390
|
+
boolean(key2, defaultValue = false) {
|
|
1391
|
+
const value = this.input(key2);
|
|
1300
1392
|
if (value === void 0) return defaultValue;
|
|
1301
1393
|
if (typeof value === "boolean") return value;
|
|
1302
1394
|
if (typeof value === "number") return value === 1;
|
|
1303
1395
|
return typeof value === "string" && TRUE_VALUES.has(value.trim().toLowerCase());
|
|
1304
1396
|
}
|
|
1305
1397
|
/** PHP's intval: leading digits, else 0. */
|
|
1306
|
-
integer(
|
|
1307
|
-
const value = this.input(
|
|
1398
|
+
integer(key2, defaultValue = 0) {
|
|
1399
|
+
const value = this.input(key2);
|
|
1308
1400
|
if (value === void 0) return defaultValue;
|
|
1309
1401
|
if (typeof value === "number") return Number.isFinite(value) ? Math.trunc(value) : 0;
|
|
1310
1402
|
if (typeof value === "boolean") return value ? 1 : 0;
|
|
@@ -1322,12 +1414,12 @@ var IncomingRequest = class {
|
|
|
1322
1414
|
this.#body = transformLeaves(this.#body, transform);
|
|
1323
1415
|
return this;
|
|
1324
1416
|
}
|
|
1325
|
-
file(
|
|
1326
|
-
const value = Arr.get(this.#files,
|
|
1417
|
+
file(key2) {
|
|
1418
|
+
const value = Arr.get(this.#files, key2);
|
|
1327
1419
|
return value instanceof FormUploadedFile ? value : void 0;
|
|
1328
1420
|
}
|
|
1329
|
-
hasFile(
|
|
1330
|
-
const value = Arr.get(this.#files,
|
|
1421
|
+
hasFile(key2) {
|
|
1422
|
+
const value = Arr.get(this.#files, key2);
|
|
1331
1423
|
return value instanceof FormUploadedFile || Array.isArray(value) && value.some((item) => item instanceof FormUploadedFile);
|
|
1332
1424
|
}
|
|
1333
1425
|
allFiles() {
|
|
@@ -1361,38 +1453,48 @@ var IncomingRequest = class {
|
|
|
1361
1453
|
this.#cookies = { ...cookies };
|
|
1362
1454
|
}
|
|
1363
1455
|
/** One route parameter (after model binding), or all of them. */
|
|
1364
|
-
route(
|
|
1456
|
+
route(key2) {
|
|
1365
1457
|
const parameters = this.#currentRoute()?.parameters ?? {};
|
|
1366
|
-
return
|
|
1458
|
+
return key2 === void 0 ? { ...parameters } : parameters[key2];
|
|
1367
1459
|
}
|
|
1368
1460
|
session() {
|
|
1369
1461
|
return inject(Session);
|
|
1370
1462
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1463
|
+
/**
|
|
1464
|
+
* The signed-in user, through the resolver Auth sets, else the current scope's guard (the default, or `guard`).
|
|
1465
|
+
* The guard keeps the user it found, so it is looked up once per request. Undefined for a guest or without auth.
|
|
1466
|
+
*/
|
|
1467
|
+
async user(guard) {
|
|
1468
|
+
if (this.#userResolver !== void 0) return await this.#userResolver(guard);
|
|
1469
|
+
const container2 = currentContainer();
|
|
1470
|
+
if (container2 === void 0 || !container2.bound(AuthFactory)) return void 0;
|
|
1471
|
+
return await container2.make(AuthFactory).guard(guard).user();
|
|
1373
1472
|
}
|
|
1374
1473
|
setUserResolver(resolver) {
|
|
1375
1474
|
this.#userResolver = resolver;
|
|
1376
1475
|
}
|
|
1377
|
-
/**
|
|
1476
|
+
/**
|
|
1477
|
+
* Validates all() through the ValidationFactory; throws its Validation error on failure. The data is typed from the
|
|
1478
|
+
* rules: `validate({ title: 'required|string' })` is `{ title: string }`.
|
|
1479
|
+
*/
|
|
1378
1480
|
validate(rules, messages, attributes) {
|
|
1379
1481
|
return inject(ValidationFactory).make(this.all(), rules, messages, attributes).validated();
|
|
1380
1482
|
}
|
|
1381
1483
|
#currentRoute() {
|
|
1382
|
-
const
|
|
1383
|
-
return
|
|
1484
|
+
const container2 = currentContainer();
|
|
1485
|
+
return container2?.bound(CurrentRoute) ? container2.make(CurrentRoute) : void 0;
|
|
1384
1486
|
}
|
|
1385
1487
|
};
|
|
1386
1488
|
function structuredCloneInput(tree) {
|
|
1387
1489
|
const out = {};
|
|
1388
|
-
for (const [
|
|
1389
|
-
out[
|
|
1490
|
+
for (const [key2, value] of Object.entries(tree)) {
|
|
1491
|
+
out[key2] = isPlainObject2(value) ? structuredCloneInput(value) : Array.isArray(value) ? [...value] : value;
|
|
1390
1492
|
}
|
|
1391
1493
|
return out;
|
|
1392
1494
|
}
|
|
1393
|
-
function request(
|
|
1495
|
+
function request(key2, defaultValue) {
|
|
1394
1496
|
const current = inject(HttpRequest);
|
|
1395
|
-
return
|
|
1497
|
+
return key2 === void 0 ? current : current.input(key2, defaultValue);
|
|
1396
1498
|
}
|
|
1397
1499
|
function response(content, status, headers) {
|
|
1398
1500
|
const factory = inject(ResponseFactory);
|
|
@@ -1409,7 +1511,8 @@ function url(path, query, absolute) {
|
|
|
1409
1511
|
const generator = inject(UrlGenerator);
|
|
1410
1512
|
return path === void 0 ? generator : generator.to(path, query, absolute);
|
|
1411
1513
|
}
|
|
1412
|
-
function route(name,
|
|
1514
|
+
function route(name, ...args) {
|
|
1515
|
+
const [parameters, absolute] = args;
|
|
1413
1516
|
return inject(UrlGenerator).route(name, parameters, absolute);
|
|
1414
1517
|
}
|
|
1415
1518
|
function abort(status, message = "", headers = {}) {
|
|
@@ -1482,7 +1585,7 @@ var TrimStrings = class {
|
|
|
1482
1585
|
async handle(request2, next) {
|
|
1483
1586
|
if (request2 instanceof IncomingRequest) {
|
|
1484
1587
|
request2.transformInput(
|
|
1485
|
-
(
|
|
1588
|
+
(key2, value) => typeof value === "string" && !this.except.includes(key2) ? value.replace(TRIM, "") : value
|
|
1486
1589
|
);
|
|
1487
1590
|
}
|
|
1488
1591
|
return next(request2);
|
|
@@ -1728,18 +1831,41 @@ var STATUS_TEXTS = {
|
|
|
1728
1831
|
403: "Forbidden",
|
|
1729
1832
|
404: "Not Found",
|
|
1730
1833
|
405: "Method Not Allowed",
|
|
1834
|
+
406: "Not Acceptable",
|
|
1835
|
+
407: "Proxy Authentication Required",
|
|
1731
1836
|
408: "Request Timeout",
|
|
1732
1837
|
409: "Conflict",
|
|
1733
1838
|
410: "Gone",
|
|
1839
|
+
411: "Length Required",
|
|
1840
|
+
412: "Precondition Failed",
|
|
1734
1841
|
413: "Content Too Large",
|
|
1842
|
+
414: "URI Too Long",
|
|
1735
1843
|
415: "Unsupported Media Type",
|
|
1844
|
+
416: "Range Not Satisfiable",
|
|
1845
|
+
417: "Expectation Failed",
|
|
1846
|
+
418: "I'm a teapot",
|
|
1736
1847
|
419: "Page Expired",
|
|
1848
|
+
421: "Misdirected Request",
|
|
1737
1849
|
422: "Unprocessable Content",
|
|
1850
|
+
423: "Locked",
|
|
1851
|
+
424: "Failed Dependency",
|
|
1852
|
+
425: "Too Early",
|
|
1853
|
+
426: "Upgrade Required",
|
|
1854
|
+
428: "Precondition Required",
|
|
1738
1855
|
429: "Too Many Requests",
|
|
1856
|
+
431: "Request Header Fields Too Large",
|
|
1857
|
+
451: "Unavailable For Legal Reasons",
|
|
1739
1858
|
500: "Server Error",
|
|
1859
|
+
501: "Not Implemented",
|
|
1740
1860
|
502: "Bad Gateway",
|
|
1741
1861
|
503: "Service Unavailable",
|
|
1742
|
-
504: "Gateway Timeout"
|
|
1862
|
+
504: "Gateway Timeout",
|
|
1863
|
+
505: "HTTP Version Not Supported",
|
|
1864
|
+
506: "Variant Also Negotiates",
|
|
1865
|
+
507: "Insufficient Storage",
|
|
1866
|
+
508: "Loop Detected",
|
|
1867
|
+
510: "Not Extended",
|
|
1868
|
+
511: "Network Authentication Required"
|
|
1743
1869
|
};
|
|
1744
1870
|
function statusText(status) {
|
|
1745
1871
|
return STATUS_TEXTS[status] ?? (status >= 500 ? "Server Error" : "Error");
|
|
@@ -1840,8 +1966,8 @@ function summarizeValidation(errors) {
|
|
|
1840
1966
|
return more === 0 ? messages[0] : `${messages[0]} (and ${more} more ${more === 1 ? "error" : "errors"})`;
|
|
1841
1967
|
}
|
|
1842
1968
|
var isHttpLike = (error) => {
|
|
1843
|
-
const
|
|
1844
|
-
return
|
|
1969
|
+
const kind2 = kindOf(error);
|
|
1970
|
+
return kind2 !== void 0 && HTTP_KINDS.has(kind2);
|
|
1845
1971
|
};
|
|
1846
1972
|
var ErrorHandler = class {
|
|
1847
1973
|
constructor(app) {
|
|
@@ -1873,8 +1999,8 @@ var ErrorHandler = class {
|
|
|
1873
1999
|
return this.#jsonWhen !== void 0 ? this.#jsonWhen(request2, error) : request2.expectsJson();
|
|
1874
2000
|
}
|
|
1875
2001
|
shouldReport(error) {
|
|
1876
|
-
const
|
|
1877
|
-
if (
|
|
2002
|
+
const kind2 = kindOf(error);
|
|
2003
|
+
if (kind2 !== void 0 && NOT_REPORTED.has(kind2)) return false;
|
|
1878
2004
|
return !this.#dontReport.some((cls) => error instanceof cls);
|
|
1879
2005
|
}
|
|
1880
2006
|
/** Report callbacks first (returning false stops the default log), then LogManager.error(), else console.error. Once per error. */
|
|
@@ -1886,10 +2012,10 @@ var ErrorHandler = class {
|
|
|
1886
2012
|
}
|
|
1887
2013
|
try {
|
|
1888
2014
|
for (const callback of this.#reportCallbacks) if (await callback(error) === false) return;
|
|
1889
|
-
const
|
|
1890
|
-
if (
|
|
2015
|
+
const container2 = this.#container();
|
|
2016
|
+
if (container2.bound(LogManager)) {
|
|
1891
2017
|
const message = error instanceof Error ? error.message : String(error);
|
|
1892
|
-
|
|
2018
|
+
container2.make(LogManager).error(message, { exception: error });
|
|
1893
2019
|
} else {
|
|
1894
2020
|
console.error(error);
|
|
1895
2021
|
}
|
|
@@ -1906,15 +2032,14 @@ var ErrorHandler = class {
|
|
|
1906
2032
|
const custom = await this.#renderCustom(request2, error);
|
|
1907
2033
|
if (custom !== void 0) return custom;
|
|
1908
2034
|
const http = this.#httpRequest(request2);
|
|
1909
|
-
const
|
|
2035
|
+
const kind2 = kindOf(error);
|
|
1910
2036
|
let response2;
|
|
1911
|
-
if (
|
|
1912
|
-
else if (
|
|
2037
|
+
if (kind2 === "Validation") response2 = await this.#renderValidation(http, error);
|
|
2038
|
+
else if (kind2 === "Authentication") response2 = await this.#renderAuthentication(http, error);
|
|
1913
2039
|
else if (this.shouldReturnJson(http, error)) response2 = this.#renderJson(error);
|
|
1914
2040
|
else response2 = await this.#renderHtml(http, error);
|
|
1915
2041
|
const headers = error?.headers;
|
|
1916
|
-
|
|
1917
|
-
return response2;
|
|
2042
|
+
return headers === void 0 ? response2 : withHeaders(response2, headers);
|
|
1918
2043
|
}
|
|
1919
2044
|
/** An error with its own render(request) or toResponse(request) (Laravel's renderable exceptions / Responsable). */
|
|
1920
2045
|
async #renderCustom(request2, error) {
|
|
@@ -1932,12 +2057,12 @@ var ErrorHandler = class {
|
|
|
1932
2057
|
return this.#json({ message: summarizeValidation(error.errors), errors: error.errors }, statusFor(error));
|
|
1933
2058
|
}
|
|
1934
2059
|
const input = { ...request2.input() };
|
|
1935
|
-
for (const
|
|
1936
|
-
const
|
|
1937
|
-
if (!
|
|
2060
|
+
for (const key2 of this.dontFlash) delete input[key2];
|
|
2061
|
+
const container2 = this.#container();
|
|
2062
|
+
if (!container2.bound(Redirector)) {
|
|
1938
2063
|
return this.#redirect(error.redirectTo ?? request2.header("referer") ?? "/");
|
|
1939
2064
|
}
|
|
1940
|
-
const redirector =
|
|
2065
|
+
const redirector = container2.make(Redirector);
|
|
1941
2066
|
const redirect2 = error.redirectTo !== void 0 ? redirector.to(error.redirectTo) : redirector.back();
|
|
1942
2067
|
const response2 = await redirect2.withInput(input).withErrors(error.errors, error.errorBag).toResponse(request2);
|
|
1943
2068
|
return response2;
|
|
@@ -1945,9 +2070,9 @@ var ErrorHandler = class {
|
|
|
1945
2070
|
async #renderAuthentication(request2, error) {
|
|
1946
2071
|
if (this.shouldReturnJson(request2, error)) return this.#json({ message: error.message || "Unauthenticated." }, 401);
|
|
1947
2072
|
const target2 = error.redirectTo ?? this.#guestRedirect(request2);
|
|
1948
|
-
const
|
|
1949
|
-
if (!
|
|
1950
|
-
const redirector =
|
|
2073
|
+
const container2 = this.#container();
|
|
2074
|
+
if (!container2.bound(Redirector)) return this.#redirect(target2);
|
|
2075
|
+
const redirector = container2.make(Redirector);
|
|
1951
2076
|
const redirect2 = typeof redirector.guest === "function" ? redirector.guest(target2) : redirector.to(target2);
|
|
1952
2077
|
return await redirect2.toResponse(request2);
|
|
1953
2078
|
}
|
|
@@ -1956,9 +2081,9 @@ var ErrorHandler = class {
|
|
|
1956
2081
|
const configured = this.#config("http.redirect_guests_to", void 0);
|
|
1957
2082
|
if (typeof configured === "function") return String(configured(request2));
|
|
1958
2083
|
if (typeof configured === "string" && configured !== "") return configured;
|
|
1959
|
-
const
|
|
1960
|
-
if (
|
|
1961
|
-
const url2 =
|
|
2084
|
+
const container2 = this.#container();
|
|
2085
|
+
if (container2.bound(UrlGenerator)) {
|
|
2086
|
+
const url2 = container2.make(UrlGenerator);
|
|
1962
2087
|
if (url2.hasRoute("login")) return url2.route("login");
|
|
1963
2088
|
}
|
|
1964
2089
|
return "/login";
|
|
@@ -1994,9 +2119,9 @@ var ErrorHandler = class {
|
|
|
1994
2119
|
}
|
|
1995
2120
|
/** resources/views/errors/{status}.tsx, then errors/4xx or errors/5xx, through the ViewFactory. */
|
|
1996
2121
|
async #errorView(status, error, message) {
|
|
1997
|
-
const
|
|
1998
|
-
if (!
|
|
1999
|
-
const views =
|
|
2122
|
+
const container2 = this.#container();
|
|
2123
|
+
if (!container2.bound(ViewFactory)) return void 0;
|
|
2124
|
+
const views = container2.make(ViewFactory);
|
|
2000
2125
|
for (const name of [`errors.${status}`, `errors.${Math.floor(status / 100)}xx`]) {
|
|
2001
2126
|
if (!views.exists(name)) continue;
|
|
2002
2127
|
try {
|
|
@@ -2026,14 +2151,19 @@ var ErrorHandler = class {
|
|
|
2026
2151
|
#debug() {
|
|
2027
2152
|
return this.#config("app.debug", false) === true;
|
|
2028
2153
|
}
|
|
2029
|
-
#config(
|
|
2030
|
-
return this.app.bound(ConfigRepository) ? this.app.make(ConfigRepository).get(
|
|
2154
|
+
#config(key2, fallback) {
|
|
2155
|
+
return this.app.bound(ConfigRepository) ? this.app.make(ConfigRepository).get(key2, fallback) : fallback;
|
|
2031
2156
|
}
|
|
2032
2157
|
/** The request scope while handling a request (so scoped services resolve), else the application. */
|
|
2033
2158
|
#container() {
|
|
2034
2159
|
return currentContainer() ?? this.app;
|
|
2035
2160
|
}
|
|
2036
2161
|
};
|
|
2162
|
+
function withHeaders(response2, extra) {
|
|
2163
|
+
const headers = new Headers(response2.headers);
|
|
2164
|
+
for (const [name, value] of Object.entries(extra)) headers.set(name, value);
|
|
2165
|
+
return new Response(response2.body, { status: response2.status, statusText: response2.statusText, headers });
|
|
2166
|
+
}
|
|
2037
2167
|
var ExceptionsConfiguration = class {
|
|
2038
2168
|
constructor(handler) {
|
|
2039
2169
|
this.handler = handler;
|
|
@@ -2108,9 +2238,9 @@ async function handleSafely(app, request2, publicPath) {
|
|
|
2108
2238
|
return plainServerError();
|
|
2109
2239
|
}
|
|
2110
2240
|
}
|
|
2111
|
-
function exitOnSignals(close, closed) {
|
|
2241
|
+
function exitOnSignals(close, closed, app) {
|
|
2112
2242
|
const onSignal = () => {
|
|
2113
|
-
void close().then(() => process.exit(0));
|
|
2243
|
+
void close().then(() => app.shutdown()).then(() => process.exit(0));
|
|
2114
2244
|
};
|
|
2115
2245
|
process.once("SIGINT", onSignal);
|
|
2116
2246
|
process.once("SIGTERM", onSignal);
|
|
@@ -2152,7 +2282,7 @@ async function serveBun(app, options) {
|
|
|
2152
2282
|
clearTimeout(timer);
|
|
2153
2283
|
markClosed();
|
|
2154
2284
|
})();
|
|
2155
|
-
if (options.signals) exitOnSignals(close, closed);
|
|
2285
|
+
if (options.signals) exitOnSignals(close, closed, app);
|
|
2156
2286
|
return {
|
|
2157
2287
|
runtime: "bun",
|
|
2158
2288
|
hostname: options.hostname,
|
|
@@ -2285,7 +2415,7 @@ async function serveNode(app, options) {
|
|
|
2285
2415
|
clearInterval(idle);
|
|
2286
2416
|
clearTimeout(force);
|
|
2287
2417
|
})();
|
|
2288
|
-
if (options.signals) exitOnSignals(close, closed);
|
|
2418
|
+
if (options.signals) exitOnSignals(close, closed, app);
|
|
2289
2419
|
return {
|
|
2290
2420
|
runtime: "node",
|
|
2291
2421
|
hostname: options.hostname,
|
|
@@ -2313,6 +2443,232 @@ async function serve(app, options = {}) {
|
|
|
2313
2443
|
onListen(server);
|
|
2314
2444
|
return server;
|
|
2315
2445
|
}
|
|
2446
|
+
var launchEnvironment = Object.freeze({ ...process.env });
|
|
2447
|
+
var BUN_ENV_FILES = [
|
|
2448
|
+
".env",
|
|
2449
|
+
".env.development",
|
|
2450
|
+
".env.production",
|
|
2451
|
+
".env.test",
|
|
2452
|
+
".env.local",
|
|
2453
|
+
".env.development.local",
|
|
2454
|
+
".env.production.local",
|
|
2455
|
+
".env.test.local"
|
|
2456
|
+
];
|
|
2457
|
+
var expand = (value, env2) => value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (_match, braced, bare) => env2[braced ?? bare] ?? "");
|
|
2458
|
+
function serverEnvironment(options = {}) {
|
|
2459
|
+
const launched = options.launched ?? launchEnvironment;
|
|
2460
|
+
const env2 = { ...launched };
|
|
2461
|
+
if (!(options.bun ?? process.versions.bun !== void 0)) return env2;
|
|
2462
|
+
const parseEnv2 = "parseEnv" in options ? options.parseEnv : util.parseEnv;
|
|
2463
|
+
if (typeof parseEnv2 !== "function") return env2;
|
|
2464
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2465
|
+
for (const name of BUN_ENV_FILES) {
|
|
2466
|
+
const file = join5(cwd, name);
|
|
2467
|
+
if (!existsSync4(file)) continue;
|
|
2468
|
+
let values;
|
|
2469
|
+
try {
|
|
2470
|
+
values = parseEnv2(readFileSync3(file, "utf8"));
|
|
2471
|
+
} catch {
|
|
2472
|
+
continue;
|
|
2473
|
+
}
|
|
2474
|
+
for (const [key2, raw] of Object.entries(values)) {
|
|
2475
|
+
const value = launched[key2];
|
|
2476
|
+
if (raw !== void 0 && value !== void 0 && (value === raw || value === expand(raw, launched))) delete env2[key2];
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
return env2;
|
|
2480
|
+
}
|
|
2481
|
+
var WATCHED_FOLDERS = ["app", "bootstrap", "config", "lang", "resources/lang", "resources/views", "routes"];
|
|
2482
|
+
var WATCHED_ROOT_FILES = ["package.json", "tsconfig.json"];
|
|
2483
|
+
var IGNORED_FOLDERS = ["bootstrap/ssr", "bootstrap/cache"];
|
|
2484
|
+
var CODE_FILE = /\.(?:[cm]?[jt]sx?|json)$/;
|
|
2485
|
+
var DECLARATION_FILE = /\.d\.[cm]?ts$/;
|
|
2486
|
+
var EDITOR_FILE = /(?:\.sw[a-px]|~|___jb_(?:tmp|old)___)$/;
|
|
2487
|
+
var isEnvironmentFile = (name) => (name === ".env" || name.startsWith(".env.")) && name !== ".env.example" && !EDITOR_FILE.test(name);
|
|
2488
|
+
function isWatchedPath(path) {
|
|
2489
|
+
const segments = path.split("/");
|
|
2490
|
+
if (segments.length === 1) return isEnvironmentFile(path) || WATCHED_ROOT_FILES.includes(path);
|
|
2491
|
+
if (!WATCHED_FOLDERS.some((folder) => path.startsWith(`${folder}/`))) return false;
|
|
2492
|
+
if (IGNORED_FOLDERS.some((folder) => path === folder || path.startsWith(`${folder}/`))) return false;
|
|
2493
|
+
if (segments.some((segment) => segment.startsWith(".") || segment === "node_modules")) return false;
|
|
2494
|
+
const name = segments.at(-1);
|
|
2495
|
+
if (DECLARATION_FILE.test(name)) return false;
|
|
2496
|
+
return CODE_FILE.test(name) || !name.includes(".");
|
|
2497
|
+
}
|
|
2498
|
+
var toSlashes = (file) => file.split("\\").join("/");
|
|
2499
|
+
function watchFiles(basePath, onChange, options = {}) {
|
|
2500
|
+
const watch = options.watch ?? ((path, watchOptions, listener) => watchPath(path, watchOptions, listener));
|
|
2501
|
+
const debounce = options.debounce ?? 200;
|
|
2502
|
+
const onError = options.onError ?? (() => {
|
|
2503
|
+
});
|
|
2504
|
+
const changed = /* @__PURE__ */ new Set();
|
|
2505
|
+
let timer;
|
|
2506
|
+
const settle = options.settle ?? 1e3;
|
|
2507
|
+
const startedAt = Date.now();
|
|
2508
|
+
const reportedLate = (path) => {
|
|
2509
|
+
if (Date.now() - startedAt >= settle) return false;
|
|
2510
|
+
try {
|
|
2511
|
+
const modified = statSync2(join23(basePath, path), { throwIfNoEntry: false })?.mtimeMs;
|
|
2512
|
+
return modified === void 0 || modified < startedAt + 1;
|
|
2513
|
+
} catch {
|
|
2514
|
+
return false;
|
|
2515
|
+
}
|
|
2516
|
+
};
|
|
2517
|
+
const record = (path) => {
|
|
2518
|
+
if (!isWatchedPath(path) || reportedLate(path)) return;
|
|
2519
|
+
changed.add(path);
|
|
2520
|
+
clearTimeout(timer);
|
|
2521
|
+
timer = setTimeout(() => {
|
|
2522
|
+
const files = [...changed];
|
|
2523
|
+
changed.clear();
|
|
2524
|
+
onChange(files);
|
|
2525
|
+
}, debounce);
|
|
2526
|
+
};
|
|
2527
|
+
const handles = [];
|
|
2528
|
+
const start = (folder, recursive, toPath) => {
|
|
2529
|
+
try {
|
|
2530
|
+
const handle = watch(folder === "." ? basePath : join23(basePath, folder), { recursive }, (_event, file) => {
|
|
2531
|
+
const path = toPath(file);
|
|
2532
|
+
if (path !== void 0) record(path);
|
|
2533
|
+
});
|
|
2534
|
+
handle.on("error", (error) => onError(folder, error));
|
|
2535
|
+
handles.push(handle);
|
|
2536
|
+
} catch (error) {
|
|
2537
|
+
onError(folder, error);
|
|
2538
|
+
}
|
|
2539
|
+
};
|
|
2540
|
+
start(".", false, (file) => file === null ? void 0 : toSlashes(file));
|
|
2541
|
+
for (const folder of WATCHED_FOLDERS) {
|
|
2542
|
+
if (existsSync22(join23(basePath, folder))) start(folder, true, (file) => `${folder}/${file === null ? "" : toSlashes(file)}`);
|
|
2543
|
+
}
|
|
2544
|
+
return {
|
|
2545
|
+
close() {
|
|
2546
|
+
clearTimeout(timer);
|
|
2547
|
+
changed.clear();
|
|
2548
|
+
for (const handle of handles) handle.close();
|
|
2549
|
+
}
|
|
2550
|
+
};
|
|
2551
|
+
}
|
|
2552
|
+
function claimPort(hostname, port) {
|
|
2553
|
+
return new Promise((resolve2, reject) => {
|
|
2554
|
+
const server = createServer2();
|
|
2555
|
+
server.once("error", reject);
|
|
2556
|
+
try {
|
|
2557
|
+
server.listen({ port, host: hostname, exclusive: true }, () => {
|
|
2558
|
+
const { port: claimed } = server.address();
|
|
2559
|
+
server.close(() => resolve2(claimed));
|
|
2560
|
+
});
|
|
2561
|
+
} catch (error) {
|
|
2562
|
+
reject(error);
|
|
2563
|
+
}
|
|
2564
|
+
});
|
|
2565
|
+
}
|
|
2566
|
+
var REASONS = {
|
|
2567
|
+
EADDRINUSE: "Address already in use",
|
|
2568
|
+
EACCES: "Permission denied",
|
|
2569
|
+
EADDRNOTAVAIL: "Can't assign requested address"
|
|
2570
|
+
};
|
|
2571
|
+
function listenFailure(hostname, port, error) {
|
|
2572
|
+
const { code, message } = error;
|
|
2573
|
+
return `Failed to listen on ${displayHost(hostname)}:${port} (reason: ${code !== void 0 && REASONS[code] || message})`;
|
|
2574
|
+
}
|
|
2575
|
+
var WatchedServer = class {
|
|
2576
|
+
constructor(options) {
|
|
2577
|
+
this.options = options;
|
|
2578
|
+
}
|
|
2579
|
+
options;
|
|
2580
|
+
running;
|
|
2581
|
+
restarting = false;
|
|
2582
|
+
stopping = false;
|
|
2583
|
+
finish = () => {
|
|
2584
|
+
};
|
|
2585
|
+
/** Starts the server and keeps it current until stopped. Resolves 0 once the last server has exited. */
|
|
2586
|
+
run() {
|
|
2587
|
+
const done = new Promise((resolve2) => this.finish = resolve2);
|
|
2588
|
+
const stopListening = this.listenForSignals();
|
|
2589
|
+
const watcher = this.options.watch((files) => void this.changed(files));
|
|
2590
|
+
this.start();
|
|
2591
|
+
return done.finally(() => {
|
|
2592
|
+
watcher.close();
|
|
2593
|
+
stopListening();
|
|
2594
|
+
});
|
|
2595
|
+
}
|
|
2596
|
+
/** Stops the server and ends run(); called again, kills the server at once. */
|
|
2597
|
+
stop() {
|
|
2598
|
+
if (this.stopping) {
|
|
2599
|
+
this.running?.child.kill("SIGKILL");
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2602
|
+
this.stopping = true;
|
|
2603
|
+
void this.stopServer().then(() => this.finish(0));
|
|
2604
|
+
}
|
|
2605
|
+
async changed(files) {
|
|
2606
|
+
if (this.stopping || this.restarting || files.length === 0) return;
|
|
2607
|
+
this.restarting = true;
|
|
2608
|
+
try {
|
|
2609
|
+
this.options.info(
|
|
2610
|
+
files.some(isEnvironmentFile) ? "Environment modified. Restarting server..." : `Application change detected in [${files[0]}]. Restarting server...`
|
|
2611
|
+
);
|
|
2612
|
+
await this.stopServer();
|
|
2613
|
+
if (!this.stopping) this.start();
|
|
2614
|
+
} finally {
|
|
2615
|
+
this.restarting = false;
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
start() {
|
|
2619
|
+
const [command, ...args] = this.options.command;
|
|
2620
|
+
const child = (this.options.spawn ?? spawnProcess)(command, args, {
|
|
2621
|
+
cwd: this.options.cwd,
|
|
2622
|
+
env: this.options.env,
|
|
2623
|
+
stdio: ["ignore", "inherit", "inherit"]
|
|
2624
|
+
});
|
|
2625
|
+
const running = { child, stopping: false, killed: false, exited: Promise.resolve() };
|
|
2626
|
+
running.exited = new Promise((resolve2) => {
|
|
2627
|
+
let settled = false;
|
|
2628
|
+
const settle = (report) => {
|
|
2629
|
+
if (settled) return;
|
|
2630
|
+
settled = true;
|
|
2631
|
+
if (this.running === running) this.running = void 0;
|
|
2632
|
+
if (!running.stopping && !this.stopping) this.options.error(`${report}. Waiting for changes before restarting...`);
|
|
2633
|
+
resolve2();
|
|
2634
|
+
};
|
|
2635
|
+
child.once("exit", (code, signal) => settle(signal !== null ? `The server stopped (signal ${signal})` : `The server stopped (exit code ${code})`));
|
|
2636
|
+
child.once("error", (error) => {
|
|
2637
|
+
if (child.pid === void 0) settle(`The server could not start (${error.message})`);
|
|
2638
|
+
});
|
|
2639
|
+
});
|
|
2640
|
+
this.running = running;
|
|
2641
|
+
}
|
|
2642
|
+
/** Sends SIGTERM once and waits for the exit, killing the server after stopTimeout. */
|
|
2643
|
+
async stopServer() {
|
|
2644
|
+
const running = this.running;
|
|
2645
|
+
if (running === void 0) return;
|
|
2646
|
+
if (!running.stopping) {
|
|
2647
|
+
running.stopping = true;
|
|
2648
|
+
running.child.kill("SIGTERM");
|
|
2649
|
+
}
|
|
2650
|
+
const timeout = this.options.stopTimeout ?? 15e3;
|
|
2651
|
+
const timer = setTimeout(() => {
|
|
2652
|
+
if (running.killed) return;
|
|
2653
|
+
running.killed = true;
|
|
2654
|
+
this.options.error(`The server did not stop within ${timeout / 1e3} seconds and was killed.`);
|
|
2655
|
+
running.child.kill("SIGKILL");
|
|
2656
|
+
}, timeout);
|
|
2657
|
+
try {
|
|
2658
|
+
await running.exited;
|
|
2659
|
+
} finally {
|
|
2660
|
+
clearTimeout(timer);
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
listenForSignals() {
|
|
2664
|
+
const signals = this.options.signals ?? ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
2665
|
+
const handler = () => this.stop();
|
|
2666
|
+
for (const signal of signals) process.on(signal, handler);
|
|
2667
|
+
return () => {
|
|
2668
|
+
for (const signal of signals) process.off(signal, handler);
|
|
2669
|
+
};
|
|
2670
|
+
}
|
|
2671
|
+
};
|
|
2316
2672
|
function io(command) {
|
|
2317
2673
|
return {
|
|
2318
2674
|
option: (name) => {
|
|
@@ -2386,11 +2742,36 @@ ${web[1]}api: 'routes/api.ts',`);
|
|
|
2386
2742
|
${create[0]}`);
|
|
2387
2743
|
return void 0;
|
|
2388
2744
|
}
|
|
2745
|
+
async function serveWatching(app, out, port, run) {
|
|
2746
|
+
const hostname = out.option("host") ?? "127.0.0.1";
|
|
2747
|
+
const requested = port !== void 0 ? Number(port) : Number(process.env.PORT ?? process.env.SERVER_PORT ?? 8e3);
|
|
2748
|
+
let claimed;
|
|
2749
|
+
try {
|
|
2750
|
+
claimed = await claimPort(hostname, requested);
|
|
2751
|
+
} catch (error) {
|
|
2752
|
+
out.error(listenFailure(hostname, requested, error));
|
|
2753
|
+
return 1;
|
|
2754
|
+
}
|
|
2755
|
+
const script = process.argv[1];
|
|
2756
|
+
if (script === void 0) {
|
|
2757
|
+
out.error("serve --watch can't tell which script started this process, so it can't start the server.");
|
|
2758
|
+
return 1;
|
|
2759
|
+
}
|
|
2760
|
+
return run({
|
|
2761
|
+
command: [process.execPath, ...process.execArgv, script, "serve", `--host=${hostname}`, `--port=${claimed}`],
|
|
2762
|
+
cwd: process.cwd(),
|
|
2763
|
+
env: serverEnvironment(),
|
|
2764
|
+
watch: (onChange) => watchFiles(app.basePath(), onChange, { onError: (folder, error) => out.warn(`Unable to watch [${folder}] for changes: ${error.message}`) }),
|
|
2765
|
+
info: (message) => out.info(message),
|
|
2766
|
+
error: (message) => out.error(message)
|
|
2767
|
+
});
|
|
2768
|
+
}
|
|
2389
2769
|
function registerHttpCommands(kernel, app, options = {}) {
|
|
2390
2770
|
const serveApp = options.serve ?? serve;
|
|
2391
2771
|
const terminalWidth = options.terminalWidth ?? (() => process.stdout.columns ?? 120);
|
|
2772
|
+
const runWatching = options.watch ?? ((watchOptions) => new WatchedServer(watchOptions).run());
|
|
2392
2773
|
kernel.command(
|
|
2393
|
-
"serve {--host=127.0.0.1 : The host address to serve the application on} {--port= : The port to serve the application on}",
|
|
2774
|
+
"serve {--host=127.0.0.1 : The host address to serve the application on} {--port= : The port to serve the application on} {--watch : Automatically restart the server when the application is modified}",
|
|
2394
2775
|
async function() {
|
|
2395
2776
|
const out = io(this);
|
|
2396
2777
|
const port = out.option("port");
|
|
@@ -2398,6 +2779,7 @@ function registerHttpCommands(kernel, app, options = {}) {
|
|
|
2398
2779
|
out.error("The --port option must be a number.");
|
|
2399
2780
|
return 1;
|
|
2400
2781
|
}
|
|
2782
|
+
if (out.flag("watch")) return serveWatching(app, out, port, runWatching);
|
|
2401
2783
|
const server = await serveApp(app, {
|
|
2402
2784
|
hostname: out.option("host") ?? "127.0.0.1",
|
|
2403
2785
|
...port !== void 0 && { port: Number(port) },
|
|
@@ -2445,7 +2827,7 @@ function registerHttpCommands(kernel, app, options = {}) {
|
|
|
2445
2827
|
kernel.command("install:api {--force : Overwrite any existing API routes file}", async function() {
|
|
2446
2828
|
const out = io(this);
|
|
2447
2829
|
const routesFile = app.basePath("routes/api.ts");
|
|
2448
|
-
if (
|
|
2830
|
+
if (existsSync32(routesFile) && !out.flag("force")) {
|
|
2449
2831
|
out.error("API routes file already exists.");
|
|
2450
2832
|
} else {
|
|
2451
2833
|
await mkdir(dirname(routesFile), { recursive: true });
|
|
@@ -2453,7 +2835,7 @@ function registerHttpCommands(kernel, app, options = {}) {
|
|
|
2453
2835
|
out.info("Published API routes file.");
|
|
2454
2836
|
}
|
|
2455
2837
|
const bootstrapFile = app.bootstrapPath("app.ts");
|
|
2456
|
-
const source =
|
|
2838
|
+
const source = existsSync32(bootstrapFile) ? await readFile2(bootstrapFile, "utf8") : void 0;
|
|
2457
2839
|
const updated = source === void 0 ? void 0 : addApiRouting(source);
|
|
2458
2840
|
if (updated === void 0) {
|
|
2459
2841
|
out.warn(
|
|
@@ -2462,7 +2844,7 @@ function registerHttpCommands(kernel, app, options = {}) {
|
|
|
2462
2844
|
} else if (updated !== source) {
|
|
2463
2845
|
await writeFile(bootstrapFile, updated);
|
|
2464
2846
|
}
|
|
2465
|
-
out.info("API scaffolding installed.
|
|
2847
|
+
out.info("API scaffolding installed.");
|
|
2466
2848
|
return 0;
|
|
2467
2849
|
}).purpose("Create an API routes file and prepare the application for API authentication");
|
|
2468
2850
|
}
|
|
@@ -2500,23 +2882,98 @@ var HttpServiceProvider = class extends ServiceProvider {
|
|
|
2500
2882
|
// ../Live/dist/index.js
|
|
2501
2883
|
import { AssertionError } from "assert";
|
|
2502
2884
|
import { isDeepStrictEqual } from "util";
|
|
2885
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
2503
2886
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
2504
2887
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
2505
|
-
import { createHmac, timingSafeEqual } from "crypto";
|
|
2506
2888
|
import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
|
|
2507
|
-
import { join as
|
|
2889
|
+
import { join as join6, relative as relative2, sep as sep2 } from "path";
|
|
2508
2890
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2509
2891
|
import { AsyncLocalStorage } from "async_hooks";
|
|
2510
|
-
import { existsSync as
|
|
2892
|
+
import { existsSync as existsSync23 } from "fs";
|
|
2511
2893
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
2512
|
-
import { dirname as dirname2, join as
|
|
2894
|
+
import { dirname as dirname2, join as join24, relative as relative22, sep as sep22 } from "path";
|
|
2895
|
+
var LiveError = class extends Error {
|
|
2896
|
+
name = "LiveError";
|
|
2897
|
+
kind = "Server";
|
|
2898
|
+
status = 500;
|
|
2899
|
+
};
|
|
2900
|
+
var CorruptComponentPayloadError = class extends LiveError {
|
|
2901
|
+
name = "CorruptComponentPayloadError";
|
|
2902
|
+
kind = "Authorization";
|
|
2903
|
+
status = 403;
|
|
2904
|
+
constructor(component = "unknown") {
|
|
2905
|
+
super(`Live encountered corrupt data when trying to hydrate the [${component}] component.`);
|
|
2906
|
+
}
|
|
2907
|
+
};
|
|
2908
|
+
var MethodNotCallableError = class extends LiveError {
|
|
2909
|
+
name = "MethodNotCallableError";
|
|
2910
|
+
kind = "Authorization";
|
|
2911
|
+
status = 403;
|
|
2912
|
+
constructor(method, component) {
|
|
2913
|
+
super(`Unable to call component method. Public method [${method}] not found on component [${component}].`);
|
|
2914
|
+
}
|
|
2915
|
+
};
|
|
2916
|
+
var PropertyNotUpdatableError = class extends LiveError {
|
|
2917
|
+
name = "PropertyNotUpdatableError";
|
|
2918
|
+
kind = "Authorization";
|
|
2919
|
+
status = 403;
|
|
2920
|
+
constructor(path, component) {
|
|
2921
|
+
super(`Cannot update property [${path}] on component [${component}]: it is not a public, unlocked property.`);
|
|
2922
|
+
}
|
|
2923
|
+
};
|
|
2924
|
+
var LiveComponentNotFoundError = class extends LiveError {
|
|
2925
|
+
name = "LiveComponentNotFoundError";
|
|
2926
|
+
kind = "Http";
|
|
2927
|
+
status = 404;
|
|
2928
|
+
constructor(component) {
|
|
2929
|
+
super(`Unable to find component: [${component}].`);
|
|
2930
|
+
}
|
|
2931
|
+
};
|
|
2932
|
+
var InvalidLiveRequestError = class extends LiveError {
|
|
2933
|
+
name = "InvalidLiveRequestError";
|
|
2934
|
+
kind = "Http";
|
|
2935
|
+
status = 400;
|
|
2936
|
+
};
|
|
2937
|
+
var InvalidUploadSignatureError = class extends LiveError {
|
|
2938
|
+
name = "InvalidUploadSignatureError";
|
|
2939
|
+
kind = "Http";
|
|
2940
|
+
status = 401;
|
|
2941
|
+
constructor() {
|
|
2942
|
+
super("");
|
|
2943
|
+
}
|
|
2944
|
+
};
|
|
2945
|
+
var TemporaryFileNotFoundError = class extends LiveError {
|
|
2946
|
+
name = "TemporaryFileNotFoundError";
|
|
2947
|
+
kind = "Http";
|
|
2948
|
+
status = 404;
|
|
2949
|
+
constructor(filename) {
|
|
2950
|
+
super(`Unable to find the temporary upload [${filename}].`);
|
|
2951
|
+
}
|
|
2952
|
+
};
|
|
2953
|
+
var FileNotPreviewableError = class extends LiveError {
|
|
2954
|
+
name = "FileNotPreviewableError";
|
|
2955
|
+
constructor(extension) {
|
|
2956
|
+
super(`File with extension "${extension}" is not previewable. See the live.temporary_file_upload.preview_mimes config.`);
|
|
2957
|
+
}
|
|
2958
|
+
};
|
|
2959
|
+
var UploadValidationError = class extends LiveError {
|
|
2960
|
+
constructor(errors) {
|
|
2961
|
+
super("The upload failed.");
|
|
2962
|
+
this.errors = errors;
|
|
2963
|
+
}
|
|
2964
|
+
errors;
|
|
2965
|
+
name = "UploadValidationError";
|
|
2966
|
+
kind = "Validation";
|
|
2967
|
+
status = 422;
|
|
2968
|
+
errorBag = "default";
|
|
2969
|
+
};
|
|
2513
2970
|
Symbol.metadata ??= /* @__PURE__ */ Symbol.for("Symbol.metadata");
|
|
2514
2971
|
var LISTENERS = /* @__PURE__ */ Symbol.for("oriel:live.listeners");
|
|
2515
2972
|
var LOCKED = /* @__PURE__ */ Symbol.for("oriel:live.locked");
|
|
2516
2973
|
var COMPUTED = /* @__PURE__ */ Symbol.for("oriel:live.computed");
|
|
2517
|
-
function own(metadata,
|
|
2518
|
-
if (!Object.hasOwn(metadata,
|
|
2519
|
-
return metadata[
|
|
2974
|
+
function own(metadata, key2, empty2, copy) {
|
|
2975
|
+
if (!Object.hasOwn(metadata, key2)) metadata[key2] = metadata[key2] === void 0 ? empty2() : copy(metadata[key2]);
|
|
2976
|
+
return metadata[key2];
|
|
2520
2977
|
}
|
|
2521
2978
|
function On(event) {
|
|
2522
2979
|
return function(_method, context) {
|
|
@@ -2559,8 +3016,8 @@ function chainOf(cls) {
|
|
|
2559
3016
|
}
|
|
2560
3017
|
return chain;
|
|
2561
3018
|
}
|
|
2562
|
-
function staticsOf(cls,
|
|
2563
|
-
return chainOf(cls).flatMap((c) => Object.hasOwn(c,
|
|
3019
|
+
function staticsOf(cls, key2) {
|
|
3020
|
+
return chainOf(cls).flatMap((c) => Object.hasOwn(c, key2) && c[key2] !== void 0 ? [c[key2]] : []);
|
|
2564
3021
|
}
|
|
2565
3022
|
function listenersOf(cls) {
|
|
2566
3023
|
const result = {};
|
|
@@ -2581,6 +3038,277 @@ function lockedOf(cls) {
|
|
|
2581
3038
|
function computedOf(cls) {
|
|
2582
3039
|
return /* @__PURE__ */ new Set([...metadataOf(cls)[COMPUTED] ?? [], ...staticsOf(cls, "computed").flat()]);
|
|
2583
3040
|
}
|
|
3041
|
+
var TEMPORARY_UPLOAD_DEFAULTS = {
|
|
3042
|
+
disk: null,
|
|
3043
|
+
directory: "live-tmp",
|
|
3044
|
+
rules: ["required", "file", "max:12288"],
|
|
3045
|
+
middleware: "throttle:60,1",
|
|
3046
|
+
preview_mimes: ["png", "gif", "bmp", "svg", "wav", "mp4", "mov", "avi", "wmv", "mp3", "m4a", "jpg", "jpeg", "mpga", "webp", "wma"],
|
|
3047
|
+
max_upload_time: 5,
|
|
3048
|
+
cleanup: true
|
|
3049
|
+
};
|
|
3050
|
+
function container() {
|
|
3051
|
+
const current = currentContainer();
|
|
3052
|
+
if (current === void 0) throw new LiveError("Live uploads need the application: use them inside a request, a command or a test.");
|
|
3053
|
+
return current;
|
|
3054
|
+
}
|
|
3055
|
+
function uploadSetting(name) {
|
|
3056
|
+
const config = container().bound(ConfigRepository) ? container().make(ConfigRepository) : void 0;
|
|
3057
|
+
return config?.get(`live.temporary_file_upload.${name}`, null) ?? TEMPORARY_UPLOAD_DEFAULTS[name];
|
|
3058
|
+
}
|
|
3059
|
+
function temporaryDisk() {
|
|
3060
|
+
return container().make(FilesystemManager).disk(uploadSetting("disk") ?? void 0);
|
|
3061
|
+
}
|
|
3062
|
+
function temporaryDirectory() {
|
|
3063
|
+
return uploadSetting("directory").replace(/^\/+|\/+$/g, "");
|
|
3064
|
+
}
|
|
3065
|
+
var FILENAME = /^[A-Za-z0-9]{40}(?:\.[a-z0-9]+)?$/;
|
|
3066
|
+
function isTemporaryFileData(value) {
|
|
3067
|
+
if (typeof value !== "object" || value === null) return false;
|
|
3068
|
+
const data = value;
|
|
3069
|
+
return typeof data.filename === "string" && FILENAME.test(data.filename) && typeof data.name === "string" && typeof data.size === "number" && Number.isSafeInteger(data.size) && data.size >= 0 && typeof data.type === "string" && typeof data.extension === "string" && /^[a-z0-9]*$/.test(data.extension);
|
|
3070
|
+
}
|
|
3071
|
+
var HOUR = 36e5;
|
|
3072
|
+
var TemporaryUploadedFile = class {
|
|
3073
|
+
filename;
|
|
3074
|
+
clientName;
|
|
3075
|
+
mimeType;
|
|
3076
|
+
size;
|
|
3077
|
+
#extension;
|
|
3078
|
+
constructor(data) {
|
|
3079
|
+
if (!isTemporaryFileData(data)) throw new LiveError("A temporary upload needs a stored name, a name, a size, a type and an extension.");
|
|
3080
|
+
this.filename = data.filename;
|
|
3081
|
+
this.clientName = data.name;
|
|
3082
|
+
this.mimeType = data.type;
|
|
3083
|
+
this.size = data.size;
|
|
3084
|
+
this.#extension = data.extension;
|
|
3085
|
+
}
|
|
3086
|
+
/** What the snapshot carries. */
|
|
3087
|
+
toData() {
|
|
3088
|
+
return { filename: this.filename, name: this.clientName, size: this.size, type: this.mimeType, extension: this.#extension };
|
|
3089
|
+
}
|
|
3090
|
+
/** The file's path on the temporary disk. */
|
|
3091
|
+
get path() {
|
|
3092
|
+
const directory = temporaryDirectory();
|
|
3093
|
+
return directory === "" ? this.filename : `${directory}/${this.filename}`;
|
|
3094
|
+
}
|
|
3095
|
+
/** The extension of the upload's own type, as FormUploadedFile's extension(): what putFile() names a stored copy by. */
|
|
3096
|
+
extension() {
|
|
3097
|
+
return this.#extension;
|
|
3098
|
+
}
|
|
3099
|
+
/** The extension of the name the browser gave the file, lower-cased ('' without one). */
|
|
3100
|
+
clientExtension() {
|
|
3101
|
+
const dot = this.clientName.lastIndexOf(".");
|
|
3102
|
+
return dot === -1 ? "" : this.clientName.slice(dot + 1).toLowerCase();
|
|
3103
|
+
}
|
|
3104
|
+
/** A random 40-character name with extension(), as FormUploadedFile's. */
|
|
3105
|
+
hashName() {
|
|
3106
|
+
const extension = this.extension();
|
|
3107
|
+
return extension === "" ? Str.random(40) : `${Str.random(40)}.${extension}`;
|
|
3108
|
+
}
|
|
3109
|
+
async arrayBuffer() {
|
|
3110
|
+
const bytes = await temporaryDisk().getBytes(this.path);
|
|
3111
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
3112
|
+
}
|
|
3113
|
+
async text() {
|
|
3114
|
+
return new TextDecoder().decode(await temporaryDisk().getBytes(this.path));
|
|
3115
|
+
}
|
|
3116
|
+
/** The file's bytes from the temporary disk, opened on the first read. */
|
|
3117
|
+
stream() {
|
|
3118
|
+
let reader;
|
|
3119
|
+
return new ReadableStream({
|
|
3120
|
+
pull: async (controller) => {
|
|
3121
|
+
reader ??= (await temporaryDisk().readStream(this.path)).getReader();
|
|
3122
|
+
const { done, value } = await reader.read();
|
|
3123
|
+
if (done) controller.close();
|
|
3124
|
+
else controller.enqueue(value);
|
|
3125
|
+
},
|
|
3126
|
+
cancel: async (reason) => {
|
|
3127
|
+
await reader?.cancel(reason);
|
|
3128
|
+
}
|
|
3129
|
+
});
|
|
3130
|
+
}
|
|
3131
|
+
/** Stores a copy under a random name in `directory`, on `disk` (by default the temporary disk, as Livewire's). */
|
|
3132
|
+
store(directory, disk) {
|
|
3133
|
+
return container().make(FilesystemManager).disk(disk ?? uploadSetting("disk") ?? void 0).putFile(directory, this);
|
|
3134
|
+
}
|
|
3135
|
+
storeAs(directory, name, disk) {
|
|
3136
|
+
return container().make(FilesystemManager).disk(disk ?? uploadSetting("disk") ?? void 0).putFileAs(directory, this, name);
|
|
3137
|
+
}
|
|
3138
|
+
exists() {
|
|
3139
|
+
return temporaryDisk().exists(this.path);
|
|
3140
|
+
}
|
|
3141
|
+
/** Removes the temporary file (a stored copy stays). */
|
|
3142
|
+
delete() {
|
|
3143
|
+
return temporaryDisk().delete(this.path);
|
|
3144
|
+
}
|
|
3145
|
+
/** Whether temporaryUrl() serves this file: its type is in `live.temporary_file_upload.preview_mimes`. */
|
|
3146
|
+
isPreviewable() {
|
|
3147
|
+
return uploadSetting("preview_mimes").includes(this.#extension);
|
|
3148
|
+
}
|
|
3149
|
+
/**
|
|
3150
|
+
* A signed link to the file for `<img src>` or `<video src>` before it is stored. It lasts until the end of the hour
|
|
3151
|
+
* at least 30 minutes away (Livewire's), so a re-render keeps the same link and the browser doesn't reload it.
|
|
3152
|
+
*/
|
|
3153
|
+
temporaryUrl() {
|
|
3154
|
+
if (!this.isPreviewable()) throw new FileNotPreviewableError(this.#extension);
|
|
3155
|
+
const expires = new Date((Math.floor((Date.now() + 30 * 6e4) / HOUR) + 1) * HOUR - 1e3);
|
|
3156
|
+
return container().make(UrlGenerator).temporarySignedRoute("live.preview-file", expires, { filename: this.filename }, false);
|
|
3157
|
+
}
|
|
3158
|
+
};
|
|
3159
|
+
function isModel(value) {
|
|
3160
|
+
if (typeof value !== "object" || value === null) return false;
|
|
3161
|
+
const candidate = value;
|
|
3162
|
+
return typeof candidate.getKey === "function" && typeof candidate.getKeyName === "function" && typeof candidate.getTable === "function" && typeof candidate.exists === "boolean";
|
|
3163
|
+
}
|
|
3164
|
+
var isPlainObject3 = (value) => {
|
|
3165
|
+
const proto = Object.getPrototypeOf(value);
|
|
3166
|
+
return proto === Object.prototype || proto === null;
|
|
3167
|
+
};
|
|
3168
|
+
var isTuple = (value) => Array.isArray(value) && value.length === 2 && typeof value[1] === "object" && value[1] !== null && !Array.isArray(value[1]) && typeof value[1].s === "string";
|
|
3169
|
+
function classNameOf(registry, cls) {
|
|
3170
|
+
return registry.nameOf(cls) ?? registry.register(cls);
|
|
3171
|
+
}
|
|
3172
|
+
function dehydrate(value, registry, path, seen = /* @__PURE__ */ new Set()) {
|
|
3173
|
+
switch (typeof value) {
|
|
3174
|
+
case "undefined":
|
|
3175
|
+
return [null, { s: "u" }];
|
|
3176
|
+
case "string":
|
|
3177
|
+
case "boolean":
|
|
3178
|
+
return value;
|
|
3179
|
+
case "number":
|
|
3180
|
+
if (!Number.isFinite(value)) throw new LiveError(`Property [${path}] is ${value}: Live state must be JSON (finite numbers only).`);
|
|
3181
|
+
return value;
|
|
3182
|
+
case "bigint":
|
|
3183
|
+
case "symbol":
|
|
3184
|
+
case "function":
|
|
3185
|
+
throw new LiveError(`Property [${path}] holds a ${typeof value}: Live state may only hold primitives, arrays, plain objects, Dates, models and collections.`);
|
|
3186
|
+
}
|
|
3187
|
+
if (value === null) return null;
|
|
3188
|
+
const object = value;
|
|
3189
|
+
if (seen.has(object)) throw new LiveError(`Property [${path}] contains a circular reference.`);
|
|
3190
|
+
seen.add(object);
|
|
3191
|
+
try {
|
|
3192
|
+
if (object instanceof Date) return [object.toISOString(), { s: "dt" }];
|
|
3193
|
+
if (object instanceof TemporaryUploadedFile) return [object.toData(), { s: "fil" }];
|
|
3194
|
+
if (Array.isArray(object)) return [object.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), { s: "arr" }];
|
|
3195
|
+
if (isModel(object)) {
|
|
3196
|
+
const name = classNameOf(registry, object.constructor);
|
|
3197
|
+
if (!object.exists) throw new LiveError(`Property [${path}] holds an unsaved [${name}] model: only saved models can be kept between requests.`);
|
|
3198
|
+
return [null, { s: "mdl", class: name, key: object.getKey() }];
|
|
3199
|
+
}
|
|
3200
|
+
if (object instanceof Collection) {
|
|
3201
|
+
const items = object.all();
|
|
3202
|
+
const first = items[0];
|
|
3203
|
+
if (items.length > 0 && isModel(first) && items.every((item) => isModel(item) && item.constructor === first.constructor)) {
|
|
3204
|
+
for (const [i, item] of items.entries()) {
|
|
3205
|
+
if (!item.exists) throw new LiveError(`Property [${path}.${i}] holds an unsaved model.`);
|
|
3206
|
+
}
|
|
3207
|
+
const name = classNameOf(registry, first.constructor);
|
|
3208
|
+
return [null, { s: "elcln", class: name, keys: items.map((item) => item.getKey()) }];
|
|
3209
|
+
}
|
|
3210
|
+
const meta = object.constructor === Collection ? { s: "clctn" } : { s: "clctn", class: classNameOf(registry, object.constructor) };
|
|
3211
|
+
return [items.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), meta];
|
|
3212
|
+
}
|
|
3213
|
+
if (isPlainObject3(object)) {
|
|
3214
|
+
const out = {};
|
|
3215
|
+
for (const [key2, item] of Object.entries(object)) out[key2] = dehydrate(item, registry, `${path}.${key2}`, seen);
|
|
3216
|
+
return [out, { s: "obj" }];
|
|
3217
|
+
}
|
|
3218
|
+
const type = object.constructor?.name || "object";
|
|
3219
|
+
throw new LiveError(
|
|
3220
|
+
`Property [${path}] holds a [${type}] instance: Live state may only hold primitives, arrays, plain objects, Dates, models and collections.`
|
|
3221
|
+
);
|
|
3222
|
+
} finally {
|
|
3223
|
+
seen.delete(object);
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
function modelClass(registry, name) {
|
|
3227
|
+
if (typeof name !== "string" || !registry.has(name)) throw new LiveError(`Unable to hydrate the [${String(name)}] model: it is not registered.`);
|
|
3228
|
+
return registry.resolve(name);
|
|
3229
|
+
}
|
|
3230
|
+
function collectionClass(registry, name) {
|
|
3231
|
+
const cls = registry.has(name) ? registry.resolve(name) : void 0;
|
|
3232
|
+
if (typeof cls !== "function" || !(cls.prototype instanceof Collection)) throw new LiveError(`Unable to hydrate the [${name}] collection: it is not registered.`);
|
|
3233
|
+
return cls;
|
|
3234
|
+
}
|
|
3235
|
+
async function hydrate(value, registry) {
|
|
3236
|
+
if (!isTuple(value)) return value;
|
|
3237
|
+
const [inner, meta] = value;
|
|
3238
|
+
switch (meta.s) {
|
|
3239
|
+
case "u":
|
|
3240
|
+
return void 0;
|
|
3241
|
+
case "dt":
|
|
3242
|
+
return new Date(String(inner));
|
|
3243
|
+
case "arr":
|
|
3244
|
+
return Promise.all(inner.map((item) => hydrate(item, registry)));
|
|
3245
|
+
case "clctn": {
|
|
3246
|
+
const items = await Promise.all(inner.map((item) => hydrate(item, registry)));
|
|
3247
|
+
return meta.class === void 0 ? new Collection(items) : new (collectionClass(registry, meta.class))(items);
|
|
3248
|
+
}
|
|
3249
|
+
case "obj": {
|
|
3250
|
+
const out = {};
|
|
3251
|
+
for (const [key2, item] of Object.entries(inner)) {
|
|
3252
|
+
Object.defineProperty(out, key2, { value: await hydrate(item, registry), enumerable: true, writable: true, configurable: true });
|
|
3253
|
+
}
|
|
3254
|
+
return out;
|
|
3255
|
+
}
|
|
3256
|
+
case "mdl":
|
|
3257
|
+
return modelClass(registry, meta.class).findOrFail(meta.key);
|
|
3258
|
+
case "fil":
|
|
3259
|
+
if (!isTemporaryFileData(inner)) throw new LiveError("Unable to hydrate a temporary upload: its data is incomplete.");
|
|
3260
|
+
return new TemporaryUploadedFile(inner);
|
|
3261
|
+
case "elcln": {
|
|
3262
|
+
const cls = modelClass(registry, meta.class);
|
|
3263
|
+
const keys = meta.keys ?? [];
|
|
3264
|
+
const model = new cls();
|
|
3265
|
+
const keyName = model.getKeyName();
|
|
3266
|
+
const found = [...await cls.query().whereIn(keyName, keys).get()];
|
|
3267
|
+
const byKey = new Map(found.map((item) => [String(item.getKey()), item]));
|
|
3268
|
+
const models = keys.map((key2) => byKey.get(String(key2))).filter((item) => item !== void 0);
|
|
3269
|
+
return model.newCollection ? model.newCollection(models) : new Collection(models);
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
throw new LiveError(`Unknown snapshot value type [${String(meta.s)}].`);
|
|
3273
|
+
}
|
|
3274
|
+
var HOOKS = /* @__PURE__ */ new Set(["constructor", "render", "placeholder", "mount", "boot", "booted", "hydrate", "dehydrate", "updating", "updated", "rendering", "rendered", "exception"]);
|
|
3275
|
+
var HOOK_PREFIX = /^(updating|updated|hydrate|dehydrate)[A-Z_]/;
|
|
3276
|
+
var UNSAFE_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
3277
|
+
var baseApi;
|
|
3278
|
+
var isBaseApi = (name) => (baseApi ??= new Set(Object.getOwnPropertyNames(Component.prototype))).has(name);
|
|
3279
|
+
var INDEX = /^(?:0|[1-9]\d*)$/;
|
|
3280
|
+
function isArraySlot(array, segment) {
|
|
3281
|
+
return INDEX.test(segment) && Number(segment) <= array.length;
|
|
3282
|
+
}
|
|
3283
|
+
function isCallable(instance, method) {
|
|
3284
|
+
if (method === "" || method.startsWith("_") || method.startsWith("$") || method.startsWith("#")) return false;
|
|
3285
|
+
if (HOOKS.has(method) || HOOK_PREFIX.test(method) || isBaseApi(method)) return false;
|
|
3286
|
+
if (lockedOf(instance.constructor).has(method)) return false;
|
|
3287
|
+
const isBase = (proto) => proto === Component.prototype || proto === Object.prototype;
|
|
3288
|
+
for (let proto = Object.getPrototypeOf(instance); proto !== null && !isBase(proto); proto = Object.getPrototypeOf(proto)) {
|
|
3289
|
+
const descriptor = Object.getOwnPropertyDescriptor(proto, method);
|
|
3290
|
+
if (descriptor !== void 0) return typeof descriptor.value === "function";
|
|
3291
|
+
}
|
|
3292
|
+
return false;
|
|
3293
|
+
}
|
|
3294
|
+
function isUpdatable(instance, path) {
|
|
3295
|
+
const segments = path.split(".");
|
|
3296
|
+
const root = segments[0];
|
|
3297
|
+
if (root === "" || root.startsWith("_") || segments.some((segment) => segment === "" || UNSAFE_SEGMENTS.has(segment))) return false;
|
|
3298
|
+
if (!Object.hasOwn(instance, root)) return false;
|
|
3299
|
+
const descriptor = Object.getOwnPropertyDescriptor(instance, root);
|
|
3300
|
+
if (!descriptor.enumerable || descriptor.get !== void 0) return false;
|
|
3301
|
+
const cls = instance.constructor;
|
|
3302
|
+
if (lockedOf(cls).has(root) || computedOf(cls).has(root)) return false;
|
|
3303
|
+
let node = descriptor.value;
|
|
3304
|
+
for (const segment of segments.slice(1)) {
|
|
3305
|
+
if (isModel(node) || node instanceof Collection) return false;
|
|
3306
|
+
if (Array.isArray(node) && !isArraySlot(node, segment)) return false;
|
|
3307
|
+
if (typeof node !== "object" || node === null || !Object.hasOwn(node, segment)) return true;
|
|
3308
|
+
node = node[segment];
|
|
3309
|
+
}
|
|
3310
|
+
return !isModel(node) && !(node instanceof Collection);
|
|
3311
|
+
}
|
|
2584
3312
|
var COMPONENT = /* @__PURE__ */ Symbol.for("oriel:live.component");
|
|
2585
3313
|
var states = /* @__PURE__ */ new WeakMap();
|
|
2586
3314
|
function stateOf(component) {
|
|
@@ -2596,14 +3324,24 @@ var ErrorBag = class {
|
|
|
2596
3324
|
this.messages = messages;
|
|
2597
3325
|
}
|
|
2598
3326
|
messages;
|
|
2599
|
-
|
|
2600
|
-
|
|
3327
|
+
/** The field's own messages: a field named `constructor` or `toString` doesn't reach Object's. */
|
|
3328
|
+
#own(key2) {
|
|
3329
|
+
return Object.hasOwn(this.messages, key2) ? this.messages[key2] : void 0;
|
|
3330
|
+
}
|
|
3331
|
+
/** Adds to the component's errors, as addError() does. */
|
|
3332
|
+
add(key2, message) {
|
|
3333
|
+
const messages = this.#own(key2);
|
|
3334
|
+
if (messages !== void 0) messages.push(message);
|
|
3335
|
+
else Object.defineProperty(this.messages, key2, { value: [message], enumerable: true, writable: true, configurable: true });
|
|
2601
3336
|
}
|
|
2602
|
-
|
|
2603
|
-
return
|
|
3337
|
+
has(key2) {
|
|
3338
|
+
return (this.#own(key2)?.length ?? 0) > 0;
|
|
2604
3339
|
}
|
|
2605
|
-
|
|
2606
|
-
return
|
|
3340
|
+
first(key2) {
|
|
3341
|
+
return key2 === void 0 ? this.all()[0] : this.#own(key2)?.[0];
|
|
3342
|
+
}
|
|
3343
|
+
get(key2) {
|
|
3344
|
+
return [...this.#own(key2) ?? []];
|
|
2607
3345
|
}
|
|
2608
3346
|
all() {
|
|
2609
3347
|
return Object.values(this.messages).flat();
|
|
@@ -2618,7 +3356,7 @@ var ErrorBag = class {
|
|
|
2618
3356
|
return this.all().length;
|
|
2619
3357
|
}
|
|
2620
3358
|
toObject() {
|
|
2621
|
-
return Object.fromEntries(Object.entries(this.messages).map(([
|
|
3359
|
+
return Object.fromEntries(Object.entries(this.messages).map(([key2, messages]) => [key2, [...messages]]));
|
|
2622
3360
|
}
|
|
2623
3361
|
};
|
|
2624
3362
|
var PendingEvent = class {
|
|
@@ -2645,6 +3383,13 @@ var Component = class {
|
|
|
2645
3383
|
static layout;
|
|
2646
3384
|
/** The page title passed to the layout as `title`. */
|
|
2647
3385
|
static title;
|
|
3386
|
+
/**
|
|
3387
|
+
* Livewire's #[Lazy]: every embed (and the full page) renders the placeholder first and mounts once the browser shows
|
|
3388
|
+
* it. `<Live component={…} lazy={false} />` mounts one at once. The placeholder is the component's
|
|
3389
|
+
* `placeholder(params)` method (TSX), else the `live.lazy_placeholder` view, else an empty `<div>`: give it the root
|
|
3390
|
+
* element the component renders.
|
|
3391
|
+
*/
|
|
3392
|
+
static lazy;
|
|
2648
3393
|
/** Static fallbacks for runtimes without decorators: { event: 'method' }, locked names, computed getter names. */
|
|
2649
3394
|
static listeners;
|
|
2650
3395
|
static locked;
|
|
@@ -2668,12 +3413,18 @@ var Component = class {
|
|
|
2668
3413
|
if (options.navigate === true) state.redirectUsingNavigate = true;
|
|
2669
3414
|
else delete state.redirectUsingNavigate;
|
|
2670
3415
|
}
|
|
2671
|
-
|
|
3416
|
+
/**
|
|
3417
|
+
* redirect() to a named route: `this.redirectRoute('posts.show', post, { navigate: true })`. Once `oriel
|
|
3418
|
+
* route:types` has run, the name and the route's parameters are checked, as route()'s are.
|
|
3419
|
+
*/
|
|
3420
|
+
redirectRoute(name, ...args) {
|
|
3421
|
+
const [parameters, options = {}] = args;
|
|
2672
3422
|
this.redirect(inject(UrlGenerator).route(name, parameters), options);
|
|
2673
3423
|
}
|
|
2674
3424
|
/**
|
|
2675
|
-
* Validates the public state. On success clears the error bag and returns the validated data
|
|
2676
|
-
*
|
|
3425
|
+
* Validates the public state. On success clears the error bag and returns the validated data, typed from the rules
|
|
3426
|
+
* (`await this.validate({ title: 'required|string' })` is `{ title: string }`); on failure fills the error bag and
|
|
3427
|
+
* throws (the rest of the action is skipped and the component re-renders with `errors`).
|
|
2677
3428
|
*/
|
|
2678
3429
|
async validate(rules, messages, attributes) {
|
|
2679
3430
|
const validator = inject(ValidationFactory).make(Object.fromEntries(Object.entries(this)), rules, messages, attributes);
|
|
@@ -2687,15 +3438,14 @@ var Component = class {
|
|
|
2687
3438
|
throw error;
|
|
2688
3439
|
}
|
|
2689
3440
|
}
|
|
2690
|
-
addError(
|
|
2691
|
-
;
|
|
2692
|
-
(stateOf(this).errors[key] ??= []).push(message);
|
|
3441
|
+
addError(key2, message) {
|
|
3442
|
+
this.errors.add(key2, message);
|
|
2693
3443
|
}
|
|
2694
3444
|
/** Clears every error, or only those of `keys`. */
|
|
2695
3445
|
resetErrorBag(...keys) {
|
|
2696
3446
|
const state = stateOf(this);
|
|
2697
3447
|
if (keys.length === 0) state.errors = {};
|
|
2698
|
-
else for (const
|
|
3448
|
+
else for (const key2 of keys) delete state.errors[key2];
|
|
2699
3449
|
}
|
|
2700
3450
|
get errors() {
|
|
2701
3451
|
return new ErrorBag(stateOf(this).errors);
|
|
@@ -2711,6 +3461,23 @@ var Component = class {
|
|
|
2711
3461
|
skipRender() {
|
|
2712
3462
|
stateOf(this).skipRender = true;
|
|
2713
3463
|
}
|
|
3464
|
+
/**
|
|
3465
|
+
* An action for an `o:` directive, typed against its method: `o:click={this.$action(this.delete, tag.id)}` writes
|
|
3466
|
+
* `delete(5)`, and a string where `delete` takes a number is a type error. The arguments are written as JSON, the
|
|
3467
|
+
* trailing `undefined` ones left out so the method's defaults apply. The method must be one of this component's
|
|
3468
|
+
* actions (a public method of its class, not a hook and not @Locked); anything else throws while rendering.
|
|
3469
|
+
*/
|
|
3470
|
+
$action(method, ...params) {
|
|
3471
|
+
const name = typeof method === "function" ? method.name : "";
|
|
3472
|
+
if (name === "" || this[name] !== method || !isCallable(this, name)) {
|
|
3473
|
+
throw new LiveError(
|
|
3474
|
+
`[${name === "" ? "anonymous function" : name}] is not an action of [${this.getName() || this.constructor.name}]: pass one of its public methods, as in this.$action(this.save).`
|
|
3475
|
+
);
|
|
3476
|
+
}
|
|
3477
|
+
let count = params.length;
|
|
3478
|
+
while (count > 0 && params[count - 1] === void 0) count--;
|
|
3479
|
+
return count === 0 ? name : `${name}(${params.slice(0, count).map((param) => JSON.stringify(param) ?? "null").join(", ")})`;
|
|
3480
|
+
}
|
|
2714
3481
|
/** A full-page component as a route action: Route.get('/counter', Counter). Route parameters go to mount(). */
|
|
2715
3482
|
async __invoke() {
|
|
2716
3483
|
return inject(LiveManager).page(this);
|
|
@@ -2722,60 +3489,21 @@ function trackFlash(session, { saved = true } = {}) {
|
|
|
2722
3489
|
return (redirected) => {
|
|
2723
3490
|
if (redirected) return;
|
|
2724
3491
|
const flashed = session.get("_flash.new", []);
|
|
2725
|
-
const fresh = flashed.filter((
|
|
3492
|
+
const fresh = flashed.filter((key2) => !before.has(key2));
|
|
2726
3493
|
if (fresh.length === 0) return;
|
|
2727
|
-
session.put("_flash.new", flashed.filter((
|
|
3494
|
+
session.put("_flash.new", flashed.filter((key2) => before.has(key2)));
|
|
2728
3495
|
if (!saved) return session.forget(...fresh);
|
|
2729
3496
|
const old = session.get("_flash.old", []);
|
|
2730
|
-
session.put("_flash.old", [...old, ...fresh.filter((
|
|
3497
|
+
session.put("_flash.old", [...old, ...fresh.filter((key2) => !old.includes(key2))]);
|
|
2731
3498
|
};
|
|
2732
3499
|
}
|
|
2733
|
-
var LiveError = class extends Error {
|
|
2734
|
-
name = "LiveError";
|
|
2735
|
-
kind = "Server";
|
|
2736
|
-
status = 500;
|
|
2737
|
-
};
|
|
2738
|
-
var CorruptComponentPayloadError = class extends LiveError {
|
|
2739
|
-
name = "CorruptComponentPayloadError";
|
|
2740
|
-
kind = "Authorization";
|
|
2741
|
-
status = 403;
|
|
2742
|
-
constructor(component = "unknown") {
|
|
2743
|
-
super(`Live encountered corrupt data when trying to hydrate the [${component}] component.`);
|
|
2744
|
-
}
|
|
2745
|
-
};
|
|
2746
|
-
var MethodNotCallableError = class extends LiveError {
|
|
2747
|
-
name = "MethodNotCallableError";
|
|
2748
|
-
kind = "Authorization";
|
|
2749
|
-
status = 403;
|
|
2750
|
-
constructor(method, component) {
|
|
2751
|
-
super(`Unable to call component method. Public method [${method}] not found on component [${component}].`);
|
|
2752
|
-
}
|
|
2753
|
-
};
|
|
2754
|
-
var PropertyNotUpdatableError = class extends LiveError {
|
|
2755
|
-
name = "PropertyNotUpdatableError";
|
|
2756
|
-
kind = "Authorization";
|
|
2757
|
-
status = 403;
|
|
2758
|
-
constructor(path, component) {
|
|
2759
|
-
super(`Cannot update property [${path}] on component [${component}]: it is not a public, unlocked property.`);
|
|
2760
|
-
}
|
|
2761
|
-
};
|
|
2762
|
-
var LiveComponentNotFoundError = class extends LiveError {
|
|
2763
|
-
name = "LiveComponentNotFoundError";
|
|
2764
|
-
kind = "Http";
|
|
2765
|
-
status = 404;
|
|
2766
|
-
constructor(component) {
|
|
2767
|
-
super(`Unable to find component: [${component}].`);
|
|
2768
|
-
}
|
|
2769
|
-
};
|
|
2770
|
-
var InvalidLiveRequestError = class extends LiveError {
|
|
2771
|
-
name = "InvalidLiveRequestError";
|
|
2772
|
-
kind = "Http";
|
|
2773
|
-
status = 400;
|
|
2774
|
-
};
|
|
2775
3500
|
var ESCAPES = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
2776
3501
|
function escapeAttribute2(value) {
|
|
2777
3502
|
return value.replace(/[&<>"']/g, (char) => ESCAPES[char]);
|
|
2778
3503
|
}
|
|
3504
|
+
function stripTags(html) {
|
|
3505
|
+
return html.replace(/<[^>]*>/g, "");
|
|
3506
|
+
}
|
|
2779
3507
|
var VOID = /* @__PURE__ */ new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr"]);
|
|
2780
3508
|
var RAW_TEXT = /* @__PURE__ */ new Set(["script", "style", "textarea", "title"]);
|
|
2781
3509
|
var TOKEN = /<\/([A-Za-z][\w:-]*)\s*>|<([A-Za-z][\w:-]*)(?:\s+[^\s"'<>/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*\s*(\/?)>/g;
|
|
@@ -2811,15 +3539,245 @@ function rootTag(html, component) {
|
|
|
2811
3539
|
if (depth === 0) rootEnd = match.index + token2.length;
|
|
2812
3540
|
}
|
|
2813
3541
|
}
|
|
2814
|
-
if (root === void 0 || depth !== 0 || source.slice(rootEnd).trim() !== "") fail22();
|
|
2815
|
-
return root;
|
|
3542
|
+
if (root === void 0 || depth !== 0 || source.slice(rootEnd).trim() !== "") fail22();
|
|
3543
|
+
return root;
|
|
3544
|
+
}
|
|
3545
|
+
function insertAttributesIntoRoot(html, attributes) {
|
|
3546
|
+
const source = html.trim();
|
|
3547
|
+
const match = /^((?:<!--[\s\S]*?-->\s*)*<[A-Za-z][\w:-]*)/.exec(source);
|
|
3548
|
+
if (match === null) throw new LiveError("Live could not find the root element of the component.");
|
|
3549
|
+
const rendered = Object.entries(attributes).map(([name, value]) => ` ${name}="${escapeAttribute2(value)}"`).join("");
|
|
3550
|
+
return match[1] + rendered + source.slice(match[1].length);
|
|
3551
|
+
}
|
|
3552
|
+
function canonicalJson(value) {
|
|
3553
|
+
if (Array.isArray(value)) return `[${value.map((item) => canonicalJson(item === void 0 ? null : item)).join(",")}]`;
|
|
3554
|
+
if (typeof value === "object" && value !== null) {
|
|
3555
|
+
const entries = Object.keys(value).sort().filter((key2) => value[key2] !== void 0).map((key2) => `${JSON.stringify(key2)}:${canonicalJson(value[key2])}`);
|
|
3556
|
+
return `{${entries.join(",")}}`;
|
|
3557
|
+
}
|
|
3558
|
+
return JSON.stringify(value);
|
|
3559
|
+
}
|
|
3560
|
+
function checksum(key2, payload) {
|
|
3561
|
+
return createHmac("sha256", key2).update(canonicalJson({ data: payload.data, memo: payload.memo })).digest("hex");
|
|
3562
|
+
}
|
|
3563
|
+
function verifyChecksum(key2, payload, given) {
|
|
3564
|
+
if (typeof given !== "string") return false;
|
|
3565
|
+
const expected = Buffer.from(checksum(key2, payload), "utf8");
|
|
3566
|
+
const actual = Buffer.from(given, "utf8");
|
|
3567
|
+
return expected.length === actual.length && timingSafeEqual(expected, actual);
|
|
3568
|
+
}
|
|
3569
|
+
function signatureOf(key2, purpose, payload) {
|
|
3570
|
+
return createHmac("sha256", key2).update(`${purpose}|${payload}`).digest("base64url");
|
|
3571
|
+
}
|
|
3572
|
+
function signValue(key2, purpose, value) {
|
|
3573
|
+
const payload = Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
3574
|
+
return `${payload}.${signatureOf(key2, purpose, payload)}`;
|
|
3575
|
+
}
|
|
3576
|
+
function verifiedValue(key2, purpose, token2) {
|
|
3577
|
+
if (typeof token2 !== "string") return void 0;
|
|
3578
|
+
const dot = token2.lastIndexOf(".");
|
|
3579
|
+
if (dot <= 0) return void 0;
|
|
3580
|
+
const payload = token2.slice(0, dot);
|
|
3581
|
+
const expected = Buffer.from(signatureOf(key2, purpose, payload), "utf8");
|
|
3582
|
+
const given = Buffer.from(token2.slice(dot + 1), "utf8");
|
|
3583
|
+
if (expected.length !== given.length || !timingSafeEqual(expected, given)) return void 0;
|
|
3584
|
+
try {
|
|
3585
|
+
return JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
3586
|
+
} catch {
|
|
3587
|
+
return void 0;
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
var UPLOAD_URI = "/oriel/live/upload-file";
|
|
3591
|
+
var PREVIEW_URI = "/oriel/live/preview-file";
|
|
3592
|
+
var REFERENCE = "live.upload";
|
|
3593
|
+
var DAY = 24 * 36e5;
|
|
3594
|
+
var PREVIEW_TYPES = {
|
|
3595
|
+
png: "image/png",
|
|
3596
|
+
gif: "image/gif",
|
|
3597
|
+
bmp: "image/bmp",
|
|
3598
|
+
svg: "image/svg+xml",
|
|
3599
|
+
jpg: "image/jpeg",
|
|
3600
|
+
jpeg: "image/jpeg",
|
|
3601
|
+
webp: "image/webp",
|
|
3602
|
+
avif: "image/avif",
|
|
3603
|
+
wav: "audio/wav",
|
|
3604
|
+
mp3: "audio/mpeg",
|
|
3605
|
+
mpga: "audio/mpeg",
|
|
3606
|
+
m4a: "audio/mp4",
|
|
3607
|
+
wma: "audio/x-ms-wma",
|
|
3608
|
+
mp4: "video/mp4",
|
|
3609
|
+
mov: "video/quicktime",
|
|
3610
|
+
avi: "video/x-msvideo",
|
|
3611
|
+
wmv: "video/x-ms-wmv"
|
|
3612
|
+
};
|
|
3613
|
+
var key = () => inject(Encrypter).getKey();
|
|
3614
|
+
function temporaryFileReference(file) {
|
|
3615
|
+
return signValue(key(), REFERENCE, file.toData());
|
|
3616
|
+
}
|
|
3617
|
+
function temporaryFileFromReference(reference, component) {
|
|
3618
|
+
const data = verifiedValue(key(), REFERENCE, reference);
|
|
3619
|
+
if (!isTemporaryFileData(data)) throw new CorruptComponentPayloadError(component);
|
|
3620
|
+
return new TemporaryUploadedFile(data);
|
|
3621
|
+
}
|
|
3622
|
+
function uploadRules() {
|
|
3623
|
+
const rules = uploadSetting("rules");
|
|
3624
|
+
return Array.isArray(rules) ? [...rules] : rules.split("|");
|
|
3625
|
+
}
|
|
3626
|
+
function uploadMiddleware() {
|
|
3627
|
+
const configured = [uploadSetting("middleware")].flat();
|
|
3628
|
+
return configured.includes("web") ? configured : ["web", ...configured];
|
|
3629
|
+
}
|
|
3630
|
+
async function storeTemporaryFiles(files) {
|
|
3631
|
+
const disk = temporaryDisk();
|
|
3632
|
+
const directory = temporaryDirectory();
|
|
3633
|
+
return Promise.all(
|
|
3634
|
+
files.map(async (file) => {
|
|
3635
|
+
const extension = file.extension();
|
|
3636
|
+
const filename = extension === "" ? Str.random(40) : `${Str.random(40)}.${extension}`;
|
|
3637
|
+
await disk.putFileAs(directory, file, filename);
|
|
3638
|
+
return new TemporaryUploadedFile({ filename, name: file.clientName, size: file.size, type: file.mimeType, extension });
|
|
3639
|
+
})
|
|
3640
|
+
);
|
|
3641
|
+
}
|
|
3642
|
+
async function uploadedFileFrom(file) {
|
|
3643
|
+
const detected = fileType_exports.detectFileType(new Uint8Array(await file.slice(0, fileType_exports.SNIFF_BYTES).arrayBuffer()));
|
|
3644
|
+
const name = file.name ?? "";
|
|
3645
|
+
const claimed = name.includes(".") ? name.slice(name.lastIndexOf(".") + 1).toLowerCase() : "";
|
|
3646
|
+
const recognised = new Set(fileType_exports.BINARY_AND_MARKUP_TYPES.flatMap(({ extensions }) => extensions));
|
|
3647
|
+
const extension = detected !== fileType_exports.TEXT && detected.extensions.length > 0 ? detected.extensions[0] : /^[a-z0-9]+$/.test(claimed) && !recognised.has(claimed) ? claimed : detected === fileType_exports.TEXT ? "txt" : "";
|
|
3648
|
+
return {
|
|
3649
|
+
clientName: name,
|
|
3650
|
+
mimeType: file.type === "" ? "application/octet-stream" : file.type,
|
|
3651
|
+
size: file.size,
|
|
3652
|
+
extension: () => extension,
|
|
3653
|
+
clientExtension: () => claimed,
|
|
3654
|
+
hashName: () => extension === "" ? Str.random(40) : `${Str.random(40)}.${extension}`,
|
|
3655
|
+
arrayBuffer: () => file.arrayBuffer(),
|
|
3656
|
+
text: () => file.text(),
|
|
3657
|
+
stream: () => file.stream(),
|
|
3658
|
+
store: () => Promise.reject(new Error("Store the temporary upload instead.")),
|
|
3659
|
+
storeAs: () => Promise.reject(new Error("Store the temporary upload instead."))
|
|
3660
|
+
};
|
|
3661
|
+
}
|
|
3662
|
+
function filesOf(request2) {
|
|
3663
|
+
const files = request2.all().files;
|
|
3664
|
+
if (files === void 0 || files === null) return [];
|
|
3665
|
+
if (Array.isArray(files)) return files;
|
|
3666
|
+
return typeof files === "object" && Object.getPrototypeOf(files) === Object.prototype ? Object.values(files) : [files];
|
|
3667
|
+
}
|
|
3668
|
+
async function handleUpload(request2) {
|
|
3669
|
+
if (!inject(UrlGenerator).hasValidSignature(request2, false)) throw new InvalidUploadSignatureError();
|
|
3670
|
+
const files = filesOf(request2);
|
|
3671
|
+
await inject(ValidationFactory).make({ files }, { "files.*": uploadRules() }).validated();
|
|
3672
|
+
const stored = await storeTemporaryFiles(files);
|
|
3673
|
+
return { paths: stored.map(temporaryFileReference) };
|
|
3674
|
+
}
|
|
3675
|
+
async function handlePreview(request2, filename) {
|
|
3676
|
+
if (!inject(UrlGenerator).hasValidSignature(request2, false)) throw new InvalidUploadSignatureError();
|
|
3677
|
+
const data = { filename, name: "", size: 0, type: "", extension: filename.includes(".") ? filename.slice(filename.lastIndexOf(".") + 1) : "" };
|
|
3678
|
+
if (!isTemporaryFileData(data)) throw new TemporaryFileNotFoundError(filename);
|
|
3679
|
+
const file = new TemporaryUploadedFile(data);
|
|
3680
|
+
const disk = temporaryDisk();
|
|
3681
|
+
if (!await disk.exists(file.path)) throw new TemporaryFileNotFoundError(filename);
|
|
3682
|
+
const bytes = await disk.getBytes(file.path);
|
|
3683
|
+
return new Response(bytes, {
|
|
3684
|
+
headers: {
|
|
3685
|
+
"Content-Type": PREVIEW_TYPES[data.extension] ?? "application/octet-stream",
|
|
3686
|
+
"Content-Length": String(bytes.byteLength),
|
|
3687
|
+
"Content-Disposition": `attachment; filename="${filename}"`,
|
|
3688
|
+
"Content-Security-Policy": "sandbox; default-src 'none'",
|
|
3689
|
+
"X-Content-Type-Options": "nosniff",
|
|
3690
|
+
"Cache-Control": "private, max-age=3600"
|
|
3691
|
+
}
|
|
3692
|
+
});
|
|
3693
|
+
}
|
|
3694
|
+
async function cleanupOldUploads() {
|
|
3695
|
+
if (uploadSetting("cleanup") === false) return;
|
|
3696
|
+
const config = inject(ConfigRepository);
|
|
3697
|
+
const disk = uploadSetting("disk") ?? config.get("filesystems.default", "local");
|
|
3698
|
+
if (config.get(`filesystems.disks.${disk}.driver`, void 0) === "s3") return;
|
|
3699
|
+
const storage = temporaryDisk();
|
|
3700
|
+
const cutoff = Date.now() - DAY;
|
|
3701
|
+
for (const path of await storage.files(temporaryDirectory())) {
|
|
3702
|
+
try {
|
|
3703
|
+
if ((await storage.lastModified(path)).getTime() < cutoff) await storage.delete(path);
|
|
3704
|
+
} catch {
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
var UPLOADS = /* @__PURE__ */ Symbol.for("oriel:live.uploads");
|
|
3709
|
+
var UPLOAD_CALLS = /* @__PURE__ */ new Set(["_startUpload", "_finishUpload", "_uploadErrored", "_removeUpload"]);
|
|
3710
|
+
function WithFileUploads(Base) {
|
|
3711
|
+
class WithFileUploadsMixin extends Base {
|
|
3712
|
+
}
|
|
3713
|
+
Object.defineProperty(WithFileUploadsMixin.prototype, UPLOADS, { value: true });
|
|
3714
|
+
return WithFileUploadsMixin;
|
|
3715
|
+
}
|
|
3716
|
+
function usesFileUploads(component) {
|
|
3717
|
+
return component[UPLOADS] === true;
|
|
3718
|
+
}
|
|
3719
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3720
|
+
var read = (component, path) => path.split(".").reduce((node, segment) => isRecord(node) || Array.isArray(node) ? node[segment] : void 0, component);
|
|
3721
|
+
function uploadErrors(name, errors, multiple) {
|
|
3722
|
+
if (typeof errors === "string") {
|
|
3723
|
+
try {
|
|
3724
|
+
const replaced = errors.replace(multiple ? /files/gi : /files\.0/gi, name);
|
|
3725
|
+
const bag = JSON.parse(replaced).errors;
|
|
3726
|
+
if (isRecord(bag) && Object.values(bag).every((messages) => Array.isArray(messages) && messages.every((message) => typeof message === "string"))) {
|
|
3727
|
+
return bag;
|
|
3728
|
+
}
|
|
3729
|
+
} catch {
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
return { [name]: [`The ${name} failed to upload.`] };
|
|
3733
|
+
}
|
|
3734
|
+
async function runUploadCall(component, method, params, update) {
|
|
3735
|
+
if (!usesFileUploads(component)) {
|
|
3736
|
+
throw new LiveError(`Cannot handle file upload without [WithFileUploads] on the [${stateOf(component).name}] component class.`);
|
|
3737
|
+
}
|
|
3738
|
+
const [name, value, multiple] = [String(params[0] ?? ""), params[1], params[2] === true];
|
|
3739
|
+
switch (method) {
|
|
3740
|
+
case "_startUpload":
|
|
3741
|
+
component.skipRender();
|
|
3742
|
+
return inject(UrlGenerator).temporarySignedRoute("live.upload-file", uploadSetting("max_upload_time") * 60, void 0, false);
|
|
3743
|
+
case "_finishUpload": {
|
|
3744
|
+
await cleanupOldUploads();
|
|
3745
|
+
const files = (Array.isArray(value) ? value : []).map((reference) => temporaryFileFromReference(reference, stateOf(component).name));
|
|
3746
|
+
const current = read(component, name);
|
|
3747
|
+
const next = multiple ? files : Array.isArray(current) ? [...current, ...files] : files[0] ?? null;
|
|
3748
|
+
await update(name, next);
|
|
3749
|
+
return files.map((file) => file.filename);
|
|
3750
|
+
}
|
|
3751
|
+
case "_uploadErrored":
|
|
3752
|
+
throw new UploadValidationError(uploadErrors(name, value, multiple));
|
|
3753
|
+
case "_removeUpload": {
|
|
3754
|
+
const filename = String(value ?? "");
|
|
3755
|
+
const current = read(component, name);
|
|
3756
|
+
if (Array.isArray(current)) {
|
|
3757
|
+
const removed = current.filter((file) => file instanceof TemporaryUploadedFile && file.filename === filename);
|
|
3758
|
+
for (const file of removed) await file.delete();
|
|
3759
|
+
await update(
|
|
3760
|
+
name,
|
|
3761
|
+
current.filter((file) => !removed.includes(file))
|
|
3762
|
+
);
|
|
3763
|
+
} else if (current instanceof TemporaryUploadedFile && current.filename === filename) {
|
|
3764
|
+
await current.delete();
|
|
3765
|
+
await update(name, null);
|
|
3766
|
+
}
|
|
3767
|
+
return filename;
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
throw new LiveError(`Unknown upload call [${method}].`);
|
|
2816
3771
|
}
|
|
2817
|
-
function
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
3772
|
+
async function uploadForTest(files) {
|
|
3773
|
+
try {
|
|
3774
|
+
await inject(ValidationFactory).make({ files: [...files] }, { "files.*": uploadRules() }).validated();
|
|
3775
|
+
} catch (error) {
|
|
3776
|
+
const errors = error.errors;
|
|
3777
|
+
if (error.kind !== "Validation" || !isRecord(errors)) throw error;
|
|
3778
|
+
return { errors: JSON.stringify({ errors }) };
|
|
3779
|
+
}
|
|
3780
|
+
return { references: (await storeTemporaryFiles(files)).map(temporaryFileReference) };
|
|
2823
3781
|
}
|
|
2824
3782
|
var fail = (message, actual, expected) => {
|
|
2825
3783
|
throw new AssertionError({ message, actual, expected, operator: "live" });
|
|
@@ -2827,14 +3785,14 @@ var fail = (message, actual, expected) => {
|
|
|
2827
3785
|
var getPath = (target2, path) => path.split(".").reduce((node, segment) => typeof node === "object" && node !== null ? node[segment] : void 0, target2);
|
|
2828
3786
|
var TestRequestScope = token("Testing.RequestScope");
|
|
2829
3787
|
function requestScope2() {
|
|
2830
|
-
const
|
|
2831
|
-
return
|
|
3788
|
+
const container2 = currentContainer() ?? Container.getInstance();
|
|
3789
|
+
return container2?.bound(TestRequestScope) === true ? container2.make(TestRequestScope) : (run) => run();
|
|
2832
3790
|
}
|
|
2833
3791
|
function currentSession() {
|
|
2834
|
-
const
|
|
2835
|
-
if (
|
|
3792
|
+
const container2 = currentContainer();
|
|
3793
|
+
if (container2?.bound(Session) !== true) return void 0;
|
|
2836
3794
|
try {
|
|
2837
|
-
return
|
|
3795
|
+
return container2.make(Session);
|
|
2838
3796
|
} catch {
|
|
2839
3797
|
return void 0;
|
|
2840
3798
|
}
|
|
@@ -2854,11 +3812,14 @@ var Testable = class _Testable {
|
|
|
2854
3812
|
#html;
|
|
2855
3813
|
#effects;
|
|
2856
3814
|
#instance;
|
|
2857
|
-
/**
|
|
3815
|
+
/**
|
|
3816
|
+
* Mounts `component` with `params`, as `<Live component={…} {...params} />` would. A lazy component mounts at once
|
|
3817
|
+
* (Livewire's withoutLazyLoading()); `{ lazy: true }` renders its placeholder, and `call('__lazyLoad', …)` loads it.
|
|
3818
|
+
*/
|
|
2858
3819
|
static async create(component, params = {}) {
|
|
2859
3820
|
const scope = requestScope2();
|
|
2860
3821
|
const manager = inject(LiveManager);
|
|
2861
|
-
return new _Testable(manager, scope, await scope(() => manager.mountComponent(component, params)));
|
|
3822
|
+
return new _Testable(manager, scope, await scope(() => manager.mountComponent(component, { lazy: false, ...params })));
|
|
2862
3823
|
}
|
|
2863
3824
|
/** The component instance after the last request. */
|
|
2864
3825
|
instance() {
|
|
@@ -2876,9 +3837,24 @@ var Testable = class _Testable {
|
|
|
2876
3837
|
effects() {
|
|
2877
3838
|
return this.#effects;
|
|
2878
3839
|
}
|
|
3840
|
+
/**
|
|
3841
|
+
* Sets public properties through an update. A file (a FakeFile) or an array of files is uploaded as `o:model` on a
|
|
3842
|
+
* file input does it: the upload rules, the temporary disk, then a TemporaryUploadedFile (or an array) in the
|
|
3843
|
+
* property, or the upload's errors on it (Livewire's).
|
|
3844
|
+
*/
|
|
2879
3845
|
async set(path, value) {
|
|
3846
|
+
const files = [value].flat();
|
|
3847
|
+
if (typeof path === "string" && files.length > 0 && files.every((file) => file instanceof Blob)) return this.upload(path, files, Array.isArray(value));
|
|
2880
3848
|
return this.#update(typeof path === "string" ? { [path]: value } : path, []);
|
|
2881
3849
|
}
|
|
3850
|
+
/** Uploads `files` into the property `name` (an array when `multiple`), as the browser's upload calls do. */
|
|
3851
|
+
async upload(name, files, multiple = false) {
|
|
3852
|
+
const info = files.map((file) => ({ name: file.name ?? "", size: file.size, type: file.type }));
|
|
3853
|
+
await this.#update({}, [{ method: "_startUpload", params: [name, info, multiple] }]);
|
|
3854
|
+
const outcome = await this.scope(async () => uploadForTest(await Promise.all(files.map((file) => uploadedFileFrom(file)))));
|
|
3855
|
+
if ("errors" in outcome) return this.#update({}, [{ method: "_uploadErrored", params: [name, outcome.errors, multiple] }]);
|
|
3856
|
+
return this.#update({}, [{ method: "_finishUpload", params: [name, outcome.references, multiple] }]);
|
|
3857
|
+
}
|
|
2882
3858
|
async call(method, ...params) {
|
|
2883
3859
|
return this.#update({}, [{ method, params }]);
|
|
2884
3860
|
}
|
|
@@ -2928,6 +3904,17 @@ var Testable = class _Testable {
|
|
|
2928
3904
|
for (const value of values) if (!this.html().includes(value)) fail(`Failed asserting that [${value}] is in the rendered HTML.`, this.html(), value);
|
|
2929
3905
|
return this;
|
|
2930
3906
|
}
|
|
3907
|
+
/** Each value, HTML-escaped, appears in the rendered text: the HTML with its tags stripped (Livewire's assertSeeText). */
|
|
3908
|
+
assertSeeText(...values) {
|
|
3909
|
+
const text = stripTags(this.html());
|
|
3910
|
+
for (const value of values) if (!text.includes(escapeAttribute2(value))) fail(`Failed asserting that [${value}] is in the rendered text.`, text, value);
|
|
3911
|
+
return this;
|
|
3912
|
+
}
|
|
3913
|
+
assertDontSeeText(...values) {
|
|
3914
|
+
const text = stripTags(this.html());
|
|
3915
|
+
for (const value of values) if (text.includes(escapeAttribute2(value))) fail(`Failed asserting that [${value}] is not in the rendered text.`, text, value);
|
|
3916
|
+
return this;
|
|
3917
|
+
}
|
|
2931
3918
|
assertDispatched(name, params) {
|
|
2932
3919
|
const dispatched = (this.#effects.dispatches ?? []).filter((event) => event.name === name);
|
|
2933
3920
|
if (dispatched.length === 0) fail(`Failed asserting that an event [${name}] was dispatched.`, this.#effects.dispatches ?? [], name);
|
|
@@ -2945,14 +3932,14 @@ var Testable = class _Testable {
|
|
|
2945
3932
|
const errors = stateOf(this.#instance).errors;
|
|
2946
3933
|
const wanted = [keys].flat();
|
|
2947
3934
|
if (wanted.length === 0 && Object.keys(errors).length === 0) fail("Component has no errors.", errors);
|
|
2948
|
-
for (const
|
|
3935
|
+
for (const key2 of wanted) if ((errors[key2]?.length ?? 0) === 0) fail(`Component missing error: ${key2}`, errors, key2);
|
|
2949
3936
|
return this;
|
|
2950
3937
|
}
|
|
2951
3938
|
/** No errors for the keys given (or none at all with no keys). */
|
|
2952
3939
|
assertHasNoErrors(keys = []) {
|
|
2953
3940
|
const errors = stateOf(this.#instance).errors;
|
|
2954
3941
|
const wanted = [keys].flat();
|
|
2955
|
-
const found = wanted.length === 0 ? Object.keys(errors).filter((
|
|
3942
|
+
const found = wanted.length === 0 ? Object.keys(errors).filter((key2) => (errors[key2]?.length ?? 0) > 0) : wanted.filter((key2) => (errors[key2]?.length ?? 0) > 0);
|
|
2956
3943
|
if (found.length > 0) fail(`Component has errors: "${found.join('", "')}"`, errors);
|
|
2957
3944
|
return this;
|
|
2958
3945
|
}
|
|
@@ -2973,12 +3960,12 @@ var Testable = class _Testable {
|
|
|
2973
3960
|
return this;
|
|
2974
3961
|
}
|
|
2975
3962
|
};
|
|
2976
|
-
async function live(component, params = {},
|
|
2977
|
-
const html = await inject(LiveManager).mount(component, params,
|
|
3963
|
+
async function live(component, params = {}, key2) {
|
|
3964
|
+
const html = await inject(LiveManager).mount(component, params, key2 === void 0 ? void 0 : String(key2));
|
|
2978
3965
|
return { toHtml: () => html };
|
|
2979
3966
|
}
|
|
2980
|
-
function LiveComponent({ component, "o:key":
|
|
2981
|
-
return live(component, params,
|
|
3967
|
+
function LiveComponent({ component, "o:key": key2, children: _children, ...params }) {
|
|
3968
|
+
return live(component, params, key2);
|
|
2982
3969
|
}
|
|
2983
3970
|
var Live = Object.assign(LiveComponent, {
|
|
2984
3971
|
/** Registers a component under a name (components in app/Live are registered at boot). */
|
|
@@ -2990,22 +3977,38 @@ var Live = Object.assign(LiveComponent, {
|
|
|
2990
3977
|
return Testable.create(component, params);
|
|
2991
3978
|
}
|
|
2992
3979
|
});
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
3980
|
+
var PURPOSE = "live.lazy";
|
|
3981
|
+
function lazyMode(cls, params) {
|
|
3982
|
+
const { lazy } = params;
|
|
3983
|
+
if (lazy === "on-load") return "on-load";
|
|
3984
|
+
if (lazy === true || lazy === void 0 && cls.lazy === true) return "intersect";
|
|
3985
|
+
return void 0;
|
|
3986
|
+
}
|
|
3987
|
+
async function placeholderHtml(container2, instance, params) {
|
|
3988
|
+
const method = instance.placeholder;
|
|
3989
|
+
const views = container2.make(ViewFactory);
|
|
3990
|
+
let output = typeof method === "function" ? await method.call(instance, params) : void 0;
|
|
3991
|
+
if (output === void 0) {
|
|
3992
|
+
const view = container2.bound(ConfigRepository) ? container2.make(ConfigRepository).get("live.lazy_placeholder", null) : null;
|
|
3993
|
+
output = view === null || view === void 0 ? "<div></div>" : views.make(view, params);
|
|
2998
3994
|
}
|
|
2999
|
-
return
|
|
3995
|
+
return (typeof output === "string" ? output : await views.renderToString(output)).trim();
|
|
3000
3996
|
}
|
|
3001
|
-
function
|
|
3002
|
-
|
|
3997
|
+
function lazyPayload(key2, registry, load) {
|
|
3998
|
+
const params = Object.fromEntries(Object.entries(load.params).map(([name, value]) => [name, dehydrate(value, registry, `${load.name}.${name}`)]));
|
|
3999
|
+
return signValue(key2, PURPOSE, { ...load, params });
|
|
3003
4000
|
}
|
|
3004
|
-
function
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
4001
|
+
async function lazyLoadOf(key2, registry, payload, component) {
|
|
4002
|
+
const load = verifiedValue(key2, PURPOSE, payload);
|
|
4003
|
+
if (load === void 0 || load.id !== component.id || load.name !== component.name || typeof load.tag !== "string" || typeof load.params !== "object" || load.params === null) {
|
|
4004
|
+
throw new CorruptComponentPayloadError(component.name);
|
|
4005
|
+
}
|
|
4006
|
+
return { params: await hydrate([load.params, { s: "obj" }], registry), tag: load.tag };
|
|
4007
|
+
}
|
|
4008
|
+
function assertSameRoot(name, placeholder, rendered) {
|
|
4009
|
+
if (placeholder !== rendered) {
|
|
4010
|
+
throw new LiveError(`The lazy component [${name}] renders a <${rendered}> where its placeholder has a <${placeholder}>: give both the same root element.`);
|
|
4011
|
+
}
|
|
3009
4012
|
}
|
|
3010
4013
|
function componentName(file) {
|
|
3011
4014
|
return file.replace(/\.(tsx|ts|jsx|js|mts|mjs)$/, "").split(/[\\/]/).map((segment) => Str.kebab(segment)).join(".");
|
|
@@ -3023,9 +4026,9 @@ async function discover(directory) {
|
|
|
3023
4026
|
for (const file of componentFiles(directory)) {
|
|
3024
4027
|
const module = await import(
|
|
3025
4028
|
/* @vite-ignore */
|
|
3026
|
-
pathToFileURL2(
|
|
4029
|
+
pathToFileURL2(join6(directory, file)).href
|
|
3027
4030
|
);
|
|
3028
|
-
found.push([componentName(relative2(directory,
|
|
4031
|
+
found.push([componentName(relative2(directory, join6(directory, file))), module.default]);
|
|
3029
4032
|
}
|
|
3030
4033
|
return found;
|
|
3031
4034
|
}
|
|
@@ -3043,7 +4046,7 @@ function routePath(path) {
|
|
|
3043
4046
|
const trimmed = path.replace(/^\/+|\/+$/g, "").replace(/\/{2,}/g, "/");
|
|
3044
4047
|
return trimmed === "" ? "" : `/${trimmed}`;
|
|
3045
4048
|
}
|
|
3046
|
-
function
|
|
4049
|
+
function expand2(references, aliases, groups, out = [], visiting = /* @__PURE__ */ new Set()) {
|
|
3047
4050
|
for (const reference of references) {
|
|
3048
4051
|
if (typeof reference === "function") {
|
|
3049
4052
|
out.push({ cls: reference, parameters: [] });
|
|
@@ -3054,7 +4057,7 @@ function expand(references, aliases, groups, out = [], visiting = /* @__PURE__ *
|
|
|
3054
4057
|
if (index === -1 && Object.hasOwn(groups, name)) {
|
|
3055
4058
|
if (visiting.has(name)) continue;
|
|
3056
4059
|
visiting.add(name);
|
|
3057
|
-
|
|
4060
|
+
expand2(groups[name], aliases, groups, out, visiting);
|
|
3058
4061
|
visiting.delete(name);
|
|
3059
4062
|
} else if (Object.hasOwn(aliases, name)) {
|
|
3060
4063
|
out.push({ cls: aliases[name], parameters: index === -1 ? [] : reference.slice(index + 1).split(",") });
|
|
@@ -3083,17 +4086,17 @@ function persistentPass(router, persistent, method, path) {
|
|
|
3083
4086
|
const aliases = router.getMiddleware();
|
|
3084
4087
|
const groups = router.getMiddlewareGroups();
|
|
3085
4088
|
const wanted = new Set(persistent.flatMap((name) => Object.hasOwn(aliases, name) ? [aliases[name]] : []));
|
|
3086
|
-
const excluded = new Set(
|
|
4089
|
+
const excluded = new Set(expand2(route2.excludedMiddleware(), aliases, groups).map((entry) => entry.cls));
|
|
3087
4090
|
const ids = /* @__PURE__ */ new Map();
|
|
3088
4091
|
const seen = /* @__PURE__ */ new Set();
|
|
3089
|
-
const middleware =
|
|
4092
|
+
const middleware = expand2(route2.gatherMiddleware(), aliases, groups).filter((entry) => {
|
|
3090
4093
|
if (!ids.has(entry.cls)) ids.set(entry.cls, ids.size);
|
|
3091
|
-
const
|
|
3092
|
-
if (!wanted.has(entry.cls) || excluded.has(entry.cls) || seen.has(
|
|
3093
|
-
seen.add(
|
|
4094
|
+
const key2 = `${ids.get(entry.cls)}:${entry.parameters.join(",")}`;
|
|
4095
|
+
if (!wanted.has(entry.cls) || excluded.has(entry.cls) || seen.has(key2)) return false;
|
|
4096
|
+
seen.add(key2);
|
|
3094
4097
|
return true;
|
|
3095
4098
|
});
|
|
3096
|
-
const early = new Set(
|
|
4099
|
+
const early = new Set(expand2(["web", "api"], aliases, groups).map((entry) => entry.cls));
|
|
3097
4100
|
for (const name of ["auth", "guest"]) if (Object.hasOwn(aliases, name)) early.add(aliases[name]);
|
|
3098
4101
|
const before = middleware.filter((entry) => early.has(entry.cls));
|
|
3099
4102
|
const after = middleware.filter((entry) => !early.has(entry.cls));
|
|
@@ -3102,38 +4105,38 @@ function persistentPass(router, persistent, method, path) {
|
|
|
3102
4105
|
function persistentMiddleware(router, persistent, method, path) {
|
|
3103
4106
|
return [...persistentPass(router, persistent, method, path)?.middleware ?? []];
|
|
3104
4107
|
}
|
|
3105
|
-
function currentRoute(
|
|
3106
|
-
if (!
|
|
4108
|
+
function currentRoute(container2) {
|
|
4109
|
+
if (!container2.bound(CurrentRoute)) return void 0;
|
|
3107
4110
|
try {
|
|
3108
|
-
return
|
|
4111
|
+
return container2.make(CurrentRoute);
|
|
3109
4112
|
} catch {
|
|
3110
4113
|
return void 0;
|
|
3111
4114
|
}
|
|
3112
4115
|
}
|
|
3113
|
-
async function throughMiddleware(
|
|
3114
|
-
const
|
|
4116
|
+
async function throughMiddleware(container2, request2, pass, destination) {
|
|
4117
|
+
const original2 = currentRoute(container2);
|
|
3115
4118
|
const page = { route: pass.route, parameters: { ...pass.values }, middleware: [] };
|
|
3116
4119
|
let swapped = false;
|
|
3117
4120
|
const show = () => {
|
|
3118
|
-
|
|
4121
|
+
container2.instance(CurrentRoute, page);
|
|
3119
4122
|
swapped = true;
|
|
3120
4123
|
};
|
|
3121
4124
|
const restore = () => {
|
|
3122
4125
|
if (!swapped) return;
|
|
3123
|
-
if (
|
|
3124
|
-
else
|
|
4126
|
+
if (original2 === void 0) container2.forgetInstance(CurrentRoute);
|
|
4127
|
+
else container2.instance(CurrentRoute, original2);
|
|
3125
4128
|
swapped = false;
|
|
3126
4129
|
};
|
|
3127
4130
|
let ran;
|
|
3128
4131
|
const run = async (index) => {
|
|
3129
|
-
if (index === pass.at) Object.assign(page.parameters, await pass.router.substituteBindings(pass.route, pass.values,
|
|
4132
|
+
if (index === pass.at) Object.assign(page.parameters, await pass.router.substituteBindings(pass.route, pass.values, container2));
|
|
3130
4133
|
if (index === pass.middleware.length) {
|
|
3131
4134
|
restore();
|
|
3132
4135
|
ran = { result: await destination() };
|
|
3133
4136
|
return new Response(null, { status: 204 });
|
|
3134
4137
|
}
|
|
3135
4138
|
const { cls, parameters } = pass.middleware[index];
|
|
3136
|
-
const instance =
|
|
4139
|
+
const instance = container2.make(cls);
|
|
3137
4140
|
const next = () => run(index + 1);
|
|
3138
4141
|
return instance.handle(request2, next, ...parameters);
|
|
3139
4142
|
};
|
|
@@ -3145,144 +4148,6 @@ async function throughMiddleware(container, request2, pass, destination) {
|
|
|
3145
4148
|
restore();
|
|
3146
4149
|
}
|
|
3147
4150
|
}
|
|
3148
|
-
function isModel(value) {
|
|
3149
|
-
if (typeof value !== "object" || value === null) return false;
|
|
3150
|
-
const candidate = value;
|
|
3151
|
-
return typeof candidate.getKey === "function" && typeof candidate.getKeyName === "function" && typeof candidate.getTable === "function" && typeof candidate.exists === "boolean";
|
|
3152
|
-
}
|
|
3153
|
-
var isPlainObject2 = (value) => {
|
|
3154
|
-
const proto = Object.getPrototypeOf(value);
|
|
3155
|
-
return proto === Object.prototype || proto === null;
|
|
3156
|
-
};
|
|
3157
|
-
var isTuple = (value) => Array.isArray(value) && value.length === 2 && typeof value[1] === "object" && value[1] !== null && !Array.isArray(value[1]) && typeof value[1].s === "string";
|
|
3158
|
-
function classNameOf(registry, cls) {
|
|
3159
|
-
return registry.nameOf(cls) ?? registry.register(cls);
|
|
3160
|
-
}
|
|
3161
|
-
function dehydrate(value, registry, path, seen = /* @__PURE__ */ new Set()) {
|
|
3162
|
-
switch (typeof value) {
|
|
3163
|
-
case "undefined":
|
|
3164
|
-
return [null, { s: "u" }];
|
|
3165
|
-
case "string":
|
|
3166
|
-
case "boolean":
|
|
3167
|
-
return value;
|
|
3168
|
-
case "number":
|
|
3169
|
-
if (!Number.isFinite(value)) throw new LiveError(`Property [${path}] is ${value}: Live state must be JSON (finite numbers only).`);
|
|
3170
|
-
return value;
|
|
3171
|
-
case "bigint":
|
|
3172
|
-
case "symbol":
|
|
3173
|
-
case "function":
|
|
3174
|
-
throw new LiveError(`Property [${path}] holds a ${typeof value}: Live state may only hold primitives, arrays, plain objects, Dates, models and collections.`);
|
|
3175
|
-
}
|
|
3176
|
-
if (value === null) return null;
|
|
3177
|
-
const object = value;
|
|
3178
|
-
if (seen.has(object)) throw new LiveError(`Property [${path}] contains a circular reference.`);
|
|
3179
|
-
seen.add(object);
|
|
3180
|
-
try {
|
|
3181
|
-
if (object instanceof Date) return [object.toISOString(), { s: "dt" }];
|
|
3182
|
-
if (Array.isArray(object)) return [object.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), { s: "arr" }];
|
|
3183
|
-
if (isModel(object)) {
|
|
3184
|
-
const name = classNameOf(registry, object.constructor);
|
|
3185
|
-
if (!object.exists) throw new LiveError(`Property [${path}] holds an unsaved [${name}] model: only saved models can be kept between requests.`);
|
|
3186
|
-
return [null, { s: "mdl", class: name, key: object.getKey() }];
|
|
3187
|
-
}
|
|
3188
|
-
if (object instanceof Collection) {
|
|
3189
|
-
const items = object.all();
|
|
3190
|
-
const first = items[0];
|
|
3191
|
-
if (items.length > 0 && isModel(first) && items.every((item) => isModel(item) && item.constructor === first.constructor)) {
|
|
3192
|
-
for (const [i, item] of items.entries()) {
|
|
3193
|
-
if (!item.exists) throw new LiveError(`Property [${path}.${i}] holds an unsaved model.`);
|
|
3194
|
-
}
|
|
3195
|
-
const name = classNameOf(registry, first.constructor);
|
|
3196
|
-
return [null, { s: "elcln", class: name, keys: items.map((item) => item.getKey()) }];
|
|
3197
|
-
}
|
|
3198
|
-
return [items.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), { s: "clctn" }];
|
|
3199
|
-
}
|
|
3200
|
-
if (isPlainObject2(object)) {
|
|
3201
|
-
const out = {};
|
|
3202
|
-
for (const [key, item] of Object.entries(object)) out[key] = dehydrate(item, registry, `${path}.${key}`, seen);
|
|
3203
|
-
return [out, { s: "obj" }];
|
|
3204
|
-
}
|
|
3205
|
-
const type = object.constructor?.name || "object";
|
|
3206
|
-
throw new LiveError(
|
|
3207
|
-
`Property [${path}] holds a [${type}] instance: Live state may only hold primitives, arrays, plain objects, Dates, models and collections.`
|
|
3208
|
-
);
|
|
3209
|
-
} finally {
|
|
3210
|
-
seen.delete(object);
|
|
3211
|
-
}
|
|
3212
|
-
}
|
|
3213
|
-
function modelClass(registry, name) {
|
|
3214
|
-
if (typeof name !== "string" || !registry.has(name)) throw new LiveError(`Unable to hydrate the [${String(name)}] model: it is not registered.`);
|
|
3215
|
-
return registry.resolve(name);
|
|
3216
|
-
}
|
|
3217
|
-
async function hydrate(value, registry) {
|
|
3218
|
-
if (!isTuple(value)) return value;
|
|
3219
|
-
const [inner, meta] = value;
|
|
3220
|
-
switch (meta.s) {
|
|
3221
|
-
case "u":
|
|
3222
|
-
return void 0;
|
|
3223
|
-
case "dt":
|
|
3224
|
-
return new Date(String(inner));
|
|
3225
|
-
case "arr":
|
|
3226
|
-
return Promise.all(inner.map((item) => hydrate(item, registry)));
|
|
3227
|
-
case "clctn":
|
|
3228
|
-
return new Collection(await Promise.all(inner.map((item) => hydrate(item, registry))));
|
|
3229
|
-
case "obj": {
|
|
3230
|
-
const out = {};
|
|
3231
|
-
for (const [key, item] of Object.entries(inner)) {
|
|
3232
|
-
Object.defineProperty(out, key, { value: await hydrate(item, registry), enumerable: true, writable: true, configurable: true });
|
|
3233
|
-
}
|
|
3234
|
-
return out;
|
|
3235
|
-
}
|
|
3236
|
-
case "mdl":
|
|
3237
|
-
return modelClass(registry, meta.class).findOrFail(meta.key);
|
|
3238
|
-
case "elcln": {
|
|
3239
|
-
const cls = modelClass(registry, meta.class);
|
|
3240
|
-
const keys = meta.keys ?? [];
|
|
3241
|
-
const keyName = new cls().getKeyName();
|
|
3242
|
-
const found = [...await cls.query().whereIn(keyName, keys).get()];
|
|
3243
|
-
const byKey = new Map(found.map((model) => [String(model.getKey()), model]));
|
|
3244
|
-
return new Collection(keys.map((key) => byKey.get(String(key))).filter((model) => model !== void 0));
|
|
3245
|
-
}
|
|
3246
|
-
}
|
|
3247
|
-
throw new LiveError(`Unknown snapshot value type [${String(meta.s)}].`);
|
|
3248
|
-
}
|
|
3249
|
-
var HOOKS = /* @__PURE__ */ new Set(["constructor", "render", "mount", "boot", "booted", "hydrate", "dehydrate", "updating", "updated", "rendering", "rendered", "exception"]);
|
|
3250
|
-
var HOOK_PREFIX = /^(updating|updated|hydrate|dehydrate)[A-Z_]/;
|
|
3251
|
-
var UNSAFE_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
3252
|
-
var BASE_API = new Set(Object.getOwnPropertyNames(Component.prototype));
|
|
3253
|
-
var INDEX = /^(?:0|[1-9]\d*)$/;
|
|
3254
|
-
function isArraySlot(array, segment) {
|
|
3255
|
-
return INDEX.test(segment) && Number(segment) <= array.length;
|
|
3256
|
-
}
|
|
3257
|
-
function isCallable(instance, method) {
|
|
3258
|
-
if (method === "" || method.startsWith("_") || method.startsWith("$") || method.startsWith("#")) return false;
|
|
3259
|
-
if (HOOKS.has(method) || HOOK_PREFIX.test(method) || BASE_API.has(method)) return false;
|
|
3260
|
-
if (lockedOf(instance.constructor).has(method)) return false;
|
|
3261
|
-
const isBase = (proto) => proto === Component.prototype || proto === Object.prototype;
|
|
3262
|
-
for (let proto = Object.getPrototypeOf(instance); proto !== null && !isBase(proto); proto = Object.getPrototypeOf(proto)) {
|
|
3263
|
-
const descriptor = Object.getOwnPropertyDescriptor(proto, method);
|
|
3264
|
-
if (descriptor !== void 0) return typeof descriptor.value === "function";
|
|
3265
|
-
}
|
|
3266
|
-
return false;
|
|
3267
|
-
}
|
|
3268
|
-
function isUpdatable(instance, path) {
|
|
3269
|
-
const segments = path.split(".");
|
|
3270
|
-
const root = segments[0];
|
|
3271
|
-
if (root === "" || root.startsWith("_") || segments.some((segment) => segment === "" || UNSAFE_SEGMENTS.has(segment))) return false;
|
|
3272
|
-
if (!Object.hasOwn(instance, root)) return false;
|
|
3273
|
-
const descriptor = Object.getOwnPropertyDescriptor(instance, root);
|
|
3274
|
-
if (!descriptor.enumerable || descriptor.get !== void 0) return false;
|
|
3275
|
-
const cls = instance.constructor;
|
|
3276
|
-
if (lockedOf(cls).has(root) || computedOf(cls).has(root)) return false;
|
|
3277
|
-
let node = descriptor.value;
|
|
3278
|
-
for (const segment of segments.slice(1)) {
|
|
3279
|
-
if (isModel(node) || node instanceof Collection) return false;
|
|
3280
|
-
if (Array.isArray(node) && !isArraySlot(node, segment)) return false;
|
|
3281
|
-
if (typeof node !== "object" || node === null || !Object.hasOwn(node, segment)) return true;
|
|
3282
|
-
node = node[segment];
|
|
3283
|
-
}
|
|
3284
|
-
return !isModel(node) && !(node instanceof Collection);
|
|
3285
|
-
}
|
|
3286
4151
|
var PAGINATED = /* @__PURE__ */ Symbol.for("oriel:live.pagination");
|
|
3287
4152
|
function toPage(value) {
|
|
3288
4153
|
const page = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : Number.NaN;
|
|
@@ -3340,18 +4205,19 @@ var livePaginationState = {
|
|
|
3340
4205
|
}
|
|
3341
4206
|
};
|
|
3342
4207
|
var renderContext = new AsyncLocalStorage2();
|
|
4208
|
+
var loading = /* @__PURE__ */ new WeakMap();
|
|
3343
4209
|
var pageRequest = new AsyncLocalStorage2();
|
|
3344
|
-
function optional(
|
|
3345
|
-
if (!
|
|
4210
|
+
function optional(container2, token2) {
|
|
4211
|
+
if (!container2.bound(token2)) return void 0;
|
|
3346
4212
|
try {
|
|
3347
|
-
return
|
|
4213
|
+
return container2.make(token2);
|
|
3348
4214
|
} catch {
|
|
3349
4215
|
return void 0;
|
|
3350
4216
|
}
|
|
3351
4217
|
}
|
|
3352
|
-
var
|
|
3353
|
-
function setOwn(object,
|
|
3354
|
-
Object.defineProperty(object,
|
|
4218
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4219
|
+
function setOwn(object, key2, value) {
|
|
4220
|
+
Object.defineProperty(object, key2, { value, enumerable: true, writable: true, configurable: true });
|
|
3355
4221
|
}
|
|
3356
4222
|
async function hook2(instance, name, ...args) {
|
|
3357
4223
|
const method = instance[name];
|
|
@@ -3395,10 +4261,10 @@ var LiveComponentManager = class {
|
|
|
3395
4261
|
return request2.header("X-Oriel-Live") === "true";
|
|
3396
4262
|
}
|
|
3397
4263
|
/** The first render: HTML with o:id, o:snapshot and o:effects on the root element. */
|
|
3398
|
-
async mount(component, params = {},
|
|
4264
|
+
async mount(component, params = {}, key2) {
|
|
3399
4265
|
const context = renderContext.getStore();
|
|
3400
4266
|
if (context === void 0) return (await this.mountComponent(component, params)).html;
|
|
3401
|
-
const childKey =
|
|
4267
|
+
const childKey = key2 ?? `lw-${context.index++}`;
|
|
3402
4268
|
const existing = Object.hasOwn(context.previous, childKey) ? context.previous[childKey] : void 0;
|
|
3403
4269
|
if (existing !== void 0) {
|
|
3404
4270
|
setOwn(context.children, childKey, existing);
|
|
@@ -3408,7 +4274,11 @@ var LiveComponentManager = class {
|
|
|
3408
4274
|
setOwn(context.children, childKey, [result.tag, result.snapshot.memo.id]);
|
|
3409
4275
|
return result.html;
|
|
3410
4276
|
}
|
|
3411
|
-
|
|
4277
|
+
/**
|
|
4278
|
+
* Mounts a component: boot(), the params into its properties, mount(params), then its first render. A lazy one
|
|
4279
|
+
* (`lazy` in the params, or `static lazy`) renders its placeholder instead and mounts when the browser loads it.
|
|
4280
|
+
*/
|
|
4281
|
+
async mountComponent(component, embedParams = {}, existing) {
|
|
3412
4282
|
const cls = typeof component === "string" ? this.resolve(component) : component;
|
|
3413
4283
|
if (!isComponentClass(cls)) throw new LiveError(`[${cls.name}] is not a Live component: it must extend Component.`);
|
|
3414
4284
|
const name = this.nameOf(cls);
|
|
@@ -3416,7 +4286,10 @@ var LiveComponentManager = class {
|
|
|
3416
4286
|
const state = stateOf(instance);
|
|
3417
4287
|
state.id = randomBytes2(15).toString("base64url");
|
|
3418
4288
|
state.name = name;
|
|
4289
|
+
const { lazy: _lazy, ...params } = embedParams;
|
|
4290
|
+
const lazy = lazyMode(cls, embedParams);
|
|
3419
4291
|
await hook2(instance, "boot");
|
|
4292
|
+
if (lazy !== void 0) return this.mountPlaceholder(instance, params, lazy);
|
|
3420
4293
|
for (const [property, value] of Object.entries(params)) {
|
|
3421
4294
|
if (Object.hasOwn(instance, property)) instance[property] = value;
|
|
3422
4295
|
}
|
|
@@ -3433,6 +4306,30 @@ var LiveComponentManager = class {
|
|
|
3433
4306
|
const attributes = { "o:snapshot": JSON.stringify(snapshot), "o:effects": JSON.stringify(effects), "o:id": state.id };
|
|
3434
4307
|
return { instance, snapshot, effects, html: insertAttributesIntoRoot(html, attributes), tag };
|
|
3435
4308
|
}
|
|
4309
|
+
/**
|
|
4310
|
+
* A lazy component's first render (Livewire's): its placeholder, with the state it has before mount() and the mount
|
|
4311
|
+
* params signed for the `__lazyLoad` call the browser makes when it shows it (`o:lazy`, or `o:lazy-on-load`).
|
|
4312
|
+
*/
|
|
4313
|
+
async mountPlaceholder(instance, params, lazy) {
|
|
4314
|
+
const state = stateOf(instance);
|
|
4315
|
+
const request2 = optional(this.container(), HttpRequest);
|
|
4316
|
+
const page = pageRequest.getStore() ?? { path: request2?.path() ?? "", method: request2?.method() ?? "GET" };
|
|
4317
|
+
const memo = { id: state.id, name: state.name, path: page.path, method: page.method, children: {}, errors: {} };
|
|
4318
|
+
const html = await placeholderHtml(this.container(), instance, params);
|
|
4319
|
+
const tag = rootTag(html, state.name);
|
|
4320
|
+
const effects = this.effects(instance);
|
|
4321
|
+
const listeners = Object.keys(listenersOf(instance.constructor));
|
|
4322
|
+
if (listeners.length > 0) effects.listeners = listeners;
|
|
4323
|
+
const snapshot = await this.snapshot(instance, memo);
|
|
4324
|
+
const payload = lazyPayload(this.key(), this.registry(), { id: state.id, name: state.name, tag, params });
|
|
4325
|
+
const attributes = {
|
|
4326
|
+
"o:snapshot": JSON.stringify(snapshot),
|
|
4327
|
+
"o:effects": JSON.stringify(effects),
|
|
4328
|
+
"o:id": state.id,
|
|
4329
|
+
[lazy === "on-load" ? "o:lazy-on-load" : "o:lazy"]: payload
|
|
4330
|
+
};
|
|
4331
|
+
return { instance, snapshot, effects, html: insertAttributesIntoRoot(html, attributes), tag };
|
|
4332
|
+
}
|
|
3436
4333
|
/** Applies each component's updates and calls, re-renders it and returns its new snapshot and effects. */
|
|
3437
4334
|
async update(request2) {
|
|
3438
4335
|
const components = [];
|
|
@@ -3445,6 +4342,7 @@ var LiveComponentManager = class {
|
|
|
3445
4342
|
}
|
|
3446
4343
|
async updateComponent(snapshotJson, updates, calls) {
|
|
3447
4344
|
const snapshot = this.verify(snapshotJson);
|
|
4345
|
+
const load = await this.lazyLoad(snapshot.memo, updates, calls);
|
|
3448
4346
|
const cls = this.resolve(snapshot.memo.name);
|
|
3449
4347
|
const instance = this.container().make(cls);
|
|
3450
4348
|
const state = stateOf(instance);
|
|
@@ -3459,13 +4357,21 @@ var LiveComponentManager = class {
|
|
|
3459
4357
|
this.prepareComputed(instance);
|
|
3460
4358
|
const returns = [];
|
|
3461
4359
|
await renderPaginated(instance, this.pageUrl(snapshot.memo.path), async () => {
|
|
3462
|
-
await hook2(instance, "hydrate");
|
|
4360
|
+
if (load === void 0) await hook2(instance, "hydrate");
|
|
3463
4361
|
try {
|
|
4362
|
+
if (load !== void 0) {
|
|
4363
|
+
for (const [property, value] of Object.entries(load.params)) {
|
|
4364
|
+
if (Object.hasOwn(instance, property)) instance[property] = value;
|
|
4365
|
+
}
|
|
4366
|
+
loading.set(instance, load.tag);
|
|
4367
|
+
await hook2(instance, "mount", load.params);
|
|
4368
|
+
returns.push(void 0);
|
|
4369
|
+
}
|
|
3464
4370
|
for (const [path, value] of Object.entries(updates)) await this.applyUpdate(instance, path, value);
|
|
3465
|
-
for (const call of calls) returns.push(await this.call(instance, call.method, call.params));
|
|
4371
|
+
for (const call of load === void 0 ? calls : []) returns.push(await this.call(instance, call.method, call.params));
|
|
3466
4372
|
} catch (error) {
|
|
3467
4373
|
const errors = error.errors;
|
|
3468
|
-
if (error.kind !== "Validation" || !
|
|
4374
|
+
if (error.kind !== "Validation" || !isRecord2(errors)) throw error;
|
|
3469
4375
|
state.errors = { ...errors };
|
|
3470
4376
|
}
|
|
3471
4377
|
});
|
|
@@ -3476,12 +4382,24 @@ var LiveComponentManager = class {
|
|
|
3476
4382
|
if (!state.skipRender) {
|
|
3477
4383
|
;
|
|
3478
4384
|
({ html, tag } = await this.render(instance, memo, snapshot.memo.children));
|
|
4385
|
+
const placeholder = loading.get(instance);
|
|
4386
|
+
if (placeholder !== void 0) assertSameRoot(state.name, placeholder, tag);
|
|
3479
4387
|
html = insertAttributesIntoRoot(html, { "o:id": state.id });
|
|
3480
4388
|
effects.html = html;
|
|
3481
4389
|
}
|
|
3482
4390
|
if (calls.length > 0) effects.returns = returns;
|
|
3483
4391
|
return { instance, snapshot: await this.snapshot(instance, memo), effects, html, tag };
|
|
3484
4392
|
}
|
|
4393
|
+
/**
|
|
4394
|
+
* The mount params and placeholder tag of a `__lazyLoad` request, or undefined for any other update. The load comes
|
|
4395
|
+
* alone (no updates, no other call: the browser sends it in a request of its own) and its payload must be the one
|
|
4396
|
+
* signed for this component: anything else is refused as corrupt (a 403) before the component boots.
|
|
4397
|
+
*/
|
|
4398
|
+
async lazyLoad(memo, updates, calls) {
|
|
4399
|
+
if (!calls.some((call) => call.method === "__lazyLoad")) return void 0;
|
|
4400
|
+
if (calls.length !== 1 || Object.keys(updates).length > 0) throw new CorruptComponentPayloadError(memo.name);
|
|
4401
|
+
return lazyLoadOf(this.key(), this.registry(), calls[0].params[0], { id: memo.id, name: memo.name });
|
|
4402
|
+
}
|
|
3485
4403
|
/**
|
|
3486
4404
|
* The route action of POST /oriel/live/update. Each component's update runs behind the persistent middleware of the
|
|
3487
4405
|
* route that rendered its page (Livewire's): `auth`, `verified`, `guest` and `can`, with the page route's parameters
|
|
@@ -3491,17 +4409,17 @@ var LiveComponentManager = class {
|
|
|
3491
4409
|
*/
|
|
3492
4410
|
async handle(request2) {
|
|
3493
4411
|
if (!this.isLiveRequest(request2)) throw new InvalidLiveRequestError("Live update requests need the X-Oriel-Live header.");
|
|
3494
|
-
const
|
|
3495
|
-
const persistent = optional(
|
|
4412
|
+
const container2 = this.container();
|
|
4413
|
+
const persistent = optional(container2, ConfigRepository)?.get("live.persistent_middleware", PERSISTENT_MIDDLEWARE) ?? PERSISTENT_MIDDLEWARE;
|
|
3496
4414
|
const components = [];
|
|
3497
|
-
const flashed = trackFlash(optional(
|
|
4415
|
+
const flashed = trackFlash(optional(container2, Session));
|
|
3498
4416
|
for (const component of this.payload(request2).components) {
|
|
3499
4417
|
const { memo } = this.verify(component.snapshot);
|
|
3500
|
-
const pass = persistentPass(
|
|
4418
|
+
const pass = persistentPass(container2.make(Router), persistent, memo.method, memo.path);
|
|
3501
4419
|
const update = () => this.updateComponent(component.snapshot, component.updates, component.calls);
|
|
3502
4420
|
const result = await pageRequest.run(
|
|
3503
4421
|
{ path: memo.path, method: memo.method },
|
|
3504
|
-
() => pass === void 0 || pass.middleware.length === 0 ? update() : throughMiddleware(
|
|
4422
|
+
() => pass === void 0 || pass.middleware.length === 0 ? update() : throughMiddleware(container2, request2, pass, update)
|
|
3505
4423
|
);
|
|
3506
4424
|
if (result instanceof Response) return result;
|
|
3507
4425
|
components.push({ snapshot: JSON.stringify(result.snapshot), effects: result.effects });
|
|
@@ -3511,19 +4429,19 @@ var LiveComponentManager = class {
|
|
|
3511
4429
|
}
|
|
3512
4430
|
/** A full-page component (Route.get('/counter', Counter)): mounted with the route parameters, inside the layout. */
|
|
3513
4431
|
async page(component) {
|
|
3514
|
-
const
|
|
3515
|
-
const params = { ...optional(
|
|
4432
|
+
const container2 = this.container();
|
|
4433
|
+
const params = { ...optional(container2, CurrentRoute)?.parameters ?? {} };
|
|
3516
4434
|
const cls = component.constructor;
|
|
3517
4435
|
const result = await this.mountComponent(cls, params, component);
|
|
3518
4436
|
const redirect2 = result.effects.redirect;
|
|
3519
4437
|
if (redirect2 !== void 0) {
|
|
3520
|
-
return optional(
|
|
4438
|
+
return optional(container2, Redirector)?.to(redirect2) ?? new Response(null, { status: 302, headers: { Location: redirect2 } });
|
|
3521
4439
|
}
|
|
3522
|
-
const layout = cls.layout ?? optional(
|
|
4440
|
+
const layout = cls.layout ?? optional(container2, ConfigRepository)?.get("live.layout") ?? "components.layouts.app";
|
|
3523
4441
|
const html = result.html;
|
|
3524
4442
|
const data = { children: { toHtml: () => html } };
|
|
3525
4443
|
if (cls.title !== void 0) data.title = cls.title;
|
|
3526
|
-
return
|
|
4444
|
+
return container2.make(ViewFactory).make(layout, data);
|
|
3527
4445
|
}
|
|
3528
4446
|
/** Parses and checks a snapshot: a bad checksum throws before anything is hydrated. */
|
|
3529
4447
|
verify(snapshotJson) {
|
|
@@ -3533,13 +4451,13 @@ var LiveComponentManager = class {
|
|
|
3533
4451
|
} catch {
|
|
3534
4452
|
throw new CorruptComponentPayloadError();
|
|
3535
4453
|
}
|
|
3536
|
-
if (!
|
|
4454
|
+
if (!isRecord2(snapshot) || !isRecord2(snapshot.data) || !isRecord2(snapshot.memo)) throw new CorruptComponentPayloadError();
|
|
3537
4455
|
const memo = snapshot.memo;
|
|
3538
4456
|
const name = typeof memo.name === "string" ? memo.name : "unknown";
|
|
3539
|
-
const
|
|
4457
|
+
const key2 = this.key();
|
|
3540
4458
|
let valid;
|
|
3541
4459
|
try {
|
|
3542
|
-
valid = verifyChecksum(
|
|
4460
|
+
valid = verifyChecksum(key2, { data: snapshot.data, memo: snapshot.memo }, snapshot.checksum);
|
|
3543
4461
|
} catch {
|
|
3544
4462
|
valid = false;
|
|
3545
4463
|
}
|
|
@@ -3558,9 +4476,9 @@ var LiveComponentManager = class {
|
|
|
3558
4476
|
} catch {
|
|
3559
4477
|
throw new InvalidLiveRequestError("The Live update request body is not valid JSON.");
|
|
3560
4478
|
}
|
|
3561
|
-
const components =
|
|
4479
|
+
const components = isRecord2(body) ? body.components : void 0;
|
|
3562
4480
|
const valid = Array.isArray(components) && components.every(
|
|
3563
|
-
(component) =>
|
|
4481
|
+
(component) => isRecord2(component) && typeof component.snapshot === "string" && isRecord2(component.updates) && Array.isArray(component.calls) && component.calls.every((call) => isRecord2(call) && typeof call.method === "string" && Array.isArray(call.params))
|
|
3564
4482
|
);
|
|
3565
4483
|
if (!valid) throw new InvalidLiveRequestError("The Live update request body is malformed.");
|
|
3566
4484
|
return body;
|
|
@@ -3579,10 +4497,10 @@ var LiveComponentManager = class {
|
|
|
3579
4497
|
async applyUpdate(instance, path, value) {
|
|
3580
4498
|
if (!isUpdatable(instance, path)) throw new PropertyNotUpdatableError(path, stateOf(instance).name);
|
|
3581
4499
|
const [root, ...rest] = path.split(".");
|
|
3582
|
-
const
|
|
4500
|
+
const key2 = rest.length > 0 ? rest.join(".") : void 0;
|
|
3583
4501
|
const studly = Str.studly(root);
|
|
3584
4502
|
await hook2(instance, "updating", path, value);
|
|
3585
|
-
await hook2(instance, `updating${studly}`, value,
|
|
4503
|
+
await hook2(instance, `updating${studly}`, value, key2);
|
|
3586
4504
|
const target2 = instance;
|
|
3587
4505
|
if (rest.length === 0) target2[root] = value;
|
|
3588
4506
|
else {
|
|
@@ -3590,20 +4508,20 @@ var LiveComponentManager = class {
|
|
|
3590
4508
|
if (Array.isArray(node2) && !isArraySlot(node2, segment)) throw new PropertyNotUpdatableError(path, stateOf(instance).name);
|
|
3591
4509
|
};
|
|
3592
4510
|
let node = target2[root];
|
|
3593
|
-
if (!
|
|
4511
|
+
if (!isRecord2(node) && !Array.isArray(node)) target2[root] = node = {};
|
|
3594
4512
|
for (const segment of rest.slice(0, -1)) {
|
|
3595
4513
|
assertSlot(node, segment);
|
|
3596
4514
|
const next = node[segment];
|
|
3597
|
-
node =
|
|
4515
|
+
node = isRecord2(next) || Array.isArray(next) ? next : node[segment] = {};
|
|
3598
4516
|
}
|
|
3599
4517
|
const last = rest.at(-1);
|
|
3600
4518
|
assertSlot(node, last);
|
|
3601
4519
|
Object.defineProperty(node, last, { value, enumerable: true, writable: true, configurable: true });
|
|
3602
4520
|
}
|
|
3603
4521
|
await hook2(instance, "updated", path, value);
|
|
3604
|
-
await hook2(instance, `updated${studly}`, value,
|
|
4522
|
+
await hook2(instance, `updated${studly}`, value, key2);
|
|
3605
4523
|
}
|
|
3606
|
-
/** Runs one call from the browser: a magic action, an event, or a public method. Returns its return value. */
|
|
4524
|
+
/** Runs one call from the browser: a magic action, an event, an upload call, or a public method. Returns its return value. */
|
|
3607
4525
|
async call(instance, method, params) {
|
|
3608
4526
|
const name = stateOf(instance).name;
|
|
3609
4527
|
switch (method) {
|
|
@@ -3613,7 +4531,7 @@ var LiveComponentManager = class {
|
|
|
3613
4531
|
return this.applyUpdate(instance, String(params[0]), params[1]);
|
|
3614
4532
|
case "$toggle": {
|
|
3615
4533
|
const path = String(params[0]);
|
|
3616
|
-
const current = path.split(".").reduce((node, segment) =>
|
|
4534
|
+
const current = path.split(".").reduce((node, segment) => isRecord2(node) || Array.isArray(node) ? node[segment] : void 0, instance);
|
|
3617
4535
|
return this.applyUpdate(instance, path, !current);
|
|
3618
4536
|
}
|
|
3619
4537
|
case "__dispatch": {
|
|
@@ -3621,11 +4539,12 @@ var LiveComponentManager = class {
|
|
|
3621
4539
|
const listeners = listenersOf(instance.constructor);
|
|
3622
4540
|
for (const listener of Object.hasOwn(listeners, String(event)) ? listeners[String(event)] : []) {
|
|
3623
4541
|
if (!isCallable(instance, listener)) throw new MethodNotCallableError(listener, name);
|
|
3624
|
-
await instance[listener](
|
|
4542
|
+
await instance[listener](isRecord2(detail) ? detail : {});
|
|
3625
4543
|
}
|
|
3626
4544
|
return void 0;
|
|
3627
4545
|
}
|
|
3628
4546
|
}
|
|
4547
|
+
if (UPLOAD_CALLS.has(method)) return runUploadCall(instance, method, params, (path, value) => this.applyUpdate(instance, path, value));
|
|
3629
4548
|
if (!isCallable(instance, method)) throw new MethodNotCallableError(method, name);
|
|
3630
4549
|
return instance[method](...params);
|
|
3631
4550
|
}
|
|
@@ -3719,9 +4638,9 @@ function registerLiveCommands(kernel, app) {
|
|
|
3719
4638
|
const className = segments.at(-1);
|
|
3720
4639
|
const config = app.bound(ConfigRepository) ? app.make(ConfigRepository) : void 0;
|
|
3721
4640
|
const directory = config?.get("live.class_path") ?? app.appPath("Live");
|
|
3722
|
-
const file =
|
|
4641
|
+
const file = join24(directory, ...segments.slice(0, -1), `${className}.tsx`);
|
|
3723
4642
|
const force = this.option("force") === true;
|
|
3724
|
-
if (
|
|
4643
|
+
if (existsSync23(file) && !force) {
|
|
3725
4644
|
this.error(`Component [${relative22(app.basePath(), file)}] already exists.`);
|
|
3726
4645
|
return 1;
|
|
3727
4646
|
}
|
|
@@ -3729,13 +4648,13 @@ function registerLiveCommands(kernel, app) {
|
|
|
3729
4648
|
await writeFile2(file, COMPONENT_STUB.replace("{{ class }}", () => className));
|
|
3730
4649
|
this.info(`Component [${relative22(app.basePath(), file)}] created successfully.`);
|
|
3731
4650
|
if (this.option("test") === true) {
|
|
3732
|
-
const testFile = app.basePath(
|
|
3733
|
-
if (
|
|
4651
|
+
const testFile = app.basePath(join24("tests", "Feature", "Live", ...segments.slice(0, -1), `${className}.test.ts`));
|
|
4652
|
+
if (existsSync23(testFile) && !force) {
|
|
3734
4653
|
this.error(`Test [${relative22(app.basePath(), testFile)}] already exists.`);
|
|
3735
4654
|
return 1;
|
|
3736
4655
|
}
|
|
3737
4656
|
const name = segments.map((segment) => Str.kebab(segment)).join(".");
|
|
3738
|
-
const test = TEST_STUB.replaceAll("{{ class }}", () => className).replace("{{ component }}", () => importPath(testFile, file)).replace("{{ testCase }}", () => importPath(testFile, app.basePath(
|
|
4657
|
+
const test = TEST_STUB.replaceAll("{{ class }}", () => className).replace("{{ component }}", () => importPath(testFile, file)).replace("{{ testCase }}", () => importPath(testFile, app.basePath(join24("tests", "TestCase.ts")))).replace("{{ name }}", () => name);
|
|
3739
4658
|
await mkdir2(dirname2(testFile), { recursive: true });
|
|
3740
4659
|
await writeFile2(testFile, test);
|
|
3741
4660
|
this.info(`Test [${relative22(app.basePath(), testFile)}] created successfully.`);
|
|
@@ -3748,13 +4667,16 @@ var LiveServiceProvider = class extends ServiceProvider {
|
|
|
3748
4667
|
register() {
|
|
3749
4668
|
this.mergeConfigFrom({ class_path: this.app.appPath("Live"), layout: "components.layouts.app", persistent_middleware: [...PERSISTENT_MIDDLEWARE] }, "live");
|
|
3750
4669
|
this.app.singleton(LiveComponentManager, () => new LiveComponentManager(this.app));
|
|
3751
|
-
this.app.singleton(LiveManager, (
|
|
4670
|
+
this.app.singleton(LiveManager, (container2) => container2.make(LiveComponentManager));
|
|
3752
4671
|
this.app.singleton(PaginationState, () => livePaginationState);
|
|
3753
4672
|
}
|
|
3754
4673
|
async boot() {
|
|
3755
4674
|
const manager = this.app.make(LiveComponentManager);
|
|
3756
4675
|
if (this.app.bound(Router)) {
|
|
3757
|
-
this.app.make(Router)
|
|
4676
|
+
const router = this.app.make(Router);
|
|
4677
|
+
router.post(UPDATE_URI, () => manager.handle(inject(HttpRequest))).middleware("web").name("live.update");
|
|
4678
|
+
router.post(UPLOAD_URI, () => handleUpload(inject(HttpRequest))).middleware(uploadMiddleware()).name("live.upload-file");
|
|
4679
|
+
router.get(`${PREVIEW_URI}/{filename}`, (filename) => handlePreview(inject(HttpRequest), filename)).middleware("web").name("live.preview-file");
|
|
3758
4680
|
}
|
|
3759
4681
|
if (this.app.bound(ConsoleKernel)) registerLiveCommands(this.app.make(ConsoleKernel), this.app);
|
|
3760
4682
|
const directory = this.app.make(ConfigRepository).get("live.class_path", this.app.appPath("Live"));
|
|
@@ -3802,7 +4724,7 @@ function Pagination({ paginator, class: className }) {
|
|
|
3802
4724
|
import { AssertionError as AssertionError2 } from "assert";
|
|
3803
4725
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
3804
4726
|
import { existsSync as existsSync6 } from "fs";
|
|
3805
|
-
import { join as
|
|
4727
|
+
import { join as join7 } from "path";
|
|
3806
4728
|
import { createHmac as createHmac2 } from "crypto";
|
|
3807
4729
|
import { randomUUID } from "crypto";
|
|
3808
4730
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -3810,6 +4732,129 @@ import { randomUUID as randomUUID3 } from "crypto";
|
|
|
3810
4732
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
3811
4733
|
import { posix } from "path";
|
|
3812
4734
|
import { deflateSync } from "zlib";
|
|
4735
|
+
var original;
|
|
4736
|
+
var testNow;
|
|
4737
|
+
var realDate = () => original ?? globalThis.Date;
|
|
4738
|
+
function install() {
|
|
4739
|
+
if (original !== void 0) return;
|
|
4740
|
+
const Real = original = globalThis.Date;
|
|
4741
|
+
const current = () => testNow ?? Real.now();
|
|
4742
|
+
const Travelling = new Proxy(Real, {
|
|
4743
|
+
construct: (target2, args, newTarget) => Reflect.construct(target2, args.length === 0 ? [current()] : args, newTarget === Travelling ? target2 : newTarget),
|
|
4744
|
+
apply: (target2) => new target2(current()).toString(),
|
|
4745
|
+
get: (target2, property) => property === "now" ? current : Reflect.get(target2, property, target2)
|
|
4746
|
+
});
|
|
4747
|
+
globalThis.Date = Travelling;
|
|
4748
|
+
}
|
|
4749
|
+
function setTestNow(date) {
|
|
4750
|
+
const time = new (realDate())(date).getTime();
|
|
4751
|
+
if (Number.isNaN(time)) throw new TypeError(`Invalid date: ${String(date)}`);
|
|
4752
|
+
install();
|
|
4753
|
+
testNow = time;
|
|
4754
|
+
return new (realDate())(time);
|
|
4755
|
+
}
|
|
4756
|
+
function thenTravelBack(callback) {
|
|
4757
|
+
let result;
|
|
4758
|
+
try {
|
|
4759
|
+
result = callback();
|
|
4760
|
+
} catch (error) {
|
|
4761
|
+
travelBack();
|
|
4762
|
+
throw error;
|
|
4763
|
+
}
|
|
4764
|
+
if (result instanceof Promise) return result.finally(travelBack);
|
|
4765
|
+
travelBack();
|
|
4766
|
+
return result;
|
|
4767
|
+
}
|
|
4768
|
+
function travelTo(date, callback) {
|
|
4769
|
+
const at = setTestNow(date);
|
|
4770
|
+
if (callback) return thenTravelBack(() => callback(at));
|
|
4771
|
+
}
|
|
4772
|
+
function travelBack() {
|
|
4773
|
+
if (original !== void 0) globalThis.Date = original;
|
|
4774
|
+
original = void 0;
|
|
4775
|
+
testNow = void 0;
|
|
4776
|
+
return /* @__PURE__ */ new Date();
|
|
4777
|
+
}
|
|
4778
|
+
function freezeTime(callback) {
|
|
4779
|
+
const at = new (realDate())(testNow ?? Date.now());
|
|
4780
|
+
if (callback) return travelTo(at, callback);
|
|
4781
|
+
travelTo(at);
|
|
4782
|
+
return at;
|
|
4783
|
+
}
|
|
4784
|
+
function freezeSecond(callback) {
|
|
4785
|
+
const at = new (realDate())(testNow ?? Date.now());
|
|
4786
|
+
at.setMilliseconds(0);
|
|
4787
|
+
if (callback) return travelTo(at, callback);
|
|
4788
|
+
travelTo(at);
|
|
4789
|
+
return at;
|
|
4790
|
+
}
|
|
4791
|
+
function travel(value) {
|
|
4792
|
+
return new Wormhole(value);
|
|
4793
|
+
}
|
|
4794
|
+
var Wormhole = class {
|
|
4795
|
+
constructor(value) {
|
|
4796
|
+
this.value = value;
|
|
4797
|
+
}
|
|
4798
|
+
value;
|
|
4799
|
+
millisecond(callback) {
|
|
4800
|
+
return this.milliseconds(callback);
|
|
4801
|
+
}
|
|
4802
|
+
milliseconds(callback) {
|
|
4803
|
+
return this.#move((date) => date.setTime(date.getTime() + this.value), callback);
|
|
4804
|
+
}
|
|
4805
|
+
second(callback) {
|
|
4806
|
+
return this.seconds(callback);
|
|
4807
|
+
}
|
|
4808
|
+
seconds(callback) {
|
|
4809
|
+
return this.#move((date) => date.setTime(date.getTime() + this.value * 1e3), callback);
|
|
4810
|
+
}
|
|
4811
|
+
minute(callback) {
|
|
4812
|
+
return this.minutes(callback);
|
|
4813
|
+
}
|
|
4814
|
+
minutes(callback) {
|
|
4815
|
+
return this.#move((date) => date.setTime(date.getTime() + this.value * 6e4), callback);
|
|
4816
|
+
}
|
|
4817
|
+
hour(callback) {
|
|
4818
|
+
return this.hours(callback);
|
|
4819
|
+
}
|
|
4820
|
+
hours(callback) {
|
|
4821
|
+
return this.#move((date) => date.setTime(date.getTime() + this.value * 36e5), callback);
|
|
4822
|
+
}
|
|
4823
|
+
day(callback) {
|
|
4824
|
+
return this.days(callback);
|
|
4825
|
+
}
|
|
4826
|
+
days(callback) {
|
|
4827
|
+
return this.#move((date) => date.setUTCDate(date.getUTCDate() + this.value), callback);
|
|
4828
|
+
}
|
|
4829
|
+
week(callback) {
|
|
4830
|
+
return this.weeks(callback);
|
|
4831
|
+
}
|
|
4832
|
+
weeks(callback) {
|
|
4833
|
+
return this.#move((date) => date.setUTCDate(date.getUTCDate() + this.value * 7), callback);
|
|
4834
|
+
}
|
|
4835
|
+
month(callback) {
|
|
4836
|
+
return this.months(callback);
|
|
4837
|
+
}
|
|
4838
|
+
months(callback) {
|
|
4839
|
+
return this.#move((date) => date.setUTCMonth(date.getUTCMonth() + this.value), callback);
|
|
4840
|
+
}
|
|
4841
|
+
year(callback) {
|
|
4842
|
+
return this.years(callback);
|
|
4843
|
+
}
|
|
4844
|
+
years(callback) {
|
|
4845
|
+
return this.#move((date) => date.setUTCFullYear(date.getUTCFullYear() + this.value), callback);
|
|
4846
|
+
}
|
|
4847
|
+
/** Laravel's Wormhole::back(). */
|
|
4848
|
+
static back() {
|
|
4849
|
+
return travelBack();
|
|
4850
|
+
}
|
|
4851
|
+
#move(step, callback) {
|
|
4852
|
+
const date = new (realDate())(testNow ?? Date.now());
|
|
4853
|
+
step(date);
|
|
4854
|
+
setTestNow(date);
|
|
4855
|
+
if (callback) return thenTravelBack(callback);
|
|
4856
|
+
}
|
|
4857
|
+
};
|
|
3813
4858
|
function fail2(message, details = {}) {
|
|
3814
4859
|
const error = new AssertionError2({ message, actual: details.actual, expected: details.expected, operator: "fail" });
|
|
3815
4860
|
delete error.diff;
|
|
@@ -3831,7 +4876,7 @@ function format(value) {
|
|
|
3831
4876
|
function escapeHtml2(value) {
|
|
3832
4877
|
return value.replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]);
|
|
3833
4878
|
}
|
|
3834
|
-
function
|
|
4879
|
+
function stripTags2(html) {
|
|
3835
4880
|
return html.replace(/<[^>]*>/g, "");
|
|
3836
4881
|
}
|
|
3837
4882
|
function equals(a, b) {
|
|
@@ -3840,7 +4885,7 @@ function equals(a, b) {
|
|
|
3840
4885
|
function canonical(value) {
|
|
3841
4886
|
if (Array.isArray(value)) return value.map(canonical);
|
|
3842
4887
|
if (typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype) {
|
|
3843
|
-
return Object.fromEntries(Object.keys(value).sort().map((
|
|
4888
|
+
return Object.fromEntries(Object.keys(value).sort().map((key2) => [key2, canonical(value[key2])]));
|
|
3844
4889
|
}
|
|
3845
4890
|
return value;
|
|
3846
4891
|
}
|
|
@@ -3848,20 +4893,20 @@ function isSubset(subset, actual) {
|
|
|
3848
4893
|
if (typeof subset !== "object" || subset === null) return equals(subset, actual);
|
|
3849
4894
|
if (typeof actual !== "object" || actual === null) return false;
|
|
3850
4895
|
if (Array.isArray(subset) !== Array.isArray(actual)) return false;
|
|
3851
|
-
return Object.entries(subset).every(([
|
|
4896
|
+
return Object.entries(subset).every(([key2, value]) => Object.hasOwn(actual, key2) && isSubset(value, actual[key2]));
|
|
3852
4897
|
}
|
|
3853
4898
|
function* objectsIn(value) {
|
|
3854
4899
|
if (typeof value !== "object" || value === null) return;
|
|
3855
4900
|
if (!Array.isArray(value)) yield value;
|
|
3856
4901
|
for (const child of Object.values(value)) yield* objectsIn(child);
|
|
3857
4902
|
}
|
|
3858
|
-
function containsPair(tree,
|
|
3859
|
-
for (const object of objectsIn(tree)) if (Object.hasOwn(object,
|
|
4903
|
+
function containsPair(tree, key2, expected) {
|
|
4904
|
+
for (const object of objectsIn(tree)) if (Object.hasOwn(object, key2) && isSubset(expected, object[key2])) return true;
|
|
3860
4905
|
return false;
|
|
3861
4906
|
}
|
|
3862
4907
|
function assertStructure(structure, data, path = "") {
|
|
3863
|
-
const at = (
|
|
3864
|
-
const hasKey = (
|
|
4908
|
+
const at = (key2) => path === "" ? key2 : `${path}.${key2}`;
|
|
4909
|
+
const hasKey = (key2) => typeof data === "object" && data !== null && Object.hasOwn(data, key2);
|
|
3865
4910
|
if (Array.isArray(structure)) {
|
|
3866
4911
|
for (const item of structure) {
|
|
3867
4912
|
if (typeof item === "string") assert(hasKey(item), `Failed asserting that the JSON has the key [${at(item)}].`, { actual: data });
|
|
@@ -3869,14 +4914,14 @@ function assertStructure(structure, data, path = "") {
|
|
|
3869
4914
|
}
|
|
3870
4915
|
return;
|
|
3871
4916
|
}
|
|
3872
|
-
for (const [
|
|
3873
|
-
if (
|
|
4917
|
+
for (const [key2, nested] of Object.entries(structure)) {
|
|
4918
|
+
if (key2 === "*") {
|
|
3874
4919
|
assert(Array.isArray(data) || typeof data === "object" && data !== null, `Failed asserting that [${path || "the JSON"}] is a list.`, { actual: data });
|
|
3875
4920
|
for (const [index, element] of Object.entries(data)) assertStructure(nested, element, at(index));
|
|
3876
4921
|
continue;
|
|
3877
4922
|
}
|
|
3878
|
-
assert(hasKey(
|
|
3879
|
-
assertStructure(nested, data[
|
|
4923
|
+
assert(hasKey(key2), `Failed asserting that the JSON has the key [${at(key2)}].`, { actual: data });
|
|
4924
|
+
assertStructure(nested, data[key2], at(key2));
|
|
3880
4925
|
}
|
|
3881
4926
|
}
|
|
3882
4927
|
var PendingCommand = class {
|
|
@@ -4079,14 +5124,14 @@ var TESTING_ENVIRONMENT = Object.freeze({
|
|
|
4079
5124
|
SESSION_DRIVER: "array"
|
|
4080
5125
|
});
|
|
4081
5126
|
function applyEnvironment(values, env2 = process.env) {
|
|
4082
|
-
for (const [
|
|
5127
|
+
for (const [key2, value] of Object.entries(values)) env2[key2] ??= value;
|
|
4083
5128
|
}
|
|
4084
5129
|
function clonePlain2(value) {
|
|
4085
5130
|
if (Array.isArray(value)) return value.map((item) => clonePlain2(item));
|
|
4086
5131
|
if (typeof value === "object" && value !== null) {
|
|
4087
5132
|
const proto = Object.getPrototypeOf(value);
|
|
4088
5133
|
if (proto !== Object.prototype && proto !== null) return value;
|
|
4089
|
-
return Object.fromEntries(Object.entries(value).map(([
|
|
5134
|
+
return Object.fromEntries(Object.entries(value).map(([key2, item]) => [key2, clonePlain2(item)]));
|
|
4090
5135
|
}
|
|
4091
5136
|
return value;
|
|
4092
5137
|
}
|
|
@@ -4121,31 +5166,31 @@ var AssertableJson = class _AssertableJson {
|
|
|
4121
5166
|
newScope(props, path) {
|
|
4122
5167
|
return new _AssertableJson(props, path);
|
|
4123
5168
|
}
|
|
4124
|
-
dotPath(
|
|
4125
|
-
if (this.path === void 0) return
|
|
4126
|
-
return
|
|
5169
|
+
dotPath(key2 = "") {
|
|
5170
|
+
if (this.path === void 0) return key2;
|
|
5171
|
+
return key2 === "" ? this.path : `${this.path}.${key2}`;
|
|
4127
5172
|
}
|
|
4128
5173
|
/** The value at `key` in this scope, or the whole scope. */
|
|
4129
|
-
prop(
|
|
4130
|
-
if (
|
|
4131
|
-
return Arr.get(this.props,
|
|
5174
|
+
prop(key2) {
|
|
5175
|
+
if (key2 !== void 0 && this.hasOwnKey(key2)) return this.props[key2];
|
|
5176
|
+
return Arr.get(this.props, key2);
|
|
4132
5177
|
}
|
|
4133
5178
|
/** A literal key of this scope wins over a dot path, like Laravel's Arr::get (keys such as 'items.0.name'). */
|
|
4134
|
-
hasOwnKey(
|
|
4135
|
-
return typeof this.props === "object" && this.props !== null && Object.hasOwn(this.props,
|
|
5179
|
+
hasOwnKey(key2) {
|
|
5180
|
+
return typeof this.props === "object" && this.props !== null && Object.hasOwn(this.props, key2);
|
|
4136
5181
|
}
|
|
4137
|
-
exists(
|
|
4138
|
-
return this.hasOwnKey(
|
|
5182
|
+
exists(key2) {
|
|
5183
|
+
return this.hasOwnKey(key2) || Arr.has(this.props, key2);
|
|
4139
5184
|
}
|
|
4140
|
-
interactsWith(
|
|
4141
|
-
const prop = this.hasOwnKey(String(
|
|
5185
|
+
interactsWith(key2) {
|
|
5186
|
+
const prop = this.hasOwnKey(String(key2)) ? String(key2) : String(key2).split(".")[0];
|
|
4142
5187
|
if (!this.interactedKeys.includes(prop)) this.interactedKeys.push(prop);
|
|
4143
5188
|
}
|
|
4144
5189
|
/** Every property of the scope was checked (called when a scope ends). */
|
|
4145
5190
|
interacted() {
|
|
4146
5191
|
const props = this.prop();
|
|
4147
5192
|
const keys = typeof props === "object" && props !== null ? Object.keys(props) : [];
|
|
4148
|
-
const unexpected = keys.filter((
|
|
5193
|
+
const unexpected = keys.filter((key2) => !this.interactedKeys.includes(key2));
|
|
4149
5194
|
assert(
|
|
4150
5195
|
unexpected.length === 0,
|
|
4151
5196
|
this.path === void 0 ? "Unexpected properties were found on the root level." : `Unexpected properties were found in scope [${this.path}].`,
|
|
@@ -4160,9 +5205,9 @@ var AssertableJson = class _AssertableJson {
|
|
|
4160
5205
|
}
|
|
4161
5206
|
// Has ------------------------------------------------------------------------------------------------------
|
|
4162
5207
|
/** has('key'), has('key', 3), has('key', json => …), has('key', 3, first => …), or has(3) for the root size. */
|
|
4163
|
-
has(
|
|
4164
|
-
if (typeof
|
|
4165
|
-
const name = String(
|
|
5208
|
+
has(key2, length, callback) {
|
|
5209
|
+
if (typeof key2 === "number" && length === void 0) return this.count(key2);
|
|
5210
|
+
const name = String(key2);
|
|
4166
5211
|
assert(this.exists(name), `Property [${this.dotPath(name)}] does not exist.`);
|
|
4167
5212
|
this.interactsWith(name);
|
|
4168
5213
|
if (callback !== void 0) {
|
|
@@ -4178,105 +5223,105 @@ var AssertableJson = class _AssertableJson {
|
|
|
4178
5223
|
/** hasAll(['a', 'b']) or hasAll({ items: 3 }). */
|
|
4179
5224
|
hasAll(keys) {
|
|
4180
5225
|
if (typeof keys === "string" || Array.isArray(keys)) {
|
|
4181
|
-
for (const
|
|
5226
|
+
for (const key2 of [keys].flat()) this.has(key2);
|
|
4182
5227
|
return this;
|
|
4183
5228
|
}
|
|
4184
|
-
for (const [
|
|
5229
|
+
for (const [key2, length] of Object.entries(keys)) this.has(key2, length);
|
|
4185
5230
|
return this;
|
|
4186
5231
|
}
|
|
4187
5232
|
hasAny(keys) {
|
|
4188
5233
|
const list = [keys].flat();
|
|
4189
|
-
const present = list.filter((
|
|
4190
|
-
assert(present.length > 0, `None of properties [${list.map((
|
|
4191
|
-
for (const
|
|
5234
|
+
const present = list.filter((key2) => this.exists(key2));
|
|
5235
|
+
assert(present.length > 0, `None of properties [${list.map((key2) => this.dotPath(key2)).join(", ")}] exist.`);
|
|
5236
|
+
for (const key2 of present) this.interactsWith(key2);
|
|
4192
5237
|
return this;
|
|
4193
5238
|
}
|
|
4194
|
-
missing(
|
|
4195
|
-
assert(!this.exists(
|
|
5239
|
+
missing(key2) {
|
|
5240
|
+
assert(!this.exists(key2), `Property [${this.dotPath(key2)}] was found while it was expected to be missing.`);
|
|
4196
5241
|
return this;
|
|
4197
5242
|
}
|
|
4198
5243
|
missingAll(keys) {
|
|
4199
|
-
for (const
|
|
5244
|
+
for (const key2 of [keys].flat()) this.missing(key2);
|
|
4200
5245
|
return this;
|
|
4201
5246
|
}
|
|
4202
5247
|
/** count(3) checks this scope's size; count('items', 3) a property's. */
|
|
4203
|
-
count(
|
|
4204
|
-
if (typeof
|
|
5248
|
+
count(key2, length) {
|
|
5249
|
+
if (typeof key2 === "number") {
|
|
4205
5250
|
const size2 = sizeOf(this.prop());
|
|
4206
|
-
assert(size2 ===
|
|
5251
|
+
assert(size2 === key2, this.path === void 0 ? "Root level does not have the expected size." : `Property [${this.path}] does not have the expected size.`, { actual: size2, expected: key2 });
|
|
4207
5252
|
return this;
|
|
4208
5253
|
}
|
|
4209
|
-
this.has(
|
|
4210
|
-
const size = sizeOf(this.prop(
|
|
4211
|
-
assert(size === length, `Property [${this.dotPath(
|
|
5254
|
+
this.has(key2);
|
|
5255
|
+
const size = sizeOf(this.prop(key2));
|
|
5256
|
+
assert(size === length, `Property [${this.dotPath(key2)}] does not have the expected size.`, { actual: size, expected: length });
|
|
4212
5257
|
return this;
|
|
4213
5258
|
}
|
|
4214
|
-
countBetween(
|
|
4215
|
-
this.has(
|
|
4216
|
-
const size = sizeOf(this.prop(
|
|
4217
|
-
assert(size >= min && size <= max, `Property [${this.dotPath(
|
|
5259
|
+
countBetween(key2, min, max) {
|
|
5260
|
+
this.has(key2);
|
|
5261
|
+
const size = sizeOf(this.prop(key2));
|
|
5262
|
+
assert(size >= min && size <= max, `Property [${this.dotPath(key2)}] size is not between ${min} and ${max}.`, { actual: size });
|
|
4218
5263
|
return this;
|
|
4219
5264
|
}
|
|
4220
5265
|
// Matching -------------------------------------------------------------------------------------------------
|
|
4221
5266
|
/** where('name', 'Ann'), or where('name', value => value.startsWith('A')). Objects compare ignoring key order. */
|
|
4222
|
-
where(
|
|
4223
|
-
this.has(
|
|
4224
|
-
const actual = this.prop(
|
|
5267
|
+
where(key2, expected) {
|
|
5268
|
+
this.has(key2);
|
|
5269
|
+
const actual = this.prop(key2);
|
|
4225
5270
|
if (typeof expected === "function") {
|
|
4226
|
-
assert(expected(actual), `Property [${this.dotPath(
|
|
5271
|
+
assert(expected(actual), `Property [${this.dotPath(key2)}] was marked as invalid using a closure.`);
|
|
4227
5272
|
return this;
|
|
4228
5273
|
}
|
|
4229
5274
|
const normalized = toPlain(expected);
|
|
4230
|
-
assert(equals(normalized, actual), `Property [${this.dotPath(
|
|
5275
|
+
assert(equals(normalized, actual), `Property [${this.dotPath(key2)}] does not match the expected value.`, { actual, expected: normalized });
|
|
4231
5276
|
return this;
|
|
4232
5277
|
}
|
|
4233
|
-
whereNot(
|
|
4234
|
-
this.has(
|
|
4235
|
-
const actual = this.prop(
|
|
5278
|
+
whereNot(key2, expected) {
|
|
5279
|
+
this.has(key2);
|
|
5280
|
+
const actual = this.prop(key2);
|
|
4236
5281
|
if (typeof expected === "function") {
|
|
4237
|
-
assert(!expected(actual), `Property [${this.dotPath(
|
|
5282
|
+
assert(!expected(actual), `Property [${this.dotPath(key2)}] was marked as invalid using a closure.`);
|
|
4238
5283
|
return this;
|
|
4239
5284
|
}
|
|
4240
|
-
assert(!equals(toPlain(expected), actual), `Property [${this.dotPath(
|
|
5285
|
+
assert(!equals(toPlain(expected), actual), `Property [${this.dotPath(key2)}] contains a value that should be missing: [${key2}, ${format(expected)}]`);
|
|
4241
5286
|
return this;
|
|
4242
5287
|
}
|
|
4243
|
-
whereNull(
|
|
4244
|
-
this.has(
|
|
4245
|
-
const actual = this.prop(
|
|
4246
|
-
assert(actual === null, `Property [${this.dotPath(
|
|
5288
|
+
whereNull(key2) {
|
|
5289
|
+
this.has(key2);
|
|
5290
|
+
const actual = this.prop(key2);
|
|
5291
|
+
assert(actual === null, `Property [${this.dotPath(key2)}] should be null.`, { actual, expected: null });
|
|
4247
5292
|
return this;
|
|
4248
5293
|
}
|
|
4249
|
-
whereNotNull(
|
|
4250
|
-
this.has(
|
|
4251
|
-
assert(this.prop(
|
|
5294
|
+
whereNotNull(key2) {
|
|
5295
|
+
this.has(key2);
|
|
5296
|
+
assert(this.prop(key2) !== null, `Property [${this.dotPath(key2)}] should not be null.`);
|
|
4252
5297
|
return this;
|
|
4253
5298
|
}
|
|
4254
5299
|
whereAll(bindings) {
|
|
4255
|
-
for (const [
|
|
5300
|
+
for (const [key2, value] of Object.entries(bindings)) this.where(key2, value);
|
|
4256
5301
|
return this;
|
|
4257
5302
|
}
|
|
4258
5303
|
/** whereType('id', 'integer'), whereType('name', 'string|null'), whereType('tags', ['array', 'null']). */
|
|
4259
|
-
whereType(
|
|
4260
|
-
this.has(
|
|
5304
|
+
whereType(key2, expected) {
|
|
5305
|
+
this.has(key2);
|
|
4261
5306
|
const types = typeof expected === "string" ? expected.split("|") : expected;
|
|
4262
|
-
const actual = typeOf(this.prop(
|
|
5307
|
+
const actual = typeOf(this.prop(key2));
|
|
4263
5308
|
assert(
|
|
4264
5309
|
types.some((type) => actual.includes(type)),
|
|
4265
|
-
`Property [${this.dotPath(
|
|
5310
|
+
`Property [${this.dotPath(key2)}] is not of expected type [${types.join("|")}].`,
|
|
4266
5311
|
{ actual: actual[0], expected: types.join("|") }
|
|
4267
5312
|
);
|
|
4268
5313
|
return this;
|
|
4269
5314
|
}
|
|
4270
5315
|
whereAllType(bindings) {
|
|
4271
|
-
for (const [
|
|
5316
|
+
for (const [key2, type] of Object.entries(bindings)) this.whereType(key2, type);
|
|
4272
5317
|
return this;
|
|
4273
5318
|
}
|
|
4274
5319
|
/** The list contains every expected value (or one passing the callback); a string contains the substring. */
|
|
4275
|
-
whereContains(
|
|
4276
|
-
this.has(
|
|
4277
|
-
const actual = this.prop(
|
|
5320
|
+
whereContains(key2, expected) {
|
|
5321
|
+
this.has(key2);
|
|
5322
|
+
const actual = this.prop(key2);
|
|
4278
5323
|
if (typeof actual === "string") {
|
|
4279
|
-
assert(actual.includes(String(expected)), `Property [${this.dotPath(
|
|
5324
|
+
assert(actual.includes(String(expected)), `Property [${this.dotPath(key2)}] does not contain [${format(expected)}].`);
|
|
4280
5325
|
return this;
|
|
4281
5326
|
}
|
|
4282
5327
|
const values = typeof actual === "object" && actual !== null ? Object.values(actual) : [actual];
|
|
@@ -4284,13 +5329,13 @@ var AssertableJson = class _AssertableJson {
|
|
|
4284
5329
|
const missing = wanted.filter(
|
|
4285
5330
|
(item) => typeof item === "function" ? !values.some((value) => item(value)) : !values.some((value) => equals(toPlain(item), value))
|
|
4286
5331
|
);
|
|
4287
|
-
assert(missing.length === 0, `Property [${this.dotPath(
|
|
5332
|
+
assert(missing.length === 0, `Property [${this.dotPath(key2)}] does not contain [${missing.map(format).join(", ")}].`, { actual });
|
|
4288
5333
|
return this;
|
|
4289
5334
|
}
|
|
4290
5335
|
// Scoping --------------------------------------------------------------------------------------------------
|
|
4291
|
-
scope(
|
|
4292
|
-
const props = this.prop(
|
|
4293
|
-
const path = this.dotPath(
|
|
5336
|
+
scope(key2, callback) {
|
|
5337
|
+
const props = this.prop(key2);
|
|
5338
|
+
const path = this.dotPath(key2);
|
|
4294
5339
|
assert(typeof props === "object" && props !== null, `Property [${path}] is not scopeable.`);
|
|
4295
5340
|
const scope = this.newScope(props, path);
|
|
4296
5341
|
callback(scope);
|
|
@@ -4312,9 +5357,9 @@ var AssertableJson = class _AssertableJson {
|
|
|
4312
5357
|
if (keys.length === 0) {
|
|
4313
5358
|
fail2(this.path === void 0 ? "Cannot scope directly onto each element of the root level because it is empty." : `Cannot scope directly onto each element of property [${this.path}] because it is empty.`);
|
|
4314
5359
|
}
|
|
4315
|
-
for (const
|
|
4316
|
-
this.interactsWith(
|
|
4317
|
-
this.scope(
|
|
5360
|
+
for (const key2 of keys) {
|
|
5361
|
+
this.interactsWith(key2);
|
|
5362
|
+
this.scope(key2, callback);
|
|
4318
5363
|
}
|
|
4319
5364
|
return this;
|
|
4320
5365
|
}
|
|
@@ -4324,8 +5369,8 @@ var AssertableJson = class _AssertableJson {
|
|
|
4324
5369
|
else otherwise?.(this);
|
|
4325
5370
|
return this;
|
|
4326
5371
|
}
|
|
4327
|
-
dump(
|
|
4328
|
-
console.log(format(this.prop(
|
|
5372
|
+
dump(key2) {
|
|
5373
|
+
console.log(format(this.prop(key2)));
|
|
4329
5374
|
return this;
|
|
4330
5375
|
}
|
|
4331
5376
|
toArray() {
|
|
@@ -4352,7 +5397,7 @@ function inertiaPageOf(response2) {
|
|
|
4352
5397
|
} catch {
|
|
4353
5398
|
page = void 0;
|
|
4354
5399
|
}
|
|
4355
|
-
const valid = typeof page === "object" && page !== null && ["component", "props", "url", "version"].every((
|
|
5400
|
+
const valid = typeof page === "object" && page !== null && ["component", "props", "url", "version"].every((key2) => Object.hasOwn(page, key2));
|
|
4356
5401
|
if (!valid) fail2("Not a valid Inertia response.");
|
|
4357
5402
|
return page;
|
|
4358
5403
|
}
|
|
@@ -4383,7 +5428,7 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
|
|
|
4383
5428
|
if (shouldExist === true || shouldExist === void 0 && config?.get("inertia.testing.ensure_pages_exist", true) !== false) {
|
|
4384
5429
|
const paths = config?.get("inertia.pages.paths", []) ?? [];
|
|
4385
5430
|
const extensions = config?.get("inertia.pages.extensions", []) ?? [];
|
|
4386
|
-
const exists = paths.some((path) => extensions.some((extension) => existsSync6(
|
|
5431
|
+
const exists = paths.some((path) => extensions.some((extension) => existsSync6(join7(path, `${value}.${extension}`))));
|
|
4387
5432
|
if (!exists) fail2(`Inertia page component file [${value}] does not exist.`);
|
|
4388
5433
|
}
|
|
4389
5434
|
return this;
|
|
@@ -4397,23 +5442,23 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
|
|
|
4397
5442
|
return this;
|
|
4398
5443
|
}
|
|
4399
5444
|
/** The flash data has `key` (dot paths allowed), equal to `expected` when given. */
|
|
4400
|
-
hasFlash(
|
|
4401
|
-
_AssertableInertia.assertFlashHas(this.page.flash ?? {},
|
|
5445
|
+
hasFlash(key2, ...expected) {
|
|
5446
|
+
_AssertableInertia.assertFlashHas(this.page.flash ?? {}, key2, ...expected);
|
|
4402
5447
|
return this;
|
|
4403
5448
|
}
|
|
4404
|
-
missingFlash(
|
|
4405
|
-
_AssertableInertia.assertFlashMissing(this.page.flash ?? {},
|
|
5449
|
+
missingFlash(key2) {
|
|
5450
|
+
_AssertableInertia.assertFlashMissing(this.page.flash ?? {}, key2);
|
|
4406
5451
|
return this;
|
|
4407
5452
|
}
|
|
4408
|
-
static assertFlashHas(flash,
|
|
4409
|
-
assert(Arr.has(flash,
|
|
5453
|
+
static assertFlashHas(flash, key2, ...expected) {
|
|
5454
|
+
assert(Arr.has(flash, key2), `Inertia Flash Data is missing key [${key2}].`);
|
|
4410
5455
|
if (expected.length > 0) {
|
|
4411
|
-
const actual = Arr.get(flash,
|
|
4412
|
-
assert(equals(actual, expected[0]), `Inertia Flash Data [${
|
|
5456
|
+
const actual = Arr.get(flash, key2);
|
|
5457
|
+
assert(equals(actual, expected[0]), `Inertia Flash Data [${key2}] does not match expected value.`, { actual, expected: expected[0] });
|
|
4413
5458
|
}
|
|
4414
5459
|
}
|
|
4415
|
-
static assertFlashMissing(flash,
|
|
4416
|
-
assert(!Arr.has(flash,
|
|
5460
|
+
static assertFlashMissing(flash, key2) {
|
|
5461
|
+
assert(!Arr.has(flash, key2), `Inertia Flash Data has unexpected key [${key2}].`);
|
|
4417
5462
|
}
|
|
4418
5463
|
/**
|
|
4419
5464
|
* Requests the page again as a partial reload (only/except) and checks it is the same page. Uses the test's
|
|
@@ -4479,8 +5524,8 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
|
|
|
4479
5524
|
return this.#response?.context.app;
|
|
4480
5525
|
}
|
|
4481
5526
|
};
|
|
4482
|
-
function cookiePrefix(name,
|
|
4483
|
-
return `${createHmac2("sha1",
|
|
5527
|
+
function cookiePrefix(name, key2) {
|
|
5528
|
+
return `${createHmac2("sha1", key2).update(`${name}v2`).digest("hex")}|`;
|
|
4484
5529
|
}
|
|
4485
5530
|
function encryptCookie(encrypter, name, value) {
|
|
4486
5531
|
return encrypter.encrypt(cookiePrefix(name, encrypter.getKey()) + value, false);
|
|
@@ -4512,15 +5557,15 @@ function parseSetCookie(header) {
|
|
|
4512
5557
|
};
|
|
4513
5558
|
for (const attribute of attributes) {
|
|
4514
5559
|
const at = attribute.indexOf("=");
|
|
4515
|
-
const
|
|
5560
|
+
const key2 = (at === -1 ? attribute : attribute.slice(0, at)).trim().toLowerCase();
|
|
4516
5561
|
const value = at === -1 ? "" : attribute.slice(at + 1).trim();
|
|
4517
|
-
if (
|
|
4518
|
-
else if (
|
|
4519
|
-
else if (
|
|
4520
|
-
else if (
|
|
4521
|
-
else if (
|
|
4522
|
-
else if (
|
|
4523
|
-
else if (
|
|
5562
|
+
if (key2 === "path") cookie.path = value;
|
|
5563
|
+
else if (key2 === "domain") cookie.domain = value;
|
|
5564
|
+
else if (key2 === "max-age") cookie.maxAge = Number(value);
|
|
5565
|
+
else if (key2 === "expires") cookie.expires = new Date(value);
|
|
5566
|
+
else if (key2 === "httponly") cookie.httpOnly = true;
|
|
5567
|
+
else if (key2 === "secure") cookie.secure = true;
|
|
5568
|
+
else if (key2 === "samesite") cookie.sameSite = value;
|
|
4524
5569
|
}
|
|
4525
5570
|
return cookie;
|
|
4526
5571
|
}
|
|
@@ -4732,7 +5777,7 @@ ${this.excerpt()}`);
|
|
|
4732
5777
|
}
|
|
4733
5778
|
/** Like assertSee, against the content with its tags stripped. */
|
|
4734
5779
|
assertSeeText(value, escape = true) {
|
|
4735
|
-
const text =
|
|
5780
|
+
const text = stripTags2(this.content);
|
|
4736
5781
|
for (const needle of this.needles(value, escape)) {
|
|
4737
5782
|
assert(text.includes(needle), `Failed asserting that the response text contains [${needle}].
|
|
4738
5783
|
|
|
@@ -4741,14 +5786,14 @@ ${this.excerpt(text)}`);
|
|
|
4741
5786
|
return this;
|
|
4742
5787
|
}
|
|
4743
5788
|
assertDontSeeText(value, escape = true) {
|
|
4744
|
-
const text =
|
|
5789
|
+
const text = stripTags2(this.content);
|
|
4745
5790
|
for (const needle of this.needles(value, escape)) {
|
|
4746
5791
|
assert(!text.includes(needle), `Failed asserting that the response text does not contain [${needle}].`);
|
|
4747
5792
|
}
|
|
4748
5793
|
return this;
|
|
4749
5794
|
}
|
|
4750
5795
|
assertSeeTextInOrder(values, escape = true) {
|
|
4751
|
-
return this.inOrder(
|
|
5796
|
+
return this.inOrder(stripTags2(this.content), this.needles(values, escape));
|
|
4752
5797
|
}
|
|
4753
5798
|
// JSON --------------------------------------------------------------------------------------------------
|
|
4754
5799
|
/**
|
|
@@ -4777,16 +5822,16 @@ ${this.excerpt(text)}`);
|
|
|
4777
5822
|
return inertiaPageOf(this);
|
|
4778
5823
|
}
|
|
4779
5824
|
/** The page props, or one prop by dot path. */
|
|
4780
|
-
inertiaProps(
|
|
4781
|
-
return Arr.get(inertiaPageOf(this).props,
|
|
5825
|
+
inertiaProps(key2) {
|
|
5826
|
+
return Arr.get(inertiaPageOf(this).props, key2);
|
|
4782
5827
|
}
|
|
4783
5828
|
/** Inertia flash data waiting in the session for the next page (after a redirect). */
|
|
4784
|
-
assertInertiaFlash(
|
|
4785
|
-
AssertableInertia.assertFlashHas(this.session().get("inertia.flash_data", {}),
|
|
5829
|
+
assertInertiaFlash(key2, ...expected) {
|
|
5830
|
+
AssertableInertia.assertFlashHas(this.session().get("inertia.flash_data", {}), key2, ...expected);
|
|
4786
5831
|
return this;
|
|
4787
5832
|
}
|
|
4788
|
-
assertInertiaFlashMissing(
|
|
4789
|
-
AssertableInertia.assertFlashMissing(this.session().get("inertia.flash_data", {}),
|
|
5833
|
+
assertInertiaFlashMissing(key2) {
|
|
5834
|
+
AssertableInertia.assertFlashMissing(this.session().get("inertia.flash_data", {}), key2);
|
|
4790
5835
|
return this;
|
|
4791
5836
|
}
|
|
4792
5837
|
/** The response JSON equals `data` exactly (object key order is ignored). */
|
|
@@ -4813,15 +5858,15 @@ ${this.excerpt(text)}`);
|
|
|
4813
5858
|
/** Each key/value pair appears somewhere in the JSON (Laravel's assertJsonFragment). */
|
|
4814
5859
|
assertJsonFragment(data) {
|
|
4815
5860
|
const actual = this.json();
|
|
4816
|
-
for (const [
|
|
4817
|
-
assert(containsPair(actual,
|
|
5861
|
+
for (const [key2, value] of Object.entries(data)) {
|
|
5862
|
+
assert(containsPair(actual, key2, value), `Unable to find JSON fragment ${format({ [key2]: value })} in the response.`, { actual, expected: data });
|
|
4818
5863
|
}
|
|
4819
5864
|
return this;
|
|
4820
5865
|
}
|
|
4821
5866
|
/** Passes when at least one pair of `data` is absent (Laravel's assertJsonMissing). */
|
|
4822
5867
|
assertJsonMissing(data) {
|
|
4823
5868
|
const actual = this.json();
|
|
4824
|
-
const allPresent = Object.entries(data).every(([
|
|
5869
|
+
const allPresent = Object.entries(data).every(([key2, value]) => containsPair(actual, key2, value));
|
|
4825
5870
|
assert(!allPresent, `Found unexpected JSON fragment ${format(data)} in the response.`, { actual });
|
|
4826
5871
|
return this;
|
|
4827
5872
|
}
|
|
@@ -4832,10 +5877,10 @@ ${this.excerpt(text)}`);
|
|
|
4832
5877
|
return this;
|
|
4833
5878
|
}
|
|
4834
5879
|
/** The JSON (or the value at `key`) has `count` elements. */
|
|
4835
|
-
assertJsonCount(count,
|
|
4836
|
-
const value = this.json(
|
|
5880
|
+
assertJsonCount(count, key2) {
|
|
5881
|
+
const value = this.json(key2);
|
|
4837
5882
|
const actual = Array.isArray(value) ? value.length : typeof value === "object" && value !== null ? Object.keys(value).length : -1;
|
|
4838
|
-
assert(actual === count, `Failed to assert that the response ${
|
|
5883
|
+
assert(actual === count, `Failed to assert that the response ${key2 === void 0 ? "" : `[${key2}] `}count matched the expected ${count} (got ${actual}).`, { actual, expected: count });
|
|
4839
5884
|
return this;
|
|
4840
5885
|
}
|
|
4841
5886
|
/** assertRedirect() to the URL of a named route (resolved with the app's UrlGenerator). */
|
|
@@ -4859,7 +5904,7 @@ ${this.excerpt()}`);
|
|
|
4859
5904
|
if (this.content === "") return this;
|
|
4860
5905
|
const errors = Arr.get(this.json(), responseKey);
|
|
4861
5906
|
if (errors === void 0 || errors === null) return this;
|
|
4862
|
-
const unexpected = keys === void 0 ? Object.keys(errors) : [keys].flat().filter((
|
|
5907
|
+
const unexpected = keys === void 0 ? Object.keys(errors) : [keys].flat().filter((key2) => Object.hasOwn(errors, key2));
|
|
4863
5908
|
assert(unexpected.length === 0, `Found unexpected validation error(s) for [${unexpected.join(", ")}]: ${format(errors)}`);
|
|
4864
5909
|
return this;
|
|
4865
5910
|
}
|
|
@@ -4884,38 +5929,38 @@ ${this.excerpt()}`);
|
|
|
4884
5929
|
return session;
|
|
4885
5930
|
}
|
|
4886
5931
|
/** A key (with an optional value or predicate), several keys, or a { key: value } record. */
|
|
4887
|
-
assertSessionHas(
|
|
4888
|
-
if (Array.isArray(
|
|
4889
|
-
for (const k of
|
|
5932
|
+
assertSessionHas(key2, value) {
|
|
5933
|
+
if (Array.isArray(key2)) {
|
|
5934
|
+
for (const k of key2) this.assertSessionHas(k);
|
|
4890
5935
|
return this;
|
|
4891
5936
|
}
|
|
4892
|
-
if (typeof
|
|
5937
|
+
if (typeof key2 === "object") return this.assertSessionHasAll(key2);
|
|
4893
5938
|
const session = this.session();
|
|
4894
5939
|
if (value === void 0) {
|
|
4895
|
-
assert(session.has(
|
|
5940
|
+
assert(session.has(key2), `Session is missing expected key [${key2}].`);
|
|
4896
5941
|
} else if (typeof value === "function") {
|
|
4897
|
-
assert(value(session.get(
|
|
5942
|
+
assert(value(session.get(key2)) === true, `Session value for [${key2}] does not pass the given check.`);
|
|
4898
5943
|
} else {
|
|
4899
|
-
assert(equals(session.get(
|
|
5944
|
+
assert(equals(session.get(key2), value), `Session value for [${key2}] does not match.`, { actual: session.get(key2), expected: value });
|
|
4900
5945
|
}
|
|
4901
5946
|
return this;
|
|
4902
5947
|
}
|
|
4903
5948
|
assertSessionHasAll(bindings) {
|
|
4904
|
-
for (const [
|
|
5949
|
+
for (const [key2, value] of Object.entries(bindings)) this.assertSessionHas(key2, value);
|
|
4905
5950
|
return this;
|
|
4906
5951
|
}
|
|
4907
|
-
assertSessionMissing(
|
|
5952
|
+
assertSessionMissing(key2, value) {
|
|
4908
5953
|
const session = this.session();
|
|
4909
|
-
for (const k of [
|
|
5954
|
+
for (const k of [key2].flat()) {
|
|
4910
5955
|
if (value === void 0) assert(!session.has(k), `Session has unexpected key [${k}].`);
|
|
4911
5956
|
else assert(!equals(session.get(k), value), `Session has unexpected value for [${k}].`);
|
|
4912
5957
|
}
|
|
4913
5958
|
return this;
|
|
4914
5959
|
}
|
|
4915
5960
|
/** Old input flashed with the redirect (withInput()). */
|
|
4916
|
-
assertSessionHasInput(
|
|
5961
|
+
assertSessionHasInput(key2, value) {
|
|
4917
5962
|
const session = this.session();
|
|
4918
|
-
const entries = typeof
|
|
5963
|
+
const entries = typeof key2 === "string" ? [[key2, value]] : Array.isArray(key2) ? key2.map((k) => [k, void 0]) : Object.entries(key2);
|
|
4919
5964
|
for (const [k, expected] of entries) {
|
|
4920
5965
|
const actual = session.getOldInput(k);
|
|
4921
5966
|
assert(actual !== void 0, `Session is missing expected key [${k}] in the old input.`);
|
|
@@ -4935,10 +5980,10 @@ ${this.excerpt()}`);
|
|
|
4935
5980
|
assert(Object.keys(errors).length > 0, `Session has no errors in the [${errorBag}] bag.`);
|
|
4936
5981
|
return this;
|
|
4937
5982
|
}
|
|
4938
|
-
for (const [
|
|
4939
|
-
assert(Object.hasOwn(errors,
|
|
5983
|
+
for (const [key2, messages] of normalizeExpected(expected)) {
|
|
5984
|
+
assert(Object.hasOwn(errors, key2), `Session missing error: ${key2}`, { actual: errors });
|
|
4940
5985
|
for (const message of messages) {
|
|
4941
|
-
assert(errors[
|
|
5986
|
+
assert(errors[key2].includes(message), `Session error for [${key2}] does not include ${format2 === void 0 ? "" : "the formatted "}message ${JSON.stringify(message)}.`, { actual: errors[key2], expected: message });
|
|
4942
5987
|
}
|
|
4943
5988
|
}
|
|
4944
5989
|
return this;
|
|
@@ -4965,7 +6010,7 @@ ${JSON.stringify(found, null, 2)}`, { actual: found, expected: {} });
|
|
|
4965
6010
|
${JSON.stringify(errors, null, 2)}`, { actual: errors, expected: {} });
|
|
4966
6011
|
return this;
|
|
4967
6012
|
}
|
|
4968
|
-
for (const
|
|
6013
|
+
for (const key2 of list) assert(!Object.hasOwn(errors, key2), `Session has unexpected error: ${key2}`, { actual: errors[key2] });
|
|
4969
6014
|
return this;
|
|
4970
6015
|
}
|
|
4971
6016
|
// Cookies -----------------------------------------------------------------------------------------------
|
|
@@ -5016,27 +6061,27 @@ ${JSON.stringify(errors, null, 2)}`, { actual: errors, expected: {} });
|
|
|
5016
6061
|
return this;
|
|
5017
6062
|
}
|
|
5018
6063
|
/** The view received `key` (dot paths allowed), optionally with this value or passing this predicate. */
|
|
5019
|
-
assertViewHas(
|
|
5020
|
-
if (typeof
|
|
6064
|
+
assertViewHas(key2, value) {
|
|
6065
|
+
if (typeof key2 !== "string") return this.assertViewHasAll(key2);
|
|
5021
6066
|
const data = this.viewDataOf(this.ensureView());
|
|
5022
|
-
assert(Arr.has(data,
|
|
5023
|
-
const actual = Arr.get(data,
|
|
5024
|
-
if (typeof value === "function") assert(value(actual) === true, `View data [${
|
|
5025
|
-
else if (value !== void 0) assert(equals(actual, value), `View data [${
|
|
6067
|
+
assert(Arr.has(data, key2), `Failed asserting that the view has key [${key2}].`, { actual: Object.keys(data) });
|
|
6068
|
+
const actual = Arr.get(data, key2);
|
|
6069
|
+
if (typeof value === "function") assert(value(actual) === true, `View data [${key2}] does not pass the given check.`);
|
|
6070
|
+
else if (value !== void 0) assert(equals(actual, value), `View data [${key2}] does not match the expected value.`, { actual, expected: value });
|
|
5026
6071
|
return this;
|
|
5027
6072
|
}
|
|
5028
6073
|
assertViewHasAll(bindings) {
|
|
5029
|
-
if (Array.isArray(bindings)) for (const
|
|
5030
|
-
else for (const [
|
|
6074
|
+
if (Array.isArray(bindings)) for (const key2 of bindings) this.assertViewHas(key2);
|
|
6075
|
+
else for (const [key2, value] of Object.entries(bindings)) this.assertViewHas(key2, value);
|
|
5031
6076
|
return this;
|
|
5032
6077
|
}
|
|
5033
|
-
assertViewMissing(
|
|
5034
|
-
assert(!Arr.has(this.viewDataOf(this.ensureView()),
|
|
6078
|
+
assertViewMissing(key2) {
|
|
6079
|
+
assert(!Arr.has(this.viewDataOf(this.ensureView()), key2), `Failed asserting that the view does not have key [${key2}].`);
|
|
5035
6080
|
return this;
|
|
5036
6081
|
}
|
|
5037
6082
|
/** The data the view received (dot path), or all of it. */
|
|
5038
|
-
viewData(
|
|
5039
|
-
return Arr.get(this.viewDataOf(this.ensureView()),
|
|
6083
|
+
viewData(key2) {
|
|
6084
|
+
return Arr.get(this.viewDataOf(this.ensureView()), key2);
|
|
5040
6085
|
}
|
|
5041
6086
|
// Internals ---------------------------------------------------------------------------------------------
|
|
5042
6087
|
absolute(url2) {
|
|
@@ -5071,15 +6116,15 @@ ${this.excerpt()}`;
|
|
|
5071
6116
|
}
|
|
5072
6117
|
formatted(errors, template) {
|
|
5073
6118
|
if (template === void 0) return errors;
|
|
5074
|
-
return Object.fromEntries(Object.entries(errors).map(([
|
|
6119
|
+
return Object.fromEntries(Object.entries(errors).map(([key2, messages]) => [key2, messages.map((m) => template.replaceAll(":message", m))]));
|
|
5075
6120
|
}
|
|
5076
6121
|
/** Laravel's assertInvalid/assertJsonValidationErrors: each key present; each expected fragment contained in a message. */
|
|
5077
6122
|
assertErrorsContain(errors, expected, where) {
|
|
5078
|
-
for (const [
|
|
5079
|
-
assert(Object.hasOwn(errors,
|
|
5080
|
-
const messages = [errors[
|
|
6123
|
+
for (const [key2, fragments] of normalizeExpected(expected)) {
|
|
6124
|
+
assert(Object.hasOwn(errors, key2), `Failed to find a validation error in the ${where} for key: [${key2}]`, { actual: errors });
|
|
6125
|
+
const messages = [errors[key2]].flat();
|
|
5081
6126
|
for (const fragment of fragments) {
|
|
5082
|
-
assert(messages.some((message) => message.includes(fragment)), `Failed to find a validation error for key and message: [${
|
|
6127
|
+
assert(messages.some((message) => message.includes(fragment)), `Failed to find a validation error for key and message: [${key2}] => [${fragment}]`, { actual: messages, expected: fragment });
|
|
5083
6128
|
}
|
|
5084
6129
|
}
|
|
5085
6130
|
}
|
|
@@ -5112,8 +6157,8 @@ ${this.excerpt()}`;
|
|
|
5112
6157
|
};
|
|
5113
6158
|
function normalizeExpected(expected) {
|
|
5114
6159
|
if (typeof expected === "string") return [[expected, []]];
|
|
5115
|
-
if (Array.isArray(expected)) return expected.map((
|
|
5116
|
-
return Object.entries(expected).map(([
|
|
6160
|
+
if (Array.isArray(expected)) return expected.map((key2) => [key2, []]);
|
|
6161
|
+
return Object.entries(expected).map(([key2, messages]) => [key2, [messages].flat()]);
|
|
5117
6162
|
}
|
|
5118
6163
|
function renderedHtml(view) {
|
|
5119
6164
|
try {
|
|
@@ -5178,6 +6223,11 @@ var HttpClient = class {
|
|
|
5178
6223
|
defaultGuard;
|
|
5179
6224
|
/** Session attributes carried from one request to the next (Laravel keeps one session store per test). */
|
|
5180
6225
|
session;
|
|
6226
|
+
/**
|
|
6227
|
+
* withSession() and flushSession() since the last started session: applied to the stored session (the one the
|
|
6228
|
+
* session cookie points to) once the next request starts it, so they win over what it holds, as in Laravel.
|
|
6229
|
+
*/
|
|
6230
|
+
sessionChanges = [];
|
|
5181
6231
|
/** from(url): the session's previous URL for the next request only (Laravel sets it once). */
|
|
5182
6232
|
previousUrl;
|
|
5183
6233
|
/** Requests whose actingAs users reached the guards (the AuthFactory they went through). */
|
|
@@ -5213,6 +6263,7 @@ var HttpClient = class {
|
|
|
5213
6263
|
const context = { request: request2, app: this.app, client: this };
|
|
5214
6264
|
const result = await scope.run(async () => {
|
|
5215
6265
|
this.beforeRequest(scope, context);
|
|
6266
|
+
if (context.session !== void 0) this.applyPending(context.session);
|
|
5216
6267
|
scope.instance(HttpRequest, new IncomingRequest(request2));
|
|
5217
6268
|
if (scope.bound(AuthFactory)) {
|
|
5218
6269
|
try {
|
|
@@ -5253,6 +6304,7 @@ var HttpClient = class {
|
|
|
5253
6304
|
const session = this.scopeSession(scope);
|
|
5254
6305
|
if (session !== void 0) {
|
|
5255
6306
|
if (this.session !== void 0) session.put(clonePlain2(this.session));
|
|
6307
|
+
if (this.sessionChanges.length > 0) this.applyOnStart(session);
|
|
5256
6308
|
if (this.previousUrl !== void 0) session.setPreviousUrl(this.previousUrl);
|
|
5257
6309
|
context.session = session;
|
|
5258
6310
|
}
|
|
@@ -5285,6 +6337,20 @@ var HttpClient = class {
|
|
|
5285
6337
|
else this.users.set(guard, user);
|
|
5286
6338
|
}
|
|
5287
6339
|
}
|
|
6340
|
+
/** Applies the pending withSession()/flushSession() changes once the session has loaded what it stored. */
|
|
6341
|
+
applyOnStart(session) {
|
|
6342
|
+
const start = session.start.bind(session);
|
|
6343
|
+
session.start = async () => {
|
|
6344
|
+
await start();
|
|
6345
|
+
this.applyPending(session);
|
|
6346
|
+
};
|
|
6347
|
+
}
|
|
6348
|
+
/** Applies the pending withSession()/flushSession() changes to the session, once. */
|
|
6349
|
+
applyPending(session) {
|
|
6350
|
+
if (this.sessionChanges.length === 0) return;
|
|
6351
|
+
for (const change of this.sessionChanges.splice(0)) change(session);
|
|
6352
|
+
if (!session.has("_token")) session.regenerateToken();
|
|
6353
|
+
}
|
|
5288
6354
|
/** The request's (scoped) Session, created now so the carried attributes are in place before StartSession. */
|
|
5289
6355
|
scopeSession(scope) {
|
|
5290
6356
|
if (!scope.bound(Session)) return void 0;
|
|
@@ -5301,14 +6367,14 @@ var HttpClient = class {
|
|
|
5301
6367
|
let body;
|
|
5302
6368
|
const data = options.data ?? {};
|
|
5303
6369
|
if (verb === "GET" || verb === "HEAD") {
|
|
5304
|
-
for (const [
|
|
6370
|
+
for (const [key2, value] of flatten(data)) if (typeof value === "string") url2.searchParams.append(key2, value);
|
|
5305
6371
|
} else if (options.json) {
|
|
5306
6372
|
body = JSON.stringify(data);
|
|
5307
6373
|
} else if (Object.keys(data).length > 0) {
|
|
5308
6374
|
const fields = flatten(data);
|
|
5309
6375
|
if (fields.some(([, value]) => typeof value !== "string")) {
|
|
5310
6376
|
const form = new FormData();
|
|
5311
|
-
for (const [
|
|
6377
|
+
for (const [key2, value] of fields) form.append(key2, value);
|
|
5312
6378
|
body = form;
|
|
5313
6379
|
} else {
|
|
5314
6380
|
body = new URLSearchParams(fields);
|
|
@@ -5351,8 +6417,8 @@ function setClientAddress2(request2, address) {
|
|
|
5351
6417
|
}
|
|
5352
6418
|
function flatten(data, prefix = "") {
|
|
5353
6419
|
const out = [];
|
|
5354
|
-
for (const [
|
|
5355
|
-
const name = prefix === "" ?
|
|
6420
|
+
for (const [key2, value] of Object.entries(data)) {
|
|
6421
|
+
const name = prefix === "" ? key2 : `${prefix}[${key2}]`;
|
|
5356
6422
|
if (value === void 0) continue;
|
|
5357
6423
|
if (value === null) out.push([name, ""]);
|
|
5358
6424
|
else if (value instanceof Blob) out.push([name, value]);
|
|
@@ -5550,14 +6616,17 @@ var MakesHttpRequests = class extends InteractsWithApplication {
|
|
|
5550
6616
|
this.http.previousUrl = url2;
|
|
5551
6617
|
return this.withHeader("Referer", url2);
|
|
5552
6618
|
}
|
|
5553
|
-
/** Puts data in the session for the following requests (Laravel's withSession()). */
|
|
6619
|
+
/** Puts data in the session for the following requests, over what the stored session holds (Laravel's withSession()). */
|
|
5554
6620
|
withSession(data) {
|
|
5555
|
-
|
|
6621
|
+
const values = clonePlain2(data);
|
|
6622
|
+
this.http.session = { ...this.http.session, ...values };
|
|
6623
|
+
this.http.sessionChanges.push((session) => session.put(clonePlain2(values)));
|
|
5556
6624
|
return this;
|
|
5557
6625
|
}
|
|
5558
|
-
/** Empties the session
|
|
6626
|
+
/** Empties the session, the stored one included, so the next request starts with none (Laravel's flushSession()). */
|
|
5559
6627
|
flushSession() {
|
|
5560
6628
|
this.http.session = {};
|
|
6629
|
+
this.http.sessionChanges.splice(0, Infinity, (session) => session.flush());
|
|
5561
6630
|
return this;
|
|
5562
6631
|
}
|
|
5563
6632
|
/** Every following request is authenticated as `user` on `guard` (which also becomes the default guard). */
|
|
@@ -5696,15 +6765,39 @@ var InteractsWithConsole = class extends InteractsWithDatabase {
|
|
|
5696
6765
|
return new PendingCommand(this.app, command, parameters);
|
|
5697
6766
|
}
|
|
5698
6767
|
};
|
|
5699
|
-
var
|
|
6768
|
+
var InteractsWithTime = class extends InteractsWithConsole {
|
|
6769
|
+
travelTo(date, callback) {
|
|
6770
|
+
return callback ? travelTo(date, callback) : travelTo(date);
|
|
6771
|
+
}
|
|
6772
|
+
travel(value) {
|
|
6773
|
+
return travel(value);
|
|
6774
|
+
}
|
|
6775
|
+
travelBack() {
|
|
6776
|
+
return travelBack();
|
|
6777
|
+
}
|
|
6778
|
+
freezeTime(callback) {
|
|
6779
|
+
return callback ? freezeTime(callback) : freezeTime();
|
|
6780
|
+
}
|
|
6781
|
+
freezeSecond(callback) {
|
|
6782
|
+
return callback ? freezeSecond(callback) : freezeSecond();
|
|
6783
|
+
}
|
|
6784
|
+
async tearDown() {
|
|
6785
|
+
try {
|
|
6786
|
+
await super.tearDown();
|
|
6787
|
+
} finally {
|
|
6788
|
+
travelBack();
|
|
6789
|
+
}
|
|
6790
|
+
}
|
|
6791
|
+
};
|
|
6792
|
+
var TestCase = class extends InteractsWithTime {
|
|
5700
6793
|
};
|
|
5701
|
-
function
|
|
6794
|
+
function install2(token2, fake) {
|
|
5702
6795
|
facade(token2).swap(fake);
|
|
5703
6796
|
return fake;
|
|
5704
6797
|
}
|
|
5705
6798
|
function bound(token2) {
|
|
5706
|
-
const
|
|
5707
|
-
return
|
|
6799
|
+
const container2 = currentContainer();
|
|
6800
|
+
return container2 !== void 0 && container2.bound(token2) ? container2.make(token2) : void 0;
|
|
5708
6801
|
}
|
|
5709
6802
|
function nameOf(target2) {
|
|
5710
6803
|
if (typeof target2 === "string") return target2;
|
|
@@ -5731,6 +6824,23 @@ function isQueueable(job) {
|
|
|
5731
6824
|
if (QUEUE_OPTIONS.some((option) => members[option] !== void 0)) return true;
|
|
5732
6825
|
return typeof members.uniqueId === "function" || typeof members.retryUntil === "function";
|
|
5733
6826
|
}
|
|
6827
|
+
var isClass = (value) => typeof value === "function" && typeof value.prototype?.handle === "function";
|
|
6828
|
+
var asPair = (listener) => {
|
|
6829
|
+
if (Array.isArray(listener) && listener.length === 2 && typeof listener[1] === "string") return [listener[0], listener[1]];
|
|
6830
|
+
return isClass(listener) ? [listener, "handle"] : void 0;
|
|
6831
|
+
};
|
|
6832
|
+
function listenerMatches(registered, expected) {
|
|
6833
|
+
if (expected === Function) return typeof registered === "function" && !isClass(registered);
|
|
6834
|
+
const [left, right] = [asPair(registered), asPair(expected)];
|
|
6835
|
+
if (left && right) return left[0] === right[0] && left[1] === right[1];
|
|
6836
|
+
return registered === expected;
|
|
6837
|
+
}
|
|
6838
|
+
function listenerName(listener) {
|
|
6839
|
+
const pair = asPair(listener);
|
|
6840
|
+
if (pair === void 0) return "Closure";
|
|
6841
|
+
const [owner, method] = pair;
|
|
6842
|
+
return Array.isArray(listener) ? `${owner.name}@${method}` : owner.name;
|
|
6843
|
+
}
|
|
5734
6844
|
var EventFake = class _EventFake {
|
|
5735
6845
|
constructor(dispatcher, eventsToFake = [], eventsToDispatch = []) {
|
|
5736
6846
|
this.dispatcher = dispatcher;
|
|
@@ -5743,11 +6853,11 @@ var EventFake = class _EventFake {
|
|
|
5743
6853
|
events = [];
|
|
5744
6854
|
/** Installs the fake (on the current container) and returns it: `const events = EventFake.fake([OrderShipped])`. */
|
|
5745
6855
|
static fake(eventsToFake = []) {
|
|
5746
|
-
return
|
|
6856
|
+
return install2(EventDispatcher, new _EventFake(_EventFake.original(), [eventsToFake].flat()));
|
|
5747
6857
|
}
|
|
5748
6858
|
/** Fakes every event except these. */
|
|
5749
6859
|
static fakeExcept(eventsToDispatch) {
|
|
5750
|
-
return
|
|
6860
|
+
return install2(EventDispatcher, new _EventFake(_EventFake.original(), [], [eventsToDispatch].flat()));
|
|
5751
6861
|
}
|
|
5752
6862
|
static original() {
|
|
5753
6863
|
const current = bound(EventDispatcher);
|
|
@@ -5804,9 +6914,20 @@ var EventFake = class _EventFake {
|
|
|
5804
6914
|
|
|
5805
6915
|
- ${names.join("\n- ")}`);
|
|
5806
6916
|
}
|
|
5807
|
-
/**
|
|
5808
|
-
|
|
5809
|
-
|
|
6917
|
+
/**
|
|
6918
|
+
* Laravel's assertListening(event, listener): the original dispatcher has that listener for the event (a class, a
|
|
6919
|
+
* [class, method] pair, the same closure, or Function for any closure; wildcard listeners count for a string event).
|
|
6920
|
+
* With only the event, any listener will do.
|
|
6921
|
+
*/
|
|
6922
|
+
assertListening(event, listener) {
|
|
6923
|
+
if (listener === void 0) {
|
|
6924
|
+
assert(this.hasListeners(event), `Event [${nameOf(event)}] does not have any listeners.`);
|
|
6925
|
+
return;
|
|
6926
|
+
}
|
|
6927
|
+
assert(
|
|
6928
|
+
this.listenersFor(event).some((registered) => listenerMatches(registered, listener)),
|
|
6929
|
+
`Event [${nameOf(event)}] does not have the [${listenerName(listener)}] listener attached to it`
|
|
6930
|
+
);
|
|
5810
6931
|
}
|
|
5811
6932
|
/** The recorded events (objects, or names for string events) that match. */
|
|
5812
6933
|
dispatched(event, callback) {
|
|
@@ -5815,6 +6936,17 @@ var EventFake = class _EventFake {
|
|
|
5815
6936
|
hasDispatched(event) {
|
|
5816
6937
|
return this.dispatched(event).length > 0;
|
|
5817
6938
|
}
|
|
6939
|
+
/** The original dispatcher's listeners for the event, as registered, wildcard ones included for a string event. */
|
|
6940
|
+
listenersFor(event) {
|
|
6941
|
+
const raw = this.dispatcher?.getRawListeners?.();
|
|
6942
|
+
if (raw === void 0) return [];
|
|
6943
|
+
const listeners = [...raw.get(event) ?? []];
|
|
6944
|
+
if (typeof event !== "string") return listeners;
|
|
6945
|
+
for (const [key2, list] of raw) {
|
|
6946
|
+
if (typeof key2 === "string" && key2 !== event && key2.includes("*") && Str.is(key2, event)) listeners.push(...list);
|
|
6947
|
+
}
|
|
6948
|
+
return listeners;
|
|
6949
|
+
}
|
|
5818
6950
|
shouldFake(event) {
|
|
5819
6951
|
if (matchesAny(event, this.eventsToDispatch)) return false;
|
|
5820
6952
|
return this.eventsToFake.length === 0 || matchesAny(event, this.eventsToFake);
|
|
@@ -5868,10 +7000,10 @@ var BusFake = class _BusFake {
|
|
|
5868
7000
|
/** The rest of the chain, by the first job (Bus.chain([a, b, c]) records a with [b, c]). */
|
|
5869
7001
|
chains = /* @__PURE__ */ new Map();
|
|
5870
7002
|
static fake(jobsToFake = []) {
|
|
5871
|
-
return
|
|
7003
|
+
return install2(BusDispatcher, new _BusFake(_BusFake.original(), [jobsToFake].flat()));
|
|
5872
7004
|
}
|
|
5873
7005
|
static fakeExcept(jobsToDispatch) {
|
|
5874
|
-
return
|
|
7006
|
+
return install2(BusDispatcher, new _BusFake(_BusFake.original(), [], [jobsToDispatch].flat()));
|
|
5875
7007
|
}
|
|
5876
7008
|
static original() {
|
|
5877
7009
|
const current = bound(BusDispatcher);
|
|
@@ -6076,10 +7208,10 @@ var QueueFake = class _QueueFake {
|
|
|
6076
7208
|
jobsToPush;
|
|
6077
7209
|
jobs = [];
|
|
6078
7210
|
static fake(jobsToFake = []) {
|
|
6079
|
-
return
|
|
7211
|
+
return install2(QueueManager, new _QueueFake(_QueueFake.original(), [jobsToFake].flat()));
|
|
6080
7212
|
}
|
|
6081
7213
|
static fakeExcept(jobsToPush) {
|
|
6082
|
-
return
|
|
7214
|
+
return install2(QueueManager, new _QueueFake(_QueueFake.original(), [], [jobsToPush].flat()));
|
|
6083
7215
|
}
|
|
6084
7216
|
static original() {
|
|
6085
7217
|
const current = bound(QueueManager);
|
|
@@ -6258,13 +7390,14 @@ var MailFake = class _MailFake {
|
|
|
6258
7390
|
records = [];
|
|
6259
7391
|
static fake() {
|
|
6260
7392
|
const current = bound(MailManager);
|
|
6261
|
-
return
|
|
7393
|
+
return install2(MailManager, new _MailFake(current instanceof _MailFake ? current.manager : current));
|
|
6262
7394
|
}
|
|
6263
7395
|
// MailManager ----------------------------------------------------------------------------------------------
|
|
6264
7396
|
mailer(name) {
|
|
6265
7397
|
return {
|
|
6266
7398
|
to: (recipients) => this.pending(name, recipients),
|
|
6267
7399
|
send: (mailable) => this.sendVia(name, mailable, empty()),
|
|
7400
|
+
sendNow: (mailable) => this.sendNowVia(name, mailable, empty()),
|
|
6268
7401
|
queue: (mailable) => this.queueVia(name, mailable, empty(), void 0)
|
|
6269
7402
|
};
|
|
6270
7403
|
}
|
|
@@ -6274,6 +7407,10 @@ var MailFake = class _MailFake {
|
|
|
6274
7407
|
send(mailable) {
|
|
6275
7408
|
return this.sendVia(void 0, mailable, empty());
|
|
6276
7409
|
}
|
|
7410
|
+
/** Recorded as sent, never queued, like the real sendNow(). */
|
|
7411
|
+
sendNow(mailable) {
|
|
7412
|
+
return this.sendNowVia(void 0, mailable, empty());
|
|
7413
|
+
}
|
|
6277
7414
|
queue(mailable) {
|
|
6278
7415
|
return this.queueVia(void 0, mailable, empty(), void 0);
|
|
6279
7416
|
}
|
|
@@ -6357,6 +7494,7 @@ var MailFake = class _MailFake {
|
|
|
6357
7494
|
return pending;
|
|
6358
7495
|
},
|
|
6359
7496
|
send: (mailable) => this.sendVia(mailer, mailable, overrides),
|
|
7497
|
+
sendNow: (mailable) => this.sendNowVia(mailer, mailable, overrides),
|
|
6360
7498
|
queue: (mailable) => this.queueVia(mailer, mailable, overrides, void 0),
|
|
6361
7499
|
later: (delay, mailable) => this.queueVia(mailer, mailable, overrides, delay)
|
|
6362
7500
|
};
|
|
@@ -6366,6 +7504,10 @@ var MailFake = class _MailFake {
|
|
|
6366
7504
|
await this.record(mailer, mailable, overrides, isQueueable(mailable), void 0);
|
|
6367
7505
|
return void 0;
|
|
6368
7506
|
}
|
|
7507
|
+
async sendNowVia(mailer, mailable, overrides) {
|
|
7508
|
+
await this.record(mailer, mailable, overrides, false, void 0);
|
|
7509
|
+
return void 0;
|
|
7510
|
+
}
|
|
6369
7511
|
async queueVia(mailer, mailable, overrides, delay) {
|
|
6370
7512
|
await this.record(mailer, mailable, overrides, true, delay);
|
|
6371
7513
|
}
|
|
@@ -6425,8 +7567,8 @@ function sameNotifiable(a, b) {
|
|
|
6425
7567
|
const model = n;
|
|
6426
7568
|
return typeof model.getKey === "function" ? model.getKey() : void 0;
|
|
6427
7569
|
};
|
|
6428
|
-
const
|
|
6429
|
-
return
|
|
7570
|
+
const key2 = keyOf(a);
|
|
7571
|
+
return key2 !== void 0 && a.constructor === b.constructor && key2 === keyOf(b);
|
|
6430
7572
|
}
|
|
6431
7573
|
var NotificationFake = class _NotificationFake {
|
|
6432
7574
|
constructor(dispatcher = void 0) {
|
|
@@ -6436,7 +7578,7 @@ var NotificationFake = class _NotificationFake {
|
|
|
6436
7578
|
notifications = [];
|
|
6437
7579
|
static fake() {
|
|
6438
7580
|
const current = bound(NotificationDispatcher);
|
|
6439
|
-
return
|
|
7581
|
+
return install2(NotificationDispatcher, new _NotificationFake(current instanceof _NotificationFake ? current.dispatcher : current));
|
|
6440
7582
|
}
|
|
6441
7583
|
// NotificationDispatcher -------------------------------------------------------------------------------------
|
|
6442
7584
|
async send(notifiables, notification) {
|
|
@@ -6611,8 +7753,8 @@ var DISK_TYPES = {
|
|
|
6611
7753
|
wasm: "application/wasm"
|
|
6612
7754
|
};
|
|
6613
7755
|
function diskType(extension) {
|
|
6614
|
-
const
|
|
6615
|
-
return Object.hasOwn(DISK_TYPES,
|
|
7756
|
+
const key2 = extension.replace(/^\./, "").toLowerCase();
|
|
7757
|
+
return Object.hasOwn(DISK_TYPES, key2) ? DISK_TYPES[key2] : void 0;
|
|
6616
7758
|
}
|
|
6617
7759
|
function hashName(file) {
|
|
6618
7760
|
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -6644,8 +7786,8 @@ var FakeDisk = class {
|
|
|
6644
7786
|
/** Directories made explicitly (others exist because files are in them). */
|
|
6645
7787
|
made = /* @__PURE__ */ new Set();
|
|
6646
7788
|
async exists(path) {
|
|
6647
|
-
const
|
|
6648
|
-
return this.stored.has(
|
|
7789
|
+
const key2 = normalize(path);
|
|
7790
|
+
return this.stored.has(key2) || this.isDirectory(key2);
|
|
6649
7791
|
}
|
|
6650
7792
|
async missing(path) {
|
|
6651
7793
|
return !await this.exists(path);
|
|
@@ -6666,9 +7808,9 @@ var FakeDisk = class {
|
|
|
6666
7808
|
});
|
|
6667
7809
|
}
|
|
6668
7810
|
async put(path, contents, options = {}) {
|
|
6669
|
-
const
|
|
7811
|
+
const key2 = normalize(path);
|
|
6670
7812
|
const bytes = typeof contents === "string" ? new TextEncoder().encode(contents) : contents instanceof Uint8Array ? contents.slice() : await collect(contents);
|
|
6671
|
-
this.write(
|
|
7813
|
+
this.write(key2, bytes, options.mimeType ?? diskType(posix.extname(key2)), options);
|
|
6672
7814
|
return true;
|
|
6673
7815
|
}
|
|
6674
7816
|
putFile(directory, file, options = {}) {
|
|
@@ -6683,8 +7825,8 @@ var FakeDisk = class {
|
|
|
6683
7825
|
this.write(path, new Uint8Array(await file.arrayBuffer()), options.mimeType ?? diskType(file.extension()), options);
|
|
6684
7826
|
return path;
|
|
6685
7827
|
}
|
|
6686
|
-
write(
|
|
6687
|
-
this.stored.set(
|
|
7828
|
+
write(key2, bytes, mimeType, options) {
|
|
7829
|
+
this.stored.set(key2, { bytes, visibility: options.visibility ?? "private", mimeType, modified: /* @__PURE__ */ new Date() });
|
|
6688
7830
|
}
|
|
6689
7831
|
async delete(paths) {
|
|
6690
7832
|
for (const path of [paths].flat()) this.stored.delete(normalize(path));
|
|
@@ -6773,8 +7915,8 @@ var FakeDisk = class {
|
|
|
6773
7915
|
if (file === void 0) throw new FakeDiskError("FileNotFoundError", `File not found at path: ${path}`, path);
|
|
6774
7916
|
return file;
|
|
6775
7917
|
}
|
|
6776
|
-
isDirectory(
|
|
6777
|
-
return this.made.has(
|
|
7918
|
+
isDirectory(key2) {
|
|
7919
|
+
return this.made.has(key2) || [...this.stored.keys()].some((path) => key2 !== "" && path.startsWith(`${key2}/`));
|
|
6778
7920
|
}
|
|
6779
7921
|
};
|
|
6780
7922
|
async function collect(stream) {
|
|
@@ -6799,7 +7941,7 @@ var StorageFake = class _StorageFake {
|
|
|
6799
7941
|
static fake(disk) {
|
|
6800
7942
|
const current = bound(FilesystemManager);
|
|
6801
7943
|
const config = bound(ConfigRepository);
|
|
6802
|
-
const fake = current instanceof _StorageFake ? current :
|
|
7944
|
+
const fake = current instanceof _StorageFake ? current : install2(FilesystemManager, new _StorageFake(current, config?.get("filesystems.default", "local") ?? "local"));
|
|
6803
7945
|
const name = disk ?? fake.defaultDisk;
|
|
6804
7946
|
const faked = new FakeDisk(name);
|
|
6805
7947
|
fake.disks.set(name, faked);
|
|
@@ -6975,8 +8117,8 @@ function lzwZeros(pixels, minCodeSize) {
|
|
|
6975
8117
|
let current = 0;
|
|
6976
8118
|
out.write(clear, codeSize);
|
|
6977
8119
|
for (let i = 1; i < pixels; i++) {
|
|
6978
|
-
const
|
|
6979
|
-
const known = table.get(
|
|
8120
|
+
const key2 = current << 8;
|
|
8121
|
+
const known = table.get(key2);
|
|
6980
8122
|
if (known !== void 0) {
|
|
6981
8123
|
current = known;
|
|
6982
8124
|
continue;
|
|
@@ -6989,7 +8131,7 @@ function lzwZeros(pixels, minCodeSize) {
|
|
|
6989
8131
|
next = end + 1;
|
|
6990
8132
|
} else {
|
|
6991
8133
|
if (next >= 1 << codeSize) codeSize++;
|
|
6992
|
-
table.set(
|
|
8134
|
+
table.set(key2, next++);
|
|
6993
8135
|
}
|
|
6994
8136
|
current = 0;
|
|
6995
8137
|
}
|
|
@@ -7110,6 +8252,7 @@ var FakeFile = {
|
|
|
7110
8252
|
};
|
|
7111
8253
|
|
|
7112
8254
|
export {
|
|
8255
|
+
Config,
|
|
7113
8256
|
Repository,
|
|
7114
8257
|
env,
|
|
7115
8258
|
ApplicationBuilder,
|
|
@@ -7187,18 +8330,27 @@ export {
|
|
|
7187
8330
|
addApiRouting,
|
|
7188
8331
|
registerHttpCommands,
|
|
7189
8332
|
HttpServiceProvider,
|
|
7190
|
-
On,
|
|
7191
|
-
Locked,
|
|
7192
|
-
Computed,
|
|
7193
|
-
ErrorBag,
|
|
7194
|
-
PendingEvent,
|
|
7195
|
-
Component,
|
|
7196
8333
|
LiveError,
|
|
7197
8334
|
CorruptComponentPayloadError,
|
|
7198
8335
|
MethodNotCallableError,
|
|
7199
8336
|
PropertyNotUpdatableError,
|
|
7200
8337
|
LiveComponentNotFoundError,
|
|
7201
8338
|
InvalidLiveRequestError,
|
|
8339
|
+
InvalidUploadSignatureError,
|
|
8340
|
+
TemporaryFileNotFoundError,
|
|
8341
|
+
FileNotPreviewableError,
|
|
8342
|
+
UploadValidationError,
|
|
8343
|
+
On,
|
|
8344
|
+
Locked,
|
|
8345
|
+
Computed,
|
|
8346
|
+
TEMPORARY_UPLOAD_DEFAULTS,
|
|
8347
|
+
TemporaryUploadedFile,
|
|
8348
|
+
ErrorBag,
|
|
8349
|
+
PendingEvent,
|
|
8350
|
+
Component,
|
|
8351
|
+
UPLOAD_URI,
|
|
8352
|
+
PREVIEW_URI,
|
|
8353
|
+
WithFileUploads,
|
|
7202
8354
|
TestRequestScope,
|
|
7203
8355
|
Testable,
|
|
7204
8356
|
live,
|
|
@@ -7211,6 +8363,12 @@ export {
|
|
|
7211
8363
|
UPDATE_URI,
|
|
7212
8364
|
LiveServiceProvider,
|
|
7213
8365
|
Pagination,
|
|
8366
|
+
travelTo,
|
|
8367
|
+
travelBack,
|
|
8368
|
+
freezeTime,
|
|
8369
|
+
freezeSecond,
|
|
8370
|
+
travel,
|
|
8371
|
+
Wormhole,
|
|
7214
8372
|
PendingCommand,
|
|
7215
8373
|
RefreshDatabaseState,
|
|
7216
8374
|
runCommand,
|
|
@@ -7239,8 +8397,9 @@ export {
|
|
|
7239
8397
|
MakesHttpRequests,
|
|
7240
8398
|
InteractsWithDatabase,
|
|
7241
8399
|
InteractsWithConsole,
|
|
8400
|
+
InteractsWithTime,
|
|
7242
8401
|
TestCase,
|
|
7243
|
-
|
|
8402
|
+
install2,
|
|
7244
8403
|
nameOf,
|
|
7245
8404
|
matches,
|
|
7246
8405
|
EventFake,
|
|
@@ -7256,4 +8415,4 @@ export {
|
|
|
7256
8415
|
StorageFake,
|
|
7257
8416
|
FakeFile
|
|
7258
8417
|
};
|
|
7259
|
-
//# sourceMappingURL=chunk-
|
|
8418
|
+
//# sourceMappingURL=chunk-4MWG3B7G.js.map
|