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