@orieljs/framework 0.1.0-alpha.2 → 0.1.0-alpha.4
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/dist/{chunk-TIE5OAMF.js → chunk-3DETYZMZ.js} +7 -3
- package/dist/{chunk-TIE5OAMF.js.map → chunk-3DETYZMZ.js.map} +1 -1
- package/dist/{chunk-IRHK7GH7.js → chunk-DCI2T7WJ.js} +24 -3
- package/dist/chunk-DCI2T7WJ.js.map +1 -0
- package/dist/{chunk-UPZAML2Q.js → chunk-SBZQ6L3Z.js} +3312 -196
- package/dist/chunk-SBZQ6L3Z.js.map +1 -0
- package/dist/{chunk-3ZRPBRAE.js → chunk-WSGWYXAO.js} +3 -1
- package/dist/chunk-WSGWYXAO.js.map +1 -0
- package/dist/contracts.d.ts +185 -144
- package/dist/contracts.js +3 -1
- package/dist/index.d-DOlTeG-8.d.ts +99 -0
- package/dist/index.d.ts +593 -326
- package/dist/index.js +844 -2053
- package/dist/index.js.map +1 -1
- package/dist/{jsx-runtime-CZ6JqBQo.d.ts → jsx-runtime-WKTl3EEw.d.ts} +8 -99
- package/dist/jsx-runtime.d.ts +2 -1
- package/dist/jsx-runtime.js +3 -3
- package/dist/testing-D3dCOkTm.d.ts +2190 -0
- package/dist/testing.d.ts +3 -1553
- package/dist/testing.js +7 -3
- package/package.json +31 -29
- package/dist/chunk-3ZRPBRAE.js.map +0 -1
- package/dist/chunk-IRHK7GH7.js.map +0 -1
- package/dist/chunk-UPZAML2Q.js.map +0 -1
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Arr,
|
|
3
3
|
ClassRegistry as ClassRegistry2,
|
|
4
|
+
Collection,
|
|
4
5
|
Container,
|
|
5
6
|
ContainerNotAvailableError,
|
|
7
|
+
ServiceProvider,
|
|
6
8
|
Str,
|
|
7
9
|
currentContainer,
|
|
8
|
-
facade
|
|
9
|
-
|
|
10
|
+
facade,
|
|
11
|
+
inject
|
|
12
|
+
} from "./chunk-DCI2T7WJ.js";
|
|
10
13
|
import {
|
|
11
14
|
Application,
|
|
12
15
|
AuthFactory,
|
|
@@ -14,6 +17,8 @@ import {
|
|
|
14
17
|
ClassRegistry,
|
|
15
18
|
ConfigRepository,
|
|
16
19
|
ConsoleKernel,
|
|
20
|
+
CookieJar,
|
|
21
|
+
CurrentRoute,
|
|
17
22
|
DatabaseManager,
|
|
18
23
|
Encrypter,
|
|
19
24
|
EventDispatcher,
|
|
@@ -22,15 +27,23 @@ import {
|
|
|
22
27
|
FilesystemManager,
|
|
23
28
|
HttpKernel,
|
|
24
29
|
HttpRequest,
|
|
30
|
+
LiveManager,
|
|
31
|
+
LogManager,
|
|
25
32
|
MailManager,
|
|
26
33
|
MiddlewareCallbacks,
|
|
27
34
|
NotificationDispatcher,
|
|
35
|
+
PipelineFactory,
|
|
28
36
|
QueueManager,
|
|
37
|
+
Redirector,
|
|
38
|
+
ResponseFactory,
|
|
39
|
+
Router,
|
|
29
40
|
RoutingPaths,
|
|
30
41
|
Session,
|
|
31
42
|
UrlGenerator,
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
ValidationFactory,
|
|
44
|
+
ViewFactory,
|
|
45
|
+
token
|
|
46
|
+
} from "./chunk-WSGWYXAO.js";
|
|
34
47
|
|
|
35
48
|
// ../Config/dist/index.js
|
|
36
49
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -80,12 +93,20 @@ function env(key, defaultValue) {
|
|
|
80
93
|
const quoted = /^(["'])(.*)\1$/s.exec(raw);
|
|
81
94
|
return quoted ? quoted[2] : raw;
|
|
82
95
|
}
|
|
96
|
+
function singleQuoted(source) {
|
|
97
|
+
const keys = /* @__PURE__ */ new Set();
|
|
98
|
+
for (const match of source.matchAll(/^[ \t]*(?:export[ \t]+)?([\w.-]+)[ \t]*=[ \t]*'/gm)) keys.add(match[1]);
|
|
99
|
+
return keys;
|
|
100
|
+
}
|
|
83
101
|
function loadEnvironment(basePath) {
|
|
84
102
|
const appEnv = process.env.APP_ENV;
|
|
85
103
|
const specific = appEnv ? join(basePath, `.env.${appEnv}`) : void 0;
|
|
86
104
|
const file = specific && existsSync(specific) ? specific : join(basePath, ".env");
|
|
87
105
|
if (!existsSync(file)) return void 0;
|
|
88
|
-
|
|
106
|
+
const source = readFileSync(file, "utf8");
|
|
107
|
+
const literal = singleQuoted(source);
|
|
108
|
+
for (const [key, raw] of Object.entries(parse(source))) {
|
|
109
|
+
const value = literal.has(key) ? raw : raw.replace(/\$\{([A-Za-z_][A-Za-z0-9_.]*)\}/g, (match, name) => process.env[name] ?? match);
|
|
89
110
|
if (process.env[key] === void 0) process.env[key] = value;
|
|
90
111
|
}
|
|
91
112
|
return file;
|
|
@@ -132,10 +153,11 @@ var ApplicationBuilder = class {
|
|
|
132
153
|
return this;
|
|
133
154
|
}
|
|
134
155
|
withRouting(options) {
|
|
135
|
-
const abs = (p) =>
|
|
156
|
+
const abs = (p) => isAbsolute(p) ? p : join4(this.app.basePath(), p);
|
|
157
|
+
const each = (p) => Array.isArray(p) ? p.map(abs) : abs(p);
|
|
136
158
|
const paths = { apiPrefix: options.apiPrefix ?? "api" };
|
|
137
|
-
if (options.web !== void 0) paths.web =
|
|
138
|
-
if (options.api !== void 0) paths.api =
|
|
159
|
+
if (options.web !== void 0) paths.web = each(options.web);
|
|
160
|
+
if (options.api !== void 0) paths.api = each(options.api);
|
|
139
161
|
if (options.console !== void 0) paths.console = abs(options.console);
|
|
140
162
|
if (options.health !== void 0) paths.health = options.health;
|
|
141
163
|
this.routing = paths;
|
|
@@ -315,9 +337,9 @@ var Vite = class {
|
|
|
315
337
|
const stylesheets = uniqueTags.filter((tag) => tag.startsWith("<link"));
|
|
316
338
|
const scripts = uniqueTags.filter((tag) => !tag.startsWith("<link"));
|
|
317
339
|
const seen = /* @__PURE__ */ new Set();
|
|
318
|
-
const uniquePreloads = preloads.filter(([,
|
|
319
|
-
const sorted = [...uniquePreloads.filter(([,
|
|
320
|
-
const preloadTags = sorted.map(([src,
|
|
340
|
+
const uniquePreloads = preloads.filter(([, url2]) => seen.has(url2) ? false : (seen.add(url2), true));
|
|
341
|
+
const sorted = [...uniquePreloads.filter(([, url2]) => CSS_PATH.test(url2)), ...uniquePreloads.filter(([, url2]) => !CSS_PATH.test(url2))];
|
|
342
|
+
const preloadTags = sorted.map(([src, url2, chunk2]) => this.#preloadTag(src, url2, chunk2, manifest));
|
|
321
343
|
return new ViteHtml(preloadTags.join("") + stylesheets.join("") + scripts.join(""));
|
|
322
344
|
}
|
|
323
345
|
/** Laravel's @viteReactRefresh: the React Refresh preamble while the dev server runs; nothing otherwise. */
|
|
@@ -402,41 +424,41 @@ var Vite = class {
|
|
|
402
424
|
}
|
|
403
425
|
return imports;
|
|
404
426
|
}
|
|
405
|
-
#tagForChunk(src,
|
|
406
|
-
if (CSS_PATH.test(
|
|
407
|
-
const attributes2 = this.#parseAttributes({ rel: "stylesheet", href:
|
|
427
|
+
#tagForChunk(src, url2, chunk2, manifest) {
|
|
428
|
+
if (CSS_PATH.test(url2)) {
|
|
429
|
+
const attributes2 = this.#parseAttributes({ rel: "stylesheet", href: url2, nonce: this.#nonce ?? false, ...this.#styleAttributes(src, url2, chunk2, manifest) });
|
|
408
430
|
return `<link ${attributes2.join(" ")} />`;
|
|
409
431
|
}
|
|
410
|
-
const attributes = this.#parseAttributes({ type: "module", src:
|
|
432
|
+
const attributes = this.#parseAttributes({ type: "module", src: url2, nonce: this.#nonce ?? false, ...this.#scriptAttributes(src, url2, chunk2, manifest) });
|
|
411
433
|
return `<script ${attributes.join(" ")}></script>`;
|
|
412
434
|
}
|
|
413
|
-
#preloadTag(src,
|
|
414
|
-
const css = CSS_PATH.test(
|
|
435
|
+
#preloadTag(src, url2, chunk2, manifest) {
|
|
436
|
+
const css = CSS_PATH.test(url2);
|
|
415
437
|
let attributes = {
|
|
416
438
|
rel: css ? "preload" : "modulepreload",
|
|
417
439
|
as: css ? "style" : "script",
|
|
418
|
-
href:
|
|
440
|
+
href: url2,
|
|
419
441
|
nonce: this.#nonce ?? false,
|
|
420
|
-
crossorigin: (css ? this.#styleAttributes(src,
|
|
442
|
+
crossorigin: (css ? this.#styleAttributes(src, url2, chunk2, manifest) : this.#scriptAttributes(src, url2, chunk2, manifest)).crossorigin ?? false
|
|
421
443
|
};
|
|
422
444
|
if (this.#integrityKey !== false) attributes.integrity = chunk2?.[this.#integrityKey] ?? false;
|
|
423
445
|
for (const resolver of this.#preloadResolvers) {
|
|
424
|
-
const resolved = resolver(src,
|
|
446
|
+
const resolved = resolver(src, url2, chunk2, manifest);
|
|
425
447
|
if (resolved === false) return "";
|
|
426
448
|
attributes = { ...attributes, ...resolved };
|
|
427
449
|
}
|
|
428
450
|
const { href: _href, ...rest } = attributes;
|
|
429
|
-
this.#preloaded[
|
|
451
|
+
this.#preloaded[url2] = this.#parseAttributes(rest);
|
|
430
452
|
return `<link ${this.#parseAttributes(attributes).join(" ")} />`;
|
|
431
453
|
}
|
|
432
|
-
#scriptAttributes(src,
|
|
454
|
+
#scriptAttributes(src, url2, chunk2, manifest) {
|
|
433
455
|
let attributes = this.#integrityKey !== false ? { integrity: chunk2?.[this.#integrityKey] ?? false } : {};
|
|
434
|
-
for (const resolver of this.#scriptResolvers) attributes = { ...attributes, ...resolver(src,
|
|
456
|
+
for (const resolver of this.#scriptResolvers) attributes = { ...attributes, ...resolver(src, url2, chunk2, manifest) };
|
|
435
457
|
return attributes;
|
|
436
458
|
}
|
|
437
|
-
#styleAttributes(src,
|
|
459
|
+
#styleAttributes(src, url2, chunk2, manifest) {
|
|
438
460
|
let attributes = this.#integrityKey !== false ? { integrity: chunk2?.[this.#integrityKey] ?? false } : {};
|
|
439
|
-
for (const resolver of this.#styleResolvers) attributes = { ...attributes, ...resolver(src,
|
|
461
|
+
for (const resolver of this.#styleResolvers) attributes = { ...attributes, ...resolver(src, url2, chunk2, manifest) };
|
|
440
462
|
return attributes;
|
|
441
463
|
}
|
|
442
464
|
/** false/null/undefined are dropped, true is a bare attribute, anything else name="value". */
|
|
@@ -468,7 +490,7 @@ var Application2 = class _Application extends Container {
|
|
|
468
490
|
this.scoped(Vite, () => new Vite(this));
|
|
469
491
|
}
|
|
470
492
|
base;
|
|
471
|
-
version = "0.1.0-alpha.
|
|
493
|
+
version = "0.1.0-alpha.4";
|
|
472
494
|
providers = [];
|
|
473
495
|
bootingCallbacks = [];
|
|
474
496
|
bootedCallbacks = [];
|
|
@@ -480,144 +502,3090 @@ var Application2 = class _Application extends Container {
|
|
|
480
502
|
static configure(options) {
|
|
481
503
|
return new ApplicationBuilder(new _Application(options.basePath));
|
|
482
504
|
}
|
|
483
|
-
basePath(path = "") {
|
|
484
|
-
return join3(this.base, path);
|
|
505
|
+
basePath(path = "") {
|
|
506
|
+
return join3(this.base, path);
|
|
507
|
+
}
|
|
508
|
+
appPath(path = "") {
|
|
509
|
+
return this.basePath(join3("app", path));
|
|
510
|
+
}
|
|
511
|
+
bootstrapPath(path = "") {
|
|
512
|
+
return this.basePath(join3("bootstrap", path));
|
|
513
|
+
}
|
|
514
|
+
configPath(path = "") {
|
|
515
|
+
return this.basePath(join3("config", path));
|
|
516
|
+
}
|
|
517
|
+
databasePath(path = "") {
|
|
518
|
+
return this.basePath(join3("database", path));
|
|
519
|
+
}
|
|
520
|
+
publicPath(path = "") {
|
|
521
|
+
return this.basePath(join3("public", path));
|
|
522
|
+
}
|
|
523
|
+
resourcePath(path = "") {
|
|
524
|
+
return this.basePath(join3("resources", path));
|
|
525
|
+
}
|
|
526
|
+
storagePath(path = "") {
|
|
527
|
+
return this.basePath(join3("storage", path));
|
|
528
|
+
}
|
|
529
|
+
environment(...environments) {
|
|
530
|
+
const fallback = process.env.APP_ENV ?? "production";
|
|
531
|
+
const current = this.bound(ConfigRepository) ? String(this.make(ConfigRepository).get("app.env", fallback)) : fallback;
|
|
532
|
+
return environments.length === 0 ? current : environments.some((pattern) => Str.is(pattern, current));
|
|
533
|
+
}
|
|
534
|
+
isProduction() {
|
|
535
|
+
return this.environment("production");
|
|
536
|
+
}
|
|
537
|
+
isLocal() {
|
|
538
|
+
return this.environment("local");
|
|
539
|
+
}
|
|
540
|
+
runningUnitTests() {
|
|
541
|
+
return this.environment("testing");
|
|
542
|
+
}
|
|
543
|
+
runningInConsole() {
|
|
544
|
+
return this.inConsole;
|
|
545
|
+
}
|
|
546
|
+
setRunningInConsole(value = true) {
|
|
547
|
+
this.inConsole = value;
|
|
548
|
+
}
|
|
549
|
+
async register(provider) {
|
|
550
|
+
const cls = typeof provider === "function" ? provider : provider.constructor;
|
|
551
|
+
const existing = this.providers.find((p) => p.constructor === cls);
|
|
552
|
+
if (existing) return existing;
|
|
553
|
+
const instance = typeof provider === "function" ? new provider(this) : provider;
|
|
554
|
+
try {
|
|
555
|
+
instance.register?.();
|
|
556
|
+
} catch (error) {
|
|
557
|
+
throw new ProviderRegisterError(instance.constructor.name, error);
|
|
558
|
+
}
|
|
559
|
+
this.providers.push(instance);
|
|
560
|
+
if (this.hasBooted) await this.bootProvider(instance);
|
|
561
|
+
return instance;
|
|
562
|
+
}
|
|
563
|
+
getProviders() {
|
|
564
|
+
return [...this.providers];
|
|
565
|
+
}
|
|
566
|
+
boot() {
|
|
567
|
+
return this.bootPromise ??= this.runBoot();
|
|
568
|
+
}
|
|
569
|
+
isBooted() {
|
|
570
|
+
return this.hasBooted;
|
|
571
|
+
}
|
|
572
|
+
booting(callback) {
|
|
573
|
+
this.bootingCallbacks.push(callback);
|
|
574
|
+
}
|
|
575
|
+
/** If already booted, the callback runs immediately (a rejection surfaces as an unhandled rejection). */
|
|
576
|
+
booted(callback) {
|
|
577
|
+
this.bootedCallbacks.push(callback);
|
|
578
|
+
if (this.hasBooted) void callback(this);
|
|
579
|
+
}
|
|
580
|
+
terminating(callback) {
|
|
581
|
+
this.terminatingCallbacks.push(callback);
|
|
582
|
+
}
|
|
583
|
+
async handle(request2) {
|
|
584
|
+
await this.boot();
|
|
585
|
+
const scope = this.createScope();
|
|
586
|
+
return scope.run(() => scope.make(HttpKernel).handle(request2));
|
|
587
|
+
}
|
|
588
|
+
async terminate() {
|
|
589
|
+
for (const callback of this.terminatingCallbacks) await callback();
|
|
590
|
+
}
|
|
591
|
+
async runBoot() {
|
|
592
|
+
for (const callback of this.bootingCallbacks) await callback(this);
|
|
593
|
+
for (let i = 0; i < this.providers.length; i++) await this.bootProvider(this.providers[i]);
|
|
594
|
+
this.hasBooted = true;
|
|
595
|
+
for (const callback of this.bootedCallbacks) await callback(this);
|
|
596
|
+
}
|
|
597
|
+
async bootProvider(provider) {
|
|
598
|
+
try {
|
|
599
|
+
await provider.boot?.();
|
|
600
|
+
} catch (error) {
|
|
601
|
+
throw new ProviderBootError(provider.constructor.name, error);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
var ClosureKernel = class {
|
|
606
|
+
routes = /* @__PURE__ */ new Map();
|
|
607
|
+
on(method, path, handler) {
|
|
608
|
+
this.routes.set(`${method.toUpperCase()} ${path}`, handler);
|
|
609
|
+
return this;
|
|
610
|
+
}
|
|
611
|
+
async handle(request2) {
|
|
612
|
+
const handler = this.routes.get(`${request2.method} ${new URL(request2.url).pathname}`);
|
|
613
|
+
if (!handler) return new Response("Not Found", { status: 404, headers: { "content-type": "text/plain; charset=utf-8" } });
|
|
614
|
+
return toResponse(await handler(request2));
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
function toResponse(output) {
|
|
618
|
+
if (output instanceof Response) return output;
|
|
619
|
+
if (output === void 0 || output === null) return new Response(null, { status: 204 });
|
|
620
|
+
if (typeof output === "string") return new Response(output, { headers: { "content-type": "text/html; charset=utf-8" } });
|
|
621
|
+
return Response.json(output);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// ../Http/dist/index.js
|
|
625
|
+
import { createReadStream } from "fs";
|
|
626
|
+
import { stat } from "fs/promises";
|
|
627
|
+
import { basename } from "path";
|
|
628
|
+
import { Readable } from "stream";
|
|
629
|
+
import { readFile } from "fs/promises";
|
|
630
|
+
import { fileURLToPath } from "url";
|
|
631
|
+
import { createReadStream as createReadStream2 } from "fs";
|
|
632
|
+
import { stat as stat2 } from "fs/promises";
|
|
633
|
+
import { Readable as Readable2 } from "stream";
|
|
634
|
+
import { relative, resolve, sep } from "path";
|
|
635
|
+
import { createServer } from "http";
|
|
636
|
+
import { Readable as Readable3 } from "stream";
|
|
637
|
+
import { pipeline } from "stream/promises";
|
|
638
|
+
import { existsSync as existsSync4 } from "fs";
|
|
639
|
+
import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
640
|
+
import { dirname } from "path";
|
|
641
|
+
var HttpException = class extends Error {
|
|
642
|
+
constructor(status, message = "", headers = {}) {
|
|
643
|
+
super(message);
|
|
644
|
+
this.status = status;
|
|
645
|
+
this.headers = headers;
|
|
646
|
+
}
|
|
647
|
+
status;
|
|
648
|
+
headers;
|
|
649
|
+
kind = "Http";
|
|
650
|
+
name = "HttpException";
|
|
651
|
+
};
|
|
652
|
+
var HttpResponseException = class extends Error {
|
|
653
|
+
constructor(response2) {
|
|
654
|
+
super(`HTTP response exception (${response2.status}).`);
|
|
655
|
+
this.response = response2;
|
|
656
|
+
this.status = response2.status;
|
|
657
|
+
}
|
|
658
|
+
response;
|
|
659
|
+
kind = "Http";
|
|
660
|
+
name = "HttpResponseException";
|
|
661
|
+
status;
|
|
662
|
+
};
|
|
663
|
+
var HttpServerError = class extends Error {
|
|
664
|
+
kind = "Server";
|
|
665
|
+
status = 500;
|
|
666
|
+
name = "HttpServerError";
|
|
667
|
+
};
|
|
668
|
+
function exceedsJsonDepth(text, maxDepth) {
|
|
669
|
+
let depth = 0;
|
|
670
|
+
let inString = false;
|
|
671
|
+
for (let i = 0; i < text.length; i++) {
|
|
672
|
+
const code = text.charCodeAt(i);
|
|
673
|
+
if (inString) {
|
|
674
|
+
if (code === 92) i++;
|
|
675
|
+
else if (code === 34) inString = false;
|
|
676
|
+
} else if (code === 34) inString = true;
|
|
677
|
+
else if (code === 123 || code === 91) {
|
|
678
|
+
if (++depth > maxDepth) return true;
|
|
679
|
+
} else if (code === 125 || code === 93) depth--;
|
|
680
|
+
}
|
|
681
|
+
return false;
|
|
682
|
+
}
|
|
683
|
+
async function readBody(raw, limit) {
|
|
684
|
+
if (raw.body === null) return new Uint8Array(0);
|
|
685
|
+
const declared = Number(raw.headers.get("content-length") ?? NaN);
|
|
686
|
+
if (limit !== null && Number.isFinite(declared) && declared > limit) {
|
|
687
|
+
await raw.body.cancel();
|
|
688
|
+
throw new HttpException(413, "Content Too Large");
|
|
689
|
+
}
|
|
690
|
+
const reader = raw.body.getReader();
|
|
691
|
+
const chunks = [];
|
|
692
|
+
let size = 0;
|
|
693
|
+
for (; ; ) {
|
|
694
|
+
const { done, value } = await reader.read();
|
|
695
|
+
if (done) break;
|
|
696
|
+
size += value.byteLength;
|
|
697
|
+
if (limit !== null && size > limit) {
|
|
698
|
+
await reader.cancel();
|
|
699
|
+
throw new HttpException(413, "Content Too Large");
|
|
700
|
+
}
|
|
701
|
+
chunks.push(value);
|
|
702
|
+
}
|
|
703
|
+
const out = new Uint8Array(size);
|
|
704
|
+
let offset = 0;
|
|
705
|
+
for (const chunk2 of chunks) {
|
|
706
|
+
out.set(chunk2, offset);
|
|
707
|
+
offset += chunk2.byteLength;
|
|
708
|
+
}
|
|
709
|
+
return out;
|
|
710
|
+
}
|
|
711
|
+
function requestScope() {
|
|
712
|
+
const container = currentContainer();
|
|
713
|
+
return container !== void 0 && container.bound(HttpRequest) ? container : void 0;
|
|
714
|
+
}
|
|
715
|
+
function currentRequest() {
|
|
716
|
+
return requestScope()?.make(HttpRequest);
|
|
717
|
+
}
|
|
718
|
+
function optionalSession() {
|
|
719
|
+
const scope = requestScope();
|
|
720
|
+
return scope?.bound(Session) ? scope.make(Session) : void 0;
|
|
721
|
+
}
|
|
722
|
+
function optionalCookieJar() {
|
|
723
|
+
const scope = requestScope();
|
|
724
|
+
return scope?.bound(CookieJar) ? scope.make(CookieJar) : void 0;
|
|
725
|
+
}
|
|
726
|
+
function serializeCookie(cookie) {
|
|
727
|
+
const parts = [`${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value)}`, `Path=${cookie.path}`];
|
|
728
|
+
if (cookie.domain !== void 0) parts.push(`Domain=${cookie.domain}`);
|
|
729
|
+
if (cookie.secure) parts.push("Secure");
|
|
730
|
+
if (cookie.httpOnly) parts.push("HttpOnly");
|
|
731
|
+
parts.push(`SameSite=${cookie.sameSite.charAt(0).toUpperCase()}${cookie.sameSite.slice(1)}`);
|
|
732
|
+
if (cookie.minutes < 0) parts.push("Max-Age=0", "Expires=Thu, 01 Jan 1970 00:00:00 GMT");
|
|
733
|
+
else if (cookie.minutes > 0) parts.push(`Max-Age=${Math.round(cookie.minutes * 60)}`);
|
|
734
|
+
return parts.join("; ");
|
|
735
|
+
}
|
|
736
|
+
var HttpRedirectResponse = class {
|
|
737
|
+
constructor(targetUrl, status = 302, headers) {
|
|
738
|
+
this.targetUrl = targetUrl;
|
|
739
|
+
this.status = status;
|
|
740
|
+
this.#headers = new Headers(headers);
|
|
741
|
+
}
|
|
742
|
+
targetUrl;
|
|
743
|
+
status;
|
|
744
|
+
#headers;
|
|
745
|
+
with(key, value) {
|
|
746
|
+
const session = optionalSession();
|
|
747
|
+
if (session === void 0) return this;
|
|
748
|
+
if (typeof key === "string") session.flash(key, value);
|
|
749
|
+
else for (const [name, item] of Object.entries(key)) session.flash(name, item);
|
|
750
|
+
return this;
|
|
751
|
+
}
|
|
752
|
+
/** Flashes `errors` as { [bag]: { field: string[] } }, keeping the other bags already flashed. */
|
|
753
|
+
withErrors(errors, bag = "default") {
|
|
754
|
+
const session = optionalSession();
|
|
755
|
+
if (session === void 0) return this;
|
|
756
|
+
const source = typeof errors.toObject === "function" ? errors.toObject() : errors;
|
|
757
|
+
const normalized = Object.fromEntries(
|
|
758
|
+
Object.entries(source).map(([field, messages]) => [field, Array.isArray(messages) ? [...messages] : [messages]])
|
|
759
|
+
);
|
|
760
|
+
const existing = session.get("errors", {});
|
|
761
|
+
session.flash("errors", { ...existing, [bag]: normalized });
|
|
762
|
+
return this;
|
|
763
|
+
}
|
|
764
|
+
/** Flashes `input`, or the current request's input (files are never part of input()). */
|
|
765
|
+
withInput(input) {
|
|
766
|
+
const session = optionalSession();
|
|
767
|
+
if (session === void 0) return this;
|
|
768
|
+
session.flashInput(input ?? currentRequest()?.input() ?? {});
|
|
769
|
+
return this;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Always a Set-Cookie header on this response (Laravel's RedirectResponse::withCookie). A bound CookieJar only
|
|
773
|
+
* supplies the defaults (path, domain, secure, same-site); queueing would lose the cookie outside the web group.
|
|
774
|
+
*/
|
|
775
|
+
withCookie(name, value, minutes = 0) {
|
|
776
|
+
const jar = optionalCookieJar();
|
|
777
|
+
const cookie = jar !== void 0 ? jar.make(name, value, minutes) : { name, value, minutes, path: "/", secure: false, httpOnly: true, sameSite: "lax" };
|
|
778
|
+
this.#headers.append("Set-Cookie", serializeCookie(cookie));
|
|
779
|
+
return this;
|
|
780
|
+
}
|
|
781
|
+
header(name, value) {
|
|
782
|
+
this.#headers.set(name, value);
|
|
783
|
+
return this;
|
|
784
|
+
}
|
|
785
|
+
toResponse() {
|
|
786
|
+
const headers = new Headers(this.#headers);
|
|
787
|
+
headers.set("Location", this.targetUrl);
|
|
788
|
+
return new Response(null, { status: this.status, headers });
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
function pathOf(url2) {
|
|
792
|
+
let parsed;
|
|
793
|
+
try {
|
|
794
|
+
parsed = new URL(url2, "http://localhost");
|
|
795
|
+
} catch {
|
|
796
|
+
return "/";
|
|
797
|
+
}
|
|
798
|
+
return `${parsed.pathname.replace(/^\/+/, "/")}${parsed.search}${parsed.hash}`;
|
|
799
|
+
}
|
|
800
|
+
var HttpRedirector = class {
|
|
801
|
+
to(path, status = 302, headers) {
|
|
802
|
+
return new HttpRedirectResponse(inject(UrlGenerator).to(path), status, headers);
|
|
803
|
+
}
|
|
804
|
+
route(name, parameters, status = 302) {
|
|
805
|
+
return new HttpRedirectResponse(inject(UrlGenerator).route(name, parameters), status);
|
|
806
|
+
}
|
|
807
|
+
back(fallback, status = 302) {
|
|
808
|
+
return new HttpRedirectResponse(inject(UrlGenerator).previous(fallback), status);
|
|
809
|
+
}
|
|
810
|
+
/** Pulls url.intended from the session (put there by guest()), else goes to `fallback`. */
|
|
811
|
+
intended(fallback = "/", status = 302) {
|
|
812
|
+
const path = optionalSession()?.pull("url.intended") ?? fallback;
|
|
813
|
+
return this.to(path, status);
|
|
814
|
+
}
|
|
815
|
+
away(url2, status = 302) {
|
|
816
|
+
return new HttpRedirectResponse(url2, status);
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Remembers where a guest was going (a GET page, else the previous URL), then redirects to `path`. Only the path
|
|
820
|
+
* is stored, so intended() goes back on whichever host serves the app, and never to another site's Referer.
|
|
821
|
+
*/
|
|
822
|
+
guest(path, status = 302) {
|
|
823
|
+
const session = optionalSession();
|
|
824
|
+
const request2 = currentRequest();
|
|
825
|
+
if (session !== void 0) {
|
|
826
|
+
const intended = request2 !== void 0 && request2.method() === "GET" && !request2.expectsJson() ? request2.fullUrl() : inject(UrlGenerator).previous();
|
|
827
|
+
session.put("url.intended", pathOf(intended));
|
|
828
|
+
}
|
|
829
|
+
return this.to(path, status);
|
|
830
|
+
}
|
|
831
|
+
setIntendedUrl(url2) {
|
|
832
|
+
optionalSession()?.put("url.intended", url2);
|
|
833
|
+
}
|
|
834
|
+
refresh(status = 302) {
|
|
835
|
+
return this.to(currentRequest()?.fullUrl() ?? inject(UrlGenerator).current(), status);
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
var KEY = /* @__PURE__ */ Symbol.for("oriel:http.client-address");
|
|
839
|
+
var store = globalThis[KEY] ??= /* @__PURE__ */ new WeakMap();
|
|
840
|
+
function setClientAddress(request2, address) {
|
|
841
|
+
if (address !== void 0 && address !== "") store.set(request2, address);
|
|
842
|
+
}
|
|
843
|
+
function clientAddress(request2) {
|
|
844
|
+
return store.get(request2);
|
|
845
|
+
}
|
|
846
|
+
var TYPES = {
|
|
847
|
+
avif: "image/avif",
|
|
848
|
+
css: "text/css",
|
|
849
|
+
csv: "text/csv",
|
|
850
|
+
doc: "application/msword",
|
|
851
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
852
|
+
gif: "image/gif",
|
|
853
|
+
gz: "application/gzip",
|
|
854
|
+
html: "text/html",
|
|
855
|
+
ico: "image/vnd.microsoft.icon",
|
|
856
|
+
jpg: "image/jpeg",
|
|
857
|
+
js: "text/javascript",
|
|
858
|
+
map: "application/json",
|
|
859
|
+
json: "application/json",
|
|
860
|
+
md: "text/markdown",
|
|
861
|
+
mp3: "audio/mpeg",
|
|
862
|
+
mp4: "video/mp4",
|
|
863
|
+
otf: "font/otf",
|
|
864
|
+
pdf: "application/pdf",
|
|
865
|
+
png: "image/png",
|
|
866
|
+
svg: "image/svg+xml",
|
|
867
|
+
ttf: "font/ttf",
|
|
868
|
+
txt: "text/plain",
|
|
869
|
+
wasm: "application/wasm",
|
|
870
|
+
webm: "video/webm",
|
|
871
|
+
webp: "image/webp",
|
|
872
|
+
woff: "font/woff",
|
|
873
|
+
woff2: "font/woff2",
|
|
874
|
+
xls: "application/vnd.ms-excel",
|
|
875
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
876
|
+
xml: "application/xml",
|
|
877
|
+
zip: "application/zip"
|
|
878
|
+
};
|
|
879
|
+
var ALIASES = { jpeg: "jpg", htm: "html", mjs: "js", text: "txt" };
|
|
880
|
+
var EXTENSIONS = Object.fromEntries(Object.entries(TYPES).map(([extension, type]) => [type, extension]));
|
|
881
|
+
function mimeFromExtension(pathOrExtension) {
|
|
882
|
+
const extension = pathOrExtension.slice(pathOrExtension.lastIndexOf(".") + 1).toLowerCase();
|
|
883
|
+
return TYPES[ALIASES[extension] ?? extension] ?? "application/octet-stream";
|
|
884
|
+
}
|
|
885
|
+
function extensionFromMime(type) {
|
|
886
|
+
return EXTENSIONS[type.split(";")[0].trim().toLowerCase()];
|
|
887
|
+
}
|
|
888
|
+
var FormUploadedFile = class {
|
|
889
|
+
constructor(file) {
|
|
890
|
+
this.file = file;
|
|
891
|
+
this.clientName = file.name ?? "";
|
|
892
|
+
this.mimeType = file.type === "" ? "application/octet-stream" : file.type;
|
|
893
|
+
this.size = file.size;
|
|
894
|
+
}
|
|
895
|
+
file;
|
|
896
|
+
clientName;
|
|
897
|
+
mimeType;
|
|
898
|
+
size;
|
|
899
|
+
/** Guessed from the MIME type, falling back to the client's extension (Laravel's extension()). */
|
|
900
|
+
extension() {
|
|
901
|
+
return extensionFromMime(this.mimeType) ?? this.clientExtension();
|
|
902
|
+
}
|
|
903
|
+
clientExtension() {
|
|
904
|
+
const dot = this.clientName.lastIndexOf(".");
|
|
905
|
+
return dot === -1 ? "" : this.clientName.slice(dot + 1).toLowerCase();
|
|
906
|
+
}
|
|
907
|
+
/** A random 40-character name with the guessed extension. */
|
|
908
|
+
hashName() {
|
|
909
|
+
const extension = this.extension();
|
|
910
|
+
return extension === "" ? Str.random(40) : `${Str.random(40)}.${extension}`;
|
|
911
|
+
}
|
|
912
|
+
arrayBuffer() {
|
|
913
|
+
return this.file.arrayBuffer();
|
|
914
|
+
}
|
|
915
|
+
text() {
|
|
916
|
+
return this.file.text();
|
|
917
|
+
}
|
|
918
|
+
stream() {
|
|
919
|
+
return this.file.stream();
|
|
920
|
+
}
|
|
921
|
+
store(directory, disk) {
|
|
922
|
+
return inject(FilesystemManager).disk(disk).putFile(directory, this);
|
|
923
|
+
}
|
|
924
|
+
storeAs(directory, name, disk) {
|
|
925
|
+
return inject(FilesystemManager).disk(disk).putFileAs(directory, this, name);
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
var FORBIDDEN = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
929
|
+
var INTEGER_KEY = /^(?:0|[1-9]\d*)$/;
|
|
930
|
+
var isPlainObject = (value) => {
|
|
931
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
932
|
+
const prototype = Object.getPrototypeOf(value);
|
|
933
|
+
return prototype === Object.prototype || prototype === null;
|
|
934
|
+
};
|
|
935
|
+
function parseBracketKey(name) {
|
|
936
|
+
const open = name.indexOf("[");
|
|
937
|
+
if (open <= 0 || !name.endsWith("]")) return [name];
|
|
938
|
+
const segments = [name.slice(0, open)];
|
|
939
|
+
const rest = name.slice(open);
|
|
940
|
+
const matches2 = rest.match(/\[[^[\]]*\]/g);
|
|
941
|
+
if (matches2 === null || matches2.join("") !== rest) return [name];
|
|
942
|
+
for (const match of matches2) segments.push(match.slice(1, -1));
|
|
943
|
+
return segments;
|
|
944
|
+
}
|
|
945
|
+
function normalizeLists(value) {
|
|
946
|
+
if (Array.isArray(value)) return value.map(normalizeLists);
|
|
947
|
+
if (!isPlainObject(value)) return value;
|
|
948
|
+
const keys = Object.keys(value);
|
|
949
|
+
const out = {};
|
|
950
|
+
for (const key of keys) out[key] = normalizeLists(value[key]);
|
|
951
|
+
if (keys.length > 0 && keys.every((key, index) => key === String(index))) return keys.map((key) => out[key]);
|
|
952
|
+
return out;
|
|
953
|
+
}
|
|
954
|
+
function fromEntries(entries) {
|
|
955
|
+
const root = {};
|
|
956
|
+
const nextIndex = /* @__PURE__ */ new WeakMap();
|
|
957
|
+
for (const [name, value] of entries) {
|
|
958
|
+
const segments = parseBracketKey(name);
|
|
959
|
+
if (segments.some((segment) => FORBIDDEN.has(segment))) continue;
|
|
960
|
+
let node = root;
|
|
961
|
+
for (let i = 0; i < segments.length; i++) {
|
|
962
|
+
let segment = segments[i];
|
|
963
|
+
const next = nextIndex.get(node) ?? 0;
|
|
964
|
+
if (segment === "") {
|
|
965
|
+
segment = String(next);
|
|
966
|
+
nextIndex.set(node, next + 1);
|
|
967
|
+
} else if (INTEGER_KEY.test(segment) && Number(segment) >= next) {
|
|
968
|
+
nextIndex.set(node, Number(segment) + 1);
|
|
969
|
+
}
|
|
970
|
+
if (i === segments.length - 1) {
|
|
971
|
+
node[segment] = value;
|
|
972
|
+
break;
|
|
973
|
+
}
|
|
974
|
+
if (!isPlainObject(node[segment])) node[segment] = {};
|
|
975
|
+
node = node[segment];
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
return normalizeLists(root);
|
|
979
|
+
}
|
|
980
|
+
function sanitize(value) {
|
|
981
|
+
if (Array.isArray(value)) return value.map(sanitize);
|
|
982
|
+
if (!isPlainObject(value)) return value;
|
|
983
|
+
const out = {};
|
|
984
|
+
for (const [key, item] of Object.entries(value)) if (!FORBIDDEN.has(key)) out[key] = sanitize(item);
|
|
985
|
+
return out;
|
|
986
|
+
}
|
|
987
|
+
function mergeRecursive(base, override) {
|
|
988
|
+
const out = { ...base };
|
|
989
|
+
for (const [key, value] of Object.entries(override)) {
|
|
990
|
+
if (FORBIDDEN.has(key)) continue;
|
|
991
|
+
const current = out[key];
|
|
992
|
+
out[key] = (isPlainObject(current) || Array.isArray(current)) && (isPlainObject(value) || Array.isArray(value)) ? normalizeLists(mergeRecursive({ ...current }, { ...value })) : value;
|
|
993
|
+
}
|
|
994
|
+
return out;
|
|
995
|
+
}
|
|
996
|
+
function isBlank(value) {
|
|
997
|
+
if (value === null || value === void 0) return true;
|
|
998
|
+
if (typeof value === "string") return value.trim() === "";
|
|
999
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
1000
|
+
if (isPlainObject(value)) return Object.keys(value).length === 0;
|
|
1001
|
+
return false;
|
|
1002
|
+
}
|
|
1003
|
+
function transformLeaves(tree, transform, prefix = "") {
|
|
1004
|
+
const out = {};
|
|
1005
|
+
for (const [key, value] of Object.entries(tree)) out[key] = transformValue(`${prefix}${key}`, value, transform);
|
|
1006
|
+
return out;
|
|
1007
|
+
}
|
|
1008
|
+
function transformValue(path, value, transform) {
|
|
1009
|
+
if (Array.isArray(value)) return value.map((item, index) => transformValue(`${path}.${index}`, item, transform));
|
|
1010
|
+
if (isPlainObject(value)) return transformLeaves(value, transform, `${path}.`);
|
|
1011
|
+
return transform(path, value);
|
|
1012
|
+
}
|
|
1013
|
+
var TRUE_VALUES = /* @__PURE__ */ new Set(["1", "true", "on", "yes"]);
|
|
1014
|
+
var normalizeIp = (ip) => ip.trim().replace(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i, "$1");
|
|
1015
|
+
function ipv4ToNumber(ip) {
|
|
1016
|
+
const parts = ip.split(".");
|
|
1017
|
+
if (parts.length !== 4 || parts.some((part) => !/^\d{1,3}$/.test(part) || Number(part) > 255)) return void 0;
|
|
1018
|
+
return parts.reduce((total, part) => total * 256 + Number(part), 0);
|
|
1019
|
+
}
|
|
1020
|
+
function ipMatches(ip, patterns) {
|
|
1021
|
+
const address = normalizeIp(ip);
|
|
1022
|
+
return patterns.some((pattern) => {
|
|
1023
|
+
if (pattern === "*") return true;
|
|
1024
|
+
if (!pattern.includes("/")) return normalizeIp(pattern) === address;
|
|
1025
|
+
const [range, bits] = pattern.split("/");
|
|
1026
|
+
const base = ipv4ToNumber(range);
|
|
1027
|
+
const value = ipv4ToNumber(address);
|
|
1028
|
+
const size = Number(bits);
|
|
1029
|
+
if (base === void 0 || value === void 0 || !(size >= 0 && size <= 32)) return false;
|
|
1030
|
+
const mask = size === 0 ? 0 : ~0 << 32 - size >>> 0;
|
|
1031
|
+
return (base & mask) >>> 0 === (value & mask) >>> 0;
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
var IncomingRequest = class {
|
|
1035
|
+
constructor(raw, options = {}) {
|
|
1036
|
+
this.raw = raw;
|
|
1037
|
+
this.#url = new URL(raw.url);
|
|
1038
|
+
this.#trustedProxies = options.trustedProxies ?? [];
|
|
1039
|
+
this.#maxBodySize = options.maxBodySize === void 0 ? 8 * 1024 * 1024 : options.maxBodySize;
|
|
1040
|
+
this.#maxJsonDepth = options.maxJsonDepth === void 0 ? 512 : options.maxJsonDepth;
|
|
1041
|
+
this.#query = fromEntries(this.#url.searchParams.entries());
|
|
1042
|
+
this.#method = raw.method.toUpperCase();
|
|
1043
|
+
}
|
|
1044
|
+
raw;
|
|
1045
|
+
#url;
|
|
1046
|
+
#trustedProxies;
|
|
1047
|
+
#maxBodySize;
|
|
1048
|
+
#maxJsonDepth;
|
|
1049
|
+
#query;
|
|
1050
|
+
#body = {};
|
|
1051
|
+
#files = {};
|
|
1052
|
+
#content = "";
|
|
1053
|
+
#method;
|
|
1054
|
+
#cookies;
|
|
1055
|
+
#loading;
|
|
1056
|
+
#userResolver;
|
|
1057
|
+
/** Reads and parses the body (JSON, urlencoded or multipart) once, then applies method spoofing. */
|
|
1058
|
+
load() {
|
|
1059
|
+
return this.#loading ??= this.#load();
|
|
1060
|
+
}
|
|
1061
|
+
async #load() {
|
|
1062
|
+
const method = this.raw.method.toUpperCase();
|
|
1063
|
+
if (method !== "GET" && method !== "HEAD" && this.raw.body !== null) {
|
|
1064
|
+
const type = (this.raw.headers.get("content-type") ?? "").toLowerCase();
|
|
1065
|
+
const isJson = type.includes("/json") || type.includes("+json");
|
|
1066
|
+
const isUrlencoded = type.startsWith("application/x-www-form-urlencoded");
|
|
1067
|
+
const isMultipart = type.startsWith("multipart/form-data");
|
|
1068
|
+
if (isJson || isUrlencoded || isMultipart) {
|
|
1069
|
+
const bytes = await readBody(this.raw, this.#maxBodySize);
|
|
1070
|
+
if (isJson && this.#maxJsonDepth !== null && exceedsJsonDepth(new TextDecoder().decode(bytes), this.#maxJsonDepth)) {
|
|
1071
|
+
throw new HttpException(400, "The request body is nested too deeply.");
|
|
1072
|
+
}
|
|
1073
|
+
try {
|
|
1074
|
+
if (isJson) {
|
|
1075
|
+
this.#content = new TextDecoder().decode(bytes);
|
|
1076
|
+
this.#body = this.#parseJson(this.#content);
|
|
1077
|
+
} else if (isUrlencoded) {
|
|
1078
|
+
this.#content = new TextDecoder().decode(bytes);
|
|
1079
|
+
this.#body = fromEntries(new URLSearchParams(this.#content).entries());
|
|
1080
|
+
} else {
|
|
1081
|
+
this.#parseForm(await new Response(bytes, { headers: { "content-type": this.raw.headers.get("content-type") ?? "" } }).formData());
|
|
1082
|
+
}
|
|
1083
|
+
} catch {
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
if (method === "POST") {
|
|
1088
|
+
const override = this.raw.headers.get("x-http-method-override") ?? this.#string(this.#body._method) ?? this.#string(this.#query._method);
|
|
1089
|
+
const spoofed = override?.toUpperCase();
|
|
1090
|
+
if (spoofed !== void 0 && /^[A-Z]+$/.test(spoofed) && !["GET", "HEAD", "CONNECT", "TRACE"].includes(spoofed)) {
|
|
1091
|
+
this.#method = spoofed;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return this;
|
|
1095
|
+
}
|
|
1096
|
+
#parseJson(text) {
|
|
1097
|
+
let parsed;
|
|
1098
|
+
try {
|
|
1099
|
+
parsed = JSON.parse(text);
|
|
1100
|
+
} catch {
|
|
1101
|
+
return {};
|
|
1102
|
+
}
|
|
1103
|
+
if (Array.isArray(parsed)) return sanitize({ ...parsed });
|
|
1104
|
+
return isPlainObject(parsed) ? sanitize(parsed) : {};
|
|
1105
|
+
}
|
|
1106
|
+
#parseForm(form) {
|
|
1107
|
+
const fields = [];
|
|
1108
|
+
const files = [];
|
|
1109
|
+
for (const [name, value] of form.entries()) {
|
|
1110
|
+
if (typeof value === "string") fields.push([name, value]);
|
|
1111
|
+
else if (!((value.name ?? "") === "" && value.size === 0)) files.push([name, new FormUploadedFile(value)]);
|
|
1112
|
+
}
|
|
1113
|
+
this.#body = fromEntries(fields);
|
|
1114
|
+
this.#files = fromEntries(files);
|
|
1115
|
+
}
|
|
1116
|
+
#string(value) {
|
|
1117
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
1118
|
+
}
|
|
1119
|
+
method() {
|
|
1120
|
+
return this.#method;
|
|
1121
|
+
}
|
|
1122
|
+
isMethod(method) {
|
|
1123
|
+
return this.#method === method.toUpperCase();
|
|
1124
|
+
}
|
|
1125
|
+
/** The scheme and host, honouring X-Forwarded-Proto/Host from trusted proxies. */
|
|
1126
|
+
root() {
|
|
1127
|
+
const trusted = this.#fromTrustedProxy();
|
|
1128
|
+
const proto = trusted ? this.raw.headers.get("x-forwarded-proto")?.split(",")[0]?.trim() : void 0;
|
|
1129
|
+
const host = trusted ? this.raw.headers.get("x-forwarded-host")?.split(",")[0]?.trim() : void 0;
|
|
1130
|
+
const scheme = proto === "https" || proto === "http" ? proto : this.#url.protocol.slice(0, -1);
|
|
1131
|
+
return `${scheme}://${host !== void 0 && host !== "" ? host : this.#url.host}`;
|
|
1132
|
+
}
|
|
1133
|
+
/** The URL without the query string or a trailing slash. */
|
|
1134
|
+
url() {
|
|
1135
|
+
return `${this.root()}${this.#url.pathname}`.replace(/\/+$/, "");
|
|
1136
|
+
}
|
|
1137
|
+
fullUrl() {
|
|
1138
|
+
return this.#url.search === "" ? this.url() : `${this.url()}${this.#url.search}`;
|
|
1139
|
+
}
|
|
1140
|
+
/** 'posts/1', or '/' for the root. */
|
|
1141
|
+
path() {
|
|
1142
|
+
const trimmed = this.#url.pathname.replace(/^\/+|\/+$/g, "");
|
|
1143
|
+
return trimmed === "" ? "/" : trimmed;
|
|
1144
|
+
}
|
|
1145
|
+
decodedPath() {
|
|
1146
|
+
try {
|
|
1147
|
+
return decodeURIComponent(this.path());
|
|
1148
|
+
} catch {
|
|
1149
|
+
return this.path();
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
secure() {
|
|
1153
|
+
return this.root().startsWith("https:");
|
|
1154
|
+
}
|
|
1155
|
+
/** request.is('admin/*'): wildcard match against the decoded path. */
|
|
1156
|
+
is(...patterns) {
|
|
1157
|
+
const path = this.decodedPath();
|
|
1158
|
+
return patterns.some((pattern) => {
|
|
1159
|
+
const trimmed = pattern.replace(/^\/+|\/+$/g, "");
|
|
1160
|
+
return Str.is(trimmed === "" ? "/" : trimmed, path);
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
routeIs(...names) {
|
|
1164
|
+
const name = this.#currentRoute()?.route.getName();
|
|
1165
|
+
return name !== void 0 && names.some((pattern) => Str.is(pattern, name));
|
|
1166
|
+
}
|
|
1167
|
+
header(name, defaultValue) {
|
|
1168
|
+
return this.raw.headers.get(name) ?? defaultValue;
|
|
1169
|
+
}
|
|
1170
|
+
bearerToken() {
|
|
1171
|
+
const header = this.header("authorization", "");
|
|
1172
|
+
const position = header.toLowerCase().lastIndexOf("bearer ");
|
|
1173
|
+
if (position === -1) return void 0;
|
|
1174
|
+
const token2 = header.slice(position + 7).split(",")[0].trim();
|
|
1175
|
+
return token2 === "" ? void 0 : token2;
|
|
1176
|
+
}
|
|
1177
|
+
userAgent() {
|
|
1178
|
+
return this.header("user-agent");
|
|
1179
|
+
}
|
|
1180
|
+
/** The socket address, or the right-most untrusted X-Forwarded-For entry when the socket is a trusted proxy. */
|
|
1181
|
+
ip() {
|
|
1182
|
+
const socket = clientAddress(this.raw);
|
|
1183
|
+
if (socket === void 0) return void 0;
|
|
1184
|
+
const address = normalizeIp(socket);
|
|
1185
|
+
if (!this.#fromTrustedProxy()) return address;
|
|
1186
|
+
const chain = (this.raw.headers.get("x-forwarded-for") ?? "").split(",").map((entry) => normalizeIp(entry)).filter((entry) => entry !== "");
|
|
1187
|
+
for (let i = chain.length - 1; i >= 0; i--) if (!ipMatches(chain[i], this.#trustedProxies)) return chain[i];
|
|
1188
|
+
return chain[0] ?? address;
|
|
1189
|
+
}
|
|
1190
|
+
#fromTrustedProxy() {
|
|
1191
|
+
const socket = clientAddress(this.raw);
|
|
1192
|
+
return socket !== void 0 && this.#trustedProxies.length > 0 && ipMatches(socket, this.#trustedProxies);
|
|
1193
|
+
}
|
|
1194
|
+
ajax() {
|
|
1195
|
+
return this.header("x-requested-with") === "XMLHttpRequest";
|
|
1196
|
+
}
|
|
1197
|
+
pjax() {
|
|
1198
|
+
return this.header("x-pjax") === "true";
|
|
1199
|
+
}
|
|
1200
|
+
/** The first Accept type is JSON. */
|
|
1201
|
+
wantsJson() {
|
|
1202
|
+
const first = this.header("accept", "").split(",")[0].trim();
|
|
1203
|
+
return first.includes("/json") || first.includes("+json");
|
|
1204
|
+
}
|
|
1205
|
+
/** Laravel: (ajax and not pjax and accepts anything) or wantsJson. */
|
|
1206
|
+
expectsJson() {
|
|
1207
|
+
const first = this.header("accept", "").split(",")[0].split(";")[0].trim();
|
|
1208
|
+
const acceptsAny = first === "" || first === "*/*" || first === "*";
|
|
1209
|
+
return this.ajax() && !this.pjax() && acceptsAny || this.wantsJson();
|
|
1210
|
+
}
|
|
1211
|
+
/** The raw body text for JSON and urlencoded requests ('' otherwise; the raw stream stays unread for other types). */
|
|
1212
|
+
content() {
|
|
1213
|
+
return this.#content;
|
|
1214
|
+
}
|
|
1215
|
+
query(key, defaultValue) {
|
|
1216
|
+
return Arr.get(this.#query, key, defaultValue);
|
|
1217
|
+
}
|
|
1218
|
+
/** Body wins over the query string (PHP's $body + $query). Files are not input. */
|
|
1219
|
+
input(key, defaultValue) {
|
|
1220
|
+
return Arr.get({ ...this.#query, ...this.#body }, key, defaultValue);
|
|
1221
|
+
}
|
|
1222
|
+
/** Input plus files (array_replace_recursive). */
|
|
1223
|
+
all() {
|
|
1224
|
+
return mergeRecursive({ ...this.#query, ...this.#body }, this.#files);
|
|
1225
|
+
}
|
|
1226
|
+
only(...keys) {
|
|
1227
|
+
const all = this.all();
|
|
1228
|
+
const out = {};
|
|
1229
|
+
for (const key of keys.flat()) if (Arr.has(all, key)) Arr.set(out, key, Arr.get(all, key));
|
|
1230
|
+
return out;
|
|
1231
|
+
}
|
|
1232
|
+
except(...keys) {
|
|
1233
|
+
const all = structuredCloneInput(this.all());
|
|
1234
|
+
for (const key of keys.flat()) Arr.forget(all, key);
|
|
1235
|
+
return all;
|
|
1236
|
+
}
|
|
1237
|
+
has(...keys) {
|
|
1238
|
+
const all = this.all();
|
|
1239
|
+
return keys.flat().every((key) => Arr.has(all, key));
|
|
1240
|
+
}
|
|
1241
|
+
hasAny(...keys) {
|
|
1242
|
+
const all = this.all();
|
|
1243
|
+
return keys.flat().some((key) => Arr.has(all, key));
|
|
1244
|
+
}
|
|
1245
|
+
missing(...keys) {
|
|
1246
|
+
return !this.has(...keys);
|
|
1247
|
+
}
|
|
1248
|
+
filled(key) {
|
|
1249
|
+
return !isBlank(Arr.get(this.all(), key));
|
|
1250
|
+
}
|
|
1251
|
+
string(key, defaultValue = "") {
|
|
1252
|
+
const value = this.input(key);
|
|
1253
|
+
return value === void 0 || value === null ? defaultValue : String(value);
|
|
1254
|
+
}
|
|
1255
|
+
/** PHP's FILTER_VALIDATE_BOOLEAN: '1', 'true', 'on', 'yes' (any case) and true are true. */
|
|
1256
|
+
boolean(key, defaultValue = false) {
|
|
1257
|
+
const value = this.input(key);
|
|
1258
|
+
if (value === void 0) return defaultValue;
|
|
1259
|
+
if (typeof value === "boolean") return value;
|
|
1260
|
+
if (typeof value === "number") return value === 1;
|
|
1261
|
+
return typeof value === "string" && TRUE_VALUES.has(value.trim().toLowerCase());
|
|
1262
|
+
}
|
|
1263
|
+
/** PHP's intval: leading digits, else 0. */
|
|
1264
|
+
integer(key, defaultValue = 0) {
|
|
1265
|
+
const value = this.input(key);
|
|
1266
|
+
if (value === void 0) return defaultValue;
|
|
1267
|
+
if (typeof value === "number") return Number.isFinite(value) ? Math.trunc(value) : 0;
|
|
1268
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
|
1269
|
+
const parsed = Number.parseInt(String(value).trim(), 10);
|
|
1270
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
1271
|
+
}
|
|
1272
|
+
/** Adds or replaces top-level body values. */
|
|
1273
|
+
merge(values) {
|
|
1274
|
+
this.#body = { ...this.#body, ...sanitize(values) };
|
|
1275
|
+
return this;
|
|
1276
|
+
}
|
|
1277
|
+
/** Rewrites every scalar in the query and the body (TrimStrings and ConvertEmptyStringsToNull use this). */
|
|
1278
|
+
transformInput(transform) {
|
|
1279
|
+
this.#query = transformLeaves(this.#query, transform);
|
|
1280
|
+
this.#body = transformLeaves(this.#body, transform);
|
|
1281
|
+
return this;
|
|
1282
|
+
}
|
|
1283
|
+
file(key) {
|
|
1284
|
+
const value = Arr.get(this.#files, key);
|
|
1285
|
+
return value instanceof FormUploadedFile ? value : void 0;
|
|
1286
|
+
}
|
|
1287
|
+
hasFile(key) {
|
|
1288
|
+
const value = Arr.get(this.#files, key);
|
|
1289
|
+
return value instanceof FormUploadedFile || Array.isArray(value) && value.some((item) => item instanceof FormUploadedFile);
|
|
1290
|
+
}
|
|
1291
|
+
allFiles() {
|
|
1292
|
+
return { ...this.#files };
|
|
1293
|
+
}
|
|
1294
|
+
cookie(name) {
|
|
1295
|
+
return this.cookies()[name];
|
|
1296
|
+
}
|
|
1297
|
+
cookies() {
|
|
1298
|
+
if (this.#cookies === void 0) {
|
|
1299
|
+
const cookies = {};
|
|
1300
|
+
for (const part of (this.raw.headers.get("cookie") ?? "").split(";")) {
|
|
1301
|
+
const index = part.indexOf("=");
|
|
1302
|
+
if (index === -1) continue;
|
|
1303
|
+
const name = part.slice(0, index).trim();
|
|
1304
|
+
if (name === "" || Object.hasOwn(cookies, name)) continue;
|
|
1305
|
+
let value = part.slice(index + 1).trim();
|
|
1306
|
+
if (value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
|
|
1307
|
+
try {
|
|
1308
|
+
cookies[name] = decodeURIComponent(value);
|
|
1309
|
+
} catch {
|
|
1310
|
+
cookies[name] = value;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
this.#cookies = cookies;
|
|
1314
|
+
}
|
|
1315
|
+
return { ...this.#cookies };
|
|
1316
|
+
}
|
|
1317
|
+
/** Replaces the parsed cookies (EncryptCookies calls this after decrypting). */
|
|
1318
|
+
setCookies(cookies) {
|
|
1319
|
+
this.#cookies = { ...cookies };
|
|
1320
|
+
}
|
|
1321
|
+
/** One route parameter (after model binding), or all of them. */
|
|
1322
|
+
route(key) {
|
|
1323
|
+
const parameters = this.#currentRoute()?.parameters ?? {};
|
|
1324
|
+
return key === void 0 ? { ...parameters } : parameters[key];
|
|
1325
|
+
}
|
|
1326
|
+
session() {
|
|
1327
|
+
return inject(Session);
|
|
1328
|
+
}
|
|
1329
|
+
user(guard) {
|
|
1330
|
+
return this.#userResolver?.(guard);
|
|
1331
|
+
}
|
|
1332
|
+
setUserResolver(resolver) {
|
|
1333
|
+
this.#userResolver = resolver;
|
|
1334
|
+
}
|
|
1335
|
+
/** Validates all() through the ValidationFactory; throws its Validation error on failure. */
|
|
1336
|
+
validate(rules, messages, attributes) {
|
|
1337
|
+
return inject(ValidationFactory).make(this.all(), rules, messages, attributes).validated();
|
|
1338
|
+
}
|
|
1339
|
+
#currentRoute() {
|
|
1340
|
+
const container = currentContainer();
|
|
1341
|
+
return container?.bound(CurrentRoute) ? container.make(CurrentRoute) : void 0;
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
function structuredCloneInput(tree) {
|
|
1345
|
+
const out = {};
|
|
1346
|
+
for (const [key, value] of Object.entries(tree)) {
|
|
1347
|
+
out[key] = isPlainObject(value) ? structuredCloneInput(value) : Array.isArray(value) ? [...value] : value;
|
|
1348
|
+
}
|
|
1349
|
+
return out;
|
|
1350
|
+
}
|
|
1351
|
+
function request(key, defaultValue) {
|
|
1352
|
+
const current = inject(HttpRequest);
|
|
1353
|
+
return key === void 0 ? current : current.input(key, defaultValue);
|
|
1354
|
+
}
|
|
1355
|
+
function response(content, status, headers) {
|
|
1356
|
+
const factory = inject(ResponseFactory);
|
|
1357
|
+
return content === void 0 ? factory : factory.make(content, status, headers);
|
|
1358
|
+
}
|
|
1359
|
+
function redirect(to, status) {
|
|
1360
|
+
const redirector = inject(Redirector);
|
|
1361
|
+
return to === void 0 ? redirector : redirector.to(to, status);
|
|
1362
|
+
}
|
|
1363
|
+
function back(fallback, status) {
|
|
1364
|
+
return inject(Redirector).back(fallback, status);
|
|
1365
|
+
}
|
|
1366
|
+
function url(path, query, absolute) {
|
|
1367
|
+
const generator = inject(UrlGenerator);
|
|
1368
|
+
return path === void 0 ? generator : generator.to(path, query, absolute);
|
|
1369
|
+
}
|
|
1370
|
+
function route(name, parameters, absolute) {
|
|
1371
|
+
return inject(UrlGenerator).route(name, parameters, absolute);
|
|
1372
|
+
}
|
|
1373
|
+
function abort(status, message = "", headers = {}) {
|
|
1374
|
+
if (status instanceof Response) throw new HttpResponseException(status);
|
|
1375
|
+
throw new HttpException(status, message, headers);
|
|
1376
|
+
}
|
|
1377
|
+
function abortIf(condition, status, message = "", headers = {}) {
|
|
1378
|
+
if (condition) abort(status, message, headers);
|
|
1379
|
+
}
|
|
1380
|
+
function abortUnless(condition, status, message = "", headers = {}) {
|
|
1381
|
+
if (!condition) abort(status, message, headers);
|
|
1382
|
+
}
|
|
1383
|
+
function contentDisposition(type, name) {
|
|
1384
|
+
const fallback = name.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "\\$&");
|
|
1385
|
+
const header = `${type}; filename="${fallback}"`;
|
|
1386
|
+
return /^[\x20-\x7e]*$/.test(name) ? header : `${header}; filename*=UTF-8''${encodeURIComponent(name)}`;
|
|
1387
|
+
}
|
|
1388
|
+
var HttpResponseFactory = class {
|
|
1389
|
+
/** Strings default to text/html; charset=utf-8, as in Laravel. */
|
|
1390
|
+
make(content = null, status = 200, headers) {
|
|
1391
|
+
const merged = new Headers(headers);
|
|
1392
|
+
if (typeof content === "string" && !merged.has("content-type")) merged.set("content-type", "text/html; charset=utf-8");
|
|
1393
|
+
return new Response(content, { status, headers: merged });
|
|
1394
|
+
}
|
|
1395
|
+
json(data, status = 200, headers) {
|
|
1396
|
+
const merged = new Headers(headers);
|
|
1397
|
+
if (!merged.has("content-type")) merged.set("content-type", "application/json");
|
|
1398
|
+
return new Response(JSON.stringify(data === void 0 ? null : data), { status, headers: merged });
|
|
1399
|
+
}
|
|
1400
|
+
noContent(status = 204) {
|
|
1401
|
+
return new Response(null, { status });
|
|
1402
|
+
}
|
|
1403
|
+
/** Renders a view (through the ViewFactory) into an HTML response. */
|
|
1404
|
+
async view(name, data = {}, status = 200, headers) {
|
|
1405
|
+
const html = await inject(ViewFactory).make(name, data).render();
|
|
1406
|
+
return this.make(html, status, headers);
|
|
1407
|
+
}
|
|
1408
|
+
/** Streams a file as an attachment. A missing file is a 404. */
|
|
1409
|
+
download(path, name = basename(path), headers) {
|
|
1410
|
+
return this.#file(path, "attachment", name, headers);
|
|
1411
|
+
}
|
|
1412
|
+
/** Streams a file for the browser to display (Laravel's response()->file()). */
|
|
1413
|
+
file(path, headers) {
|
|
1414
|
+
return this.#file(path, "inline", basename(path), headers);
|
|
1415
|
+
}
|
|
1416
|
+
stream(body, status = 200, headers) {
|
|
1417
|
+
return new Response(body, { status, headers });
|
|
1418
|
+
}
|
|
1419
|
+
async #file(path, disposition, name, headers) {
|
|
1420
|
+
const info = await stat(path).catch(() => void 0);
|
|
1421
|
+
if (info === void 0 || !info.isFile()) throw new HttpException(404);
|
|
1422
|
+
const merged = new Headers(headers);
|
|
1423
|
+
if (!merged.has("content-type")) merged.set("content-type", mimeFromExtension(name));
|
|
1424
|
+
merged.set("content-length", String(info.size));
|
|
1425
|
+
merged.set("content-disposition", contentDisposition(disposition, name));
|
|
1426
|
+
const body = Readable.toWeb(createReadStream(path));
|
|
1427
|
+
return new Response(body, { status: 200, headers: merged });
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
var ConvertEmptyStringsToNull = class {
|
|
1431
|
+
async handle(request2, next) {
|
|
1432
|
+
if (request2 instanceof IncomingRequest) request2.transformInput((_key, value) => value === "" ? null : value);
|
|
1433
|
+
return next(request2);
|
|
1434
|
+
}
|
|
1435
|
+
};
|
|
1436
|
+
var TRIM = /^[\s\uFEFF\u200B\u200E]+|[\s\uFEFF\u200B\u200E]+$/g;
|
|
1437
|
+
var TrimStrings = class {
|
|
1438
|
+
/** Dotted input keys left untouched. Override in a subclass to change. */
|
|
1439
|
+
except = ["current_password", "password", "password_confirmation"];
|
|
1440
|
+
async handle(request2, next) {
|
|
1441
|
+
if (request2 instanceof IncomingRequest) {
|
|
1442
|
+
request2.transformInput(
|
|
1443
|
+
(key, value) => typeof value === "string" && !this.except.includes(key) ? value.replace(TRIM, "") : value
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
return next(request2);
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
var plainServerError = () => new Response("Server Error", { status: 500, headers: { "content-type": "text/plain; charset=utf-8" } });
|
|
1450
|
+
var Kernel = class {
|
|
1451
|
+
constructor(app) {
|
|
1452
|
+
this.app = app;
|
|
1453
|
+
}
|
|
1454
|
+
app;
|
|
1455
|
+
#middleware = [TrimStrings, ConvertEmptyStringsToNull];
|
|
1456
|
+
#requests = /* @__PURE__ */ new WeakMap();
|
|
1457
|
+
getGlobalMiddleware() {
|
|
1458
|
+
return [...this.#middleware];
|
|
1459
|
+
}
|
|
1460
|
+
setGlobalMiddleware(middleware) {
|
|
1461
|
+
this.#middleware = [...middleware];
|
|
1462
|
+
return this;
|
|
1463
|
+
}
|
|
1464
|
+
appendMiddleware(...middleware) {
|
|
1465
|
+
for (const cls of middleware) if (!this.#middleware.includes(cls)) this.#middleware.push(cls);
|
|
1466
|
+
return this;
|
|
1467
|
+
}
|
|
1468
|
+
prependMiddleware(...middleware) {
|
|
1469
|
+
for (const cls of [...middleware].reverse()) if (!this.#middleware.includes(cls)) this.#middleware.unshift(cls);
|
|
1470
|
+
return this;
|
|
1471
|
+
}
|
|
1472
|
+
hasMiddleware(middleware) {
|
|
1473
|
+
return this.#middleware.includes(middleware);
|
|
1474
|
+
}
|
|
1475
|
+
/** Runs in the current request scope (Application.handle() opens one); called directly, it opens its own. */
|
|
1476
|
+
handle(raw) {
|
|
1477
|
+
const current = currentContainer();
|
|
1478
|
+
const scope = current !== void 0 && current !== this.app ? current : this.app.createScope();
|
|
1479
|
+
return scope.run(() => this.#handle(raw, scope));
|
|
1480
|
+
}
|
|
1481
|
+
async #handle(raw, scope) {
|
|
1482
|
+
let request2;
|
|
1483
|
+
let response2;
|
|
1484
|
+
try {
|
|
1485
|
+
request2 = new IncomingRequest(raw, {
|
|
1486
|
+
trustedProxies: this.#trustedProxies(),
|
|
1487
|
+
maxBodySize: this.#maxBodySize(),
|
|
1488
|
+
maxJsonDepth: this.#maxJsonDepth()
|
|
1489
|
+
});
|
|
1490
|
+
scope.instance(HttpRequest, request2);
|
|
1491
|
+
const state = { scope, request: request2, middleware: [] };
|
|
1492
|
+
this.#requests.set(raw, state);
|
|
1493
|
+
await request2.load();
|
|
1494
|
+
response2 = await this.#sendThroughGlobal(state);
|
|
1495
|
+
} catch (error) {
|
|
1496
|
+
request2 ??= this.#bareRequest(scope, raw);
|
|
1497
|
+
if (request2 !== void 0) {
|
|
1498
|
+
response2 = await this.#renderLast(scope, request2, error);
|
|
1499
|
+
} else {
|
|
1500
|
+
await this.#report(scope, error);
|
|
1501
|
+
response2 = plainServerError();
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
if (raw.method.toUpperCase() === "HEAD" && response2.body !== null) {
|
|
1505
|
+
await response2.body.cancel().catch(() => void 0);
|
|
1506
|
+
response2 = new Response(null, { status: response2.status, statusText: response2.statusText, headers: response2.headers });
|
|
1507
|
+
}
|
|
1508
|
+
return response2;
|
|
1509
|
+
}
|
|
1510
|
+
/** The request without options, for rendering an error raised while the real one was being built. */
|
|
1511
|
+
#bareRequest(scope, raw) {
|
|
1512
|
+
try {
|
|
1513
|
+
const request2 = new IncomingRequest(raw);
|
|
1514
|
+
scope.instance(HttpRequest, request2);
|
|
1515
|
+
return request2;
|
|
1516
|
+
} catch {
|
|
1517
|
+
return void 0;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
/** Runs terminate() on the global and route middleware (inside the request's scope), then Application.terminate(), inside the same scope, so terminating callbacks see the request's bindings. */
|
|
1521
|
+
async terminate(raw, response2) {
|
|
1522
|
+
const state = this.#requests.get(raw);
|
|
1523
|
+
if (state !== void 0) {
|
|
1524
|
+
this.#requests.delete(raw);
|
|
1525
|
+
await state.scope.run(async () => {
|
|
1526
|
+
const route2 = state.scope.bound(CurrentRoute) ? state.scope.make(CurrentRoute).middleware : [];
|
|
1527
|
+
for (const middleware of [...state.middleware, ...route2]) {
|
|
1528
|
+
if (typeof middleware.terminate !== "function") continue;
|
|
1529
|
+
try {
|
|
1530
|
+
await middleware.terminate(state.request, response2);
|
|
1531
|
+
} catch (error) {
|
|
1532
|
+
await this.#report(state.scope, error);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
await this.app.terminate();
|
|
1536
|
+
});
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
await this.app.terminate();
|
|
1540
|
+
}
|
|
1541
|
+
#sendThroughGlobal(state) {
|
|
1542
|
+
const { scope, request: request2 } = state;
|
|
1543
|
+
const pipes = this.#middleware.map((cls) => {
|
|
1544
|
+
const instance = scope.make(cls);
|
|
1545
|
+
state.middleware.push(instance);
|
|
1546
|
+
return (passable, next) => this.#guard(scope, passable, () => instance.handle(passable, next));
|
|
1547
|
+
});
|
|
1548
|
+
return scope.make(PipelineFactory).make().send(request2).through(pipes).then((passable) => this.#guard(scope, passable, () => this.#dispatch(scope, passable)));
|
|
1549
|
+
}
|
|
1550
|
+
#dispatch(scope, request2) {
|
|
1551
|
+
return scope.make(Router).dispatch(request2);
|
|
1552
|
+
}
|
|
1553
|
+
/** Converts a Responsable result, and renders a thrown error at this stage (Laravel's Routing\Pipeline). */
|
|
1554
|
+
async #guard(scope, request2, run) {
|
|
1555
|
+
try {
|
|
1556
|
+
const result = await run();
|
|
1557
|
+
if (result instanceof Response) return result;
|
|
1558
|
+
const responsable = result;
|
|
1559
|
+
if (typeof responsable?.toResponse === "function") return await responsable.toResponse(request2);
|
|
1560
|
+
throw new HttpServerError("Middleware must return a Response.");
|
|
1561
|
+
} catch (error) {
|
|
1562
|
+
if (!scope.bound(ExceptionHandler)) throw error;
|
|
1563
|
+
const handler = scope.make(ExceptionHandler);
|
|
1564
|
+
await handler.report(error);
|
|
1565
|
+
return handler.render(request2, error);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
/** Last resort: the handler failed or isn't bound. Never throws. */
|
|
1569
|
+
async #renderLast(scope, request2, error) {
|
|
1570
|
+
if (scope.bound(ExceptionHandler)) {
|
|
1571
|
+
try {
|
|
1572
|
+
const handler = scope.make(ExceptionHandler);
|
|
1573
|
+
await handler.report(error);
|
|
1574
|
+
return await handler.render(request2, error);
|
|
1575
|
+
} catch (renderError) {
|
|
1576
|
+
console.error(renderError);
|
|
1577
|
+
}
|
|
1578
|
+
} else {
|
|
1579
|
+
console.error(error);
|
|
1580
|
+
}
|
|
1581
|
+
return plainServerError();
|
|
1582
|
+
}
|
|
1583
|
+
async #report(scope, error) {
|
|
1584
|
+
try {
|
|
1585
|
+
if (scope.bound(ExceptionHandler)) await scope.make(ExceptionHandler).report(error);
|
|
1586
|
+
else console.error(error);
|
|
1587
|
+
} catch (reportError) {
|
|
1588
|
+
console.error(reportError);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
/** config('http.max_body_size'): bytes, or null for no limit. Anything else is a configuration error. */
|
|
1592
|
+
#maxBodySize() {
|
|
1593
|
+
if (!this.app.bound(ConfigRepository)) return 8 * 1024 * 1024;
|
|
1594
|
+
const value = this.app.make(ConfigRepository).get("http.max_body_size", 8 * 1024 * 1024);
|
|
1595
|
+
if (value === null) return null;
|
|
1596
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
|
|
1597
|
+
throw new HttpServerError(
|
|
1598
|
+
`Invalid http.max_body_size ${JSON.stringify(value) ?? String(value)}: use a number of bytes (0 or more), or null for no limit.`
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
/** config('http.max_json_depth'): how deep a JSON body may nest, or null for no limit. */
|
|
1602
|
+
#maxJsonDepth() {
|
|
1603
|
+
if (!this.app.bound(ConfigRepository)) return 512;
|
|
1604
|
+
const value = this.app.make(ConfigRepository).get("http.max_json_depth", 512);
|
|
1605
|
+
if (value === null) return null;
|
|
1606
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 1) return value;
|
|
1607
|
+
throw new HttpServerError(
|
|
1608
|
+
`Invalid http.max_json_depth ${JSON.stringify(value) ?? String(value)}: use a whole number (1 or more), or null for no limit.`
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
/** config('http.trusted_proxies'): an array, or a comma-separated string such as '*' or '10.0.0.0/8,::1'. */
|
|
1612
|
+
#trustedProxies() {
|
|
1613
|
+
if (!this.app.bound(ConfigRepository)) return [];
|
|
1614
|
+
const value = this.app.make(ConfigRepository).get("http.trusted_proxies", []);
|
|
1615
|
+
if (typeof value === "string") return value.split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
|
|
1616
|
+
return Array.isArray(value) ? value.map(String) : [];
|
|
1617
|
+
}
|
|
1618
|
+
};
|
|
1619
|
+
var MiddlewareConfiguration = class {
|
|
1620
|
+
constructor(kernel, router, config) {
|
|
1621
|
+
this.kernel = kernel;
|
|
1622
|
+
this.router = router;
|
|
1623
|
+
this.config = config;
|
|
1624
|
+
}
|
|
1625
|
+
kernel;
|
|
1626
|
+
router;
|
|
1627
|
+
config;
|
|
1628
|
+
append(...middleware) {
|
|
1629
|
+
this.kernel.appendMiddleware(...middleware);
|
|
1630
|
+
return this;
|
|
1631
|
+
}
|
|
1632
|
+
prepend(...middleware) {
|
|
1633
|
+
this.kernel.prependMiddleware(...middleware);
|
|
1634
|
+
return this;
|
|
1635
|
+
}
|
|
1636
|
+
web(options) {
|
|
1637
|
+
return this.#modifyGroup("web", options);
|
|
1638
|
+
}
|
|
1639
|
+
api(options) {
|
|
1640
|
+
return this.#modifyGroup("api", options);
|
|
1641
|
+
}
|
|
1642
|
+
alias(aliases) {
|
|
1643
|
+
for (const [name, middleware] of Object.entries(aliases)) this.router.aliasMiddleware(name, middleware);
|
|
1644
|
+
return this;
|
|
1645
|
+
}
|
|
1646
|
+
group(name, middleware) {
|
|
1647
|
+
this.router.middlewareGroup(name, middleware);
|
|
1648
|
+
return this;
|
|
1649
|
+
}
|
|
1650
|
+
/** Stored in config('http.csrf_except'), which the Session component's VerifyCsrfToken reads. */
|
|
1651
|
+
validateCsrfTokens(options) {
|
|
1652
|
+
const current = this.config?.get("http.csrf_except", []) ?? [];
|
|
1653
|
+
this.config?.set("http.csrf_except", [...current, ...options.except]);
|
|
1654
|
+
return this;
|
|
1655
|
+
}
|
|
1656
|
+
/** Stored in config('http.redirect_guests_to'), which the Auth middleware and the exception handler read. */
|
|
1657
|
+
redirectGuestsTo(url2) {
|
|
1658
|
+
this.config?.set("http.redirect_guests_to", url2);
|
|
1659
|
+
return this;
|
|
1660
|
+
}
|
|
1661
|
+
/** Laravel 11's redirectUsersTo(): stored in config('http.redirect_users_to') for Auth's RedirectIfAuthenticated. */
|
|
1662
|
+
redirectUsersTo(url2) {
|
|
1663
|
+
this.config?.set("http.redirect_users_to", url2);
|
|
1664
|
+
return this;
|
|
1665
|
+
}
|
|
1666
|
+
/** Laravel's order: prepends, then the group minus removals, then appends. */
|
|
1667
|
+
#modifyGroup(name, { append = [], prepend = [], remove = [] }) {
|
|
1668
|
+
const current = this.router.getMiddlewareGroups()[name] ?? [];
|
|
1669
|
+
const moved = /* @__PURE__ */ new Set([...append, ...prepend, ...remove]);
|
|
1670
|
+
this.router.middlewareGroup(name, [...prepend, ...current.filter((entry) => !moved.has(entry)), ...append]);
|
|
1671
|
+
return this;
|
|
1672
|
+
}
|
|
1673
|
+
};
|
|
1674
|
+
function applyMiddlewareCallbacks(app, kernel) {
|
|
1675
|
+
if (!app.bound(MiddlewareCallbacks)) return;
|
|
1676
|
+
const callbacks = app.make(MiddlewareCallbacks);
|
|
1677
|
+
if (callbacks.length === 0) return;
|
|
1678
|
+
const config = app.bound(ConfigRepository) ? app.make(ConfigRepository) : void 0;
|
|
1679
|
+
const configurator = new MiddlewareConfiguration(kernel, app.make(Router), config);
|
|
1680
|
+
for (const callback of callbacks) callback(configurator);
|
|
1681
|
+
}
|
|
1682
|
+
var STATUS_TEXTS = {
|
|
1683
|
+
400: "Bad Request",
|
|
1684
|
+
401: "Unauthorized",
|
|
1685
|
+
402: "Payment Required",
|
|
1686
|
+
403: "Forbidden",
|
|
1687
|
+
404: "Not Found",
|
|
1688
|
+
405: "Method Not Allowed",
|
|
1689
|
+
408: "Request Timeout",
|
|
1690
|
+
409: "Conflict",
|
|
1691
|
+
410: "Gone",
|
|
1692
|
+
413: "Content Too Large",
|
|
1693
|
+
415: "Unsupported Media Type",
|
|
1694
|
+
419: "Page Expired",
|
|
1695
|
+
422: "Unprocessable Content",
|
|
1696
|
+
429: "Too Many Requests",
|
|
1697
|
+
500: "Server Error",
|
|
1698
|
+
502: "Bad Gateway",
|
|
1699
|
+
503: "Service Unavailable",
|
|
1700
|
+
504: "Gateway Timeout"
|
|
1701
|
+
};
|
|
1702
|
+
function statusText(status) {
|
|
1703
|
+
return STATUS_TEXTS[status] ?? (status >= 500 ? "Server Error" : "Error");
|
|
1704
|
+
}
|
|
1705
|
+
function escapeHtml(value) {
|
|
1706
|
+
return value.replace(/[&<>"']/g, (char) => `&#${char.charCodeAt(0)};`);
|
|
1707
|
+
}
|
|
1708
|
+
function minimalPage(status, message) {
|
|
1709
|
+
const title = escapeHtml(message);
|
|
1710
|
+
return `<!DOCTYPE html>
|
|
1711
|
+
<html lang="en">
|
|
1712
|
+
<head>
|
|
1713
|
+
<meta charset="utf-8">
|
|
1714
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1715
|
+
<title>${title}</title>
|
|
1716
|
+
<style>body{margin:0;font-family:ui-sans-serif,system-ui,sans-serif;background:#f7fafc;color:#1a202c;display:flex;align-items:center;justify-content:center;min-height:100vh}.code{padding:0 1rem;border-right:1px solid #a0aec0;font-size:1.125rem}.message{padding:0 1rem;font-size:1.125rem;text-transform:uppercase;letter-spacing:.05em}</style>
|
|
1717
|
+
</head>
|
|
1718
|
+
<body><div class="code">${status}</div><div class="message">${title}</div></body>
|
|
1719
|
+
</html>`;
|
|
1720
|
+
}
|
|
1721
|
+
function stackFrames(error) {
|
|
1722
|
+
const frames = [];
|
|
1723
|
+
for (const raw of (error.stack ?? "").split("\n").slice(1)) {
|
|
1724
|
+
const text = raw.trim();
|
|
1725
|
+
const match = /(?:\(|@|at )((?:file:\/\/)?(?:\/|[A-Za-z]:\\)[^()]*?):(\d+):(\d+)\)?$/.exec(text);
|
|
1726
|
+
if (match === null) continue;
|
|
1727
|
+
const location = match[1];
|
|
1728
|
+
const file = location.startsWith("file://") ? fileURLToPath(location) : location;
|
|
1729
|
+
frames.push({ file, line: Number(match[2]), column: Number(match[3]), text });
|
|
1730
|
+
}
|
|
1731
|
+
return frames;
|
|
1732
|
+
}
|
|
1733
|
+
async function excerpt(frame, radius = 5) {
|
|
1734
|
+
const source = await readFile(frame.file, "utf8").catch(() => void 0);
|
|
1735
|
+
if (source === void 0) return void 0;
|
|
1736
|
+
const lines = source.split("\n");
|
|
1737
|
+
const start = Math.max(1, frame.line - radius);
|
|
1738
|
+
const end = Math.min(lines.length, frame.line + radius);
|
|
1739
|
+
const rows = [];
|
|
1740
|
+
for (let number = start; number <= end; number++) {
|
|
1741
|
+
const current = number === frame.line ? ' class="current"' : "";
|
|
1742
|
+
rows.push(`<tr${current}><td class="n">${number}</td><td><code>${escapeHtml(lines[number - 1] ?? "")}</code></td></tr>`);
|
|
1743
|
+
}
|
|
1744
|
+
return `<table class="source">${rows.join("")}</table>`;
|
|
1745
|
+
}
|
|
1746
|
+
async function debugPage(error, method, url2) {
|
|
1747
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1748
|
+
const frames = stackFrames(err);
|
|
1749
|
+
const appFrame = frames.find((frame) => !frame.file.includes("node_modules") && !frame.file.startsWith("node:")) ?? frames[0];
|
|
1750
|
+
const source = appFrame === void 0 ? void 0 : await excerpt(appFrame);
|
|
1751
|
+
const causes = [];
|
|
1752
|
+
for (let cause = err.cause, depth = 0; cause !== void 0 && depth < 5; cause = cause.cause, depth++) {
|
|
1753
|
+
causes.push(`<li>${escapeHtml(cause instanceof Error ? `${cause.name}: ${cause.message}` : String(cause))}</li>`);
|
|
1754
|
+
}
|
|
1755
|
+
return `<!DOCTYPE html>
|
|
1756
|
+
<html lang="en">
|
|
1757
|
+
<head>
|
|
1758
|
+
<meta charset="utf-8">
|
|
1759
|
+
<title>${escapeHtml(err.message || err.name)}</title>
|
|
1760
|
+
<style>body{margin:0;font-family:ui-sans-serif,system-ui,sans-serif;background:#f3f4f6;color:#111827}main{max-width:1100px;margin:2rem auto;padding:0 1rem}header{background:#fff;border-radius:.5rem;padding:1.5rem;box-shadow:0 1px 3px #0001}.class{color:#dc2626;font-weight:600}.message{font-size:1.5rem;margin:.5rem 0}.request{color:#6b7280}section{background:#fff;border-radius:.5rem;padding:1rem;margin-top:1rem;box-shadow:0 1px 3px #0001;overflow:auto}.source td{padding:0 .5rem;white-space:pre;font-size:.85rem}.source .n{color:#9ca3af;text-align:right}.source .current{background:#fee2e2}ol{font-family:ui-monospace,monospace;font-size:.85rem}</style>
|
|
1761
|
+
</head>
|
|
1762
|
+
<body><main>
|
|
1763
|
+
<header><div class="class">${escapeHtml(err.name)}</div><div class="message">${escapeHtml(err.message)}</div><div class="request">${escapeHtml(method)} ${escapeHtml(url2)}</div></header>
|
|
1764
|
+
${appFrame === void 0 ? "" : `<section><div>${escapeHtml(appFrame.file)}:${appFrame.line}</div>${source ?? ""}</section>`}
|
|
1765
|
+
<section><ol>${frames.map((frame) => `<li>${escapeHtml(frame.text)}</li>`).join("")}</ol></section>
|
|
1766
|
+
${causes.length === 0 ? "" : `<section><div>Caused by</div><ul>${causes.join("")}</ul></section>`}
|
|
1767
|
+
</main></body>
|
|
1768
|
+
</html>`;
|
|
1769
|
+
}
|
|
1770
|
+
var NOT_REPORTED = /* @__PURE__ */ new Set(["Http", "ModelNotFound", "Validation", "Authentication", "Authorization", "TokenMismatch"]);
|
|
1771
|
+
var HTTP_KINDS = /* @__PURE__ */ new Set(["Http", "ModelNotFound", "Authorization", "TokenMismatch"]);
|
|
1772
|
+
var kindOf = (error) => typeof error === "object" && error !== null && typeof error.kind === "string" ? error.kind : void 0;
|
|
1773
|
+
function statusFor(error) {
|
|
1774
|
+
const status = error?.status;
|
|
1775
|
+
switch (kindOf(error)) {
|
|
1776
|
+
case "Http":
|
|
1777
|
+
return status ?? 500;
|
|
1778
|
+
case "ModelNotFound":
|
|
1779
|
+
return 404;
|
|
1780
|
+
case "Authorization":
|
|
1781
|
+
return status ?? 403;
|
|
1782
|
+
case "TokenMismatch":
|
|
1783
|
+
return 419;
|
|
1784
|
+
case "Authentication":
|
|
1785
|
+
return 401;
|
|
1786
|
+
case "Validation":
|
|
1787
|
+
return status ?? 422;
|
|
1788
|
+
case "Server":
|
|
1789
|
+
return status ?? 500;
|
|
1790
|
+
default:
|
|
1791
|
+
return 500;
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
function summarizeValidation(errors) {
|
|
1795
|
+
const messages = Object.values(errors).flat();
|
|
1796
|
+
if (messages.length === 0) return "The given data was invalid.";
|
|
1797
|
+
const more = messages.length - 1;
|
|
1798
|
+
return more === 0 ? messages[0] : `${messages[0]} (and ${more} more ${more === 1 ? "error" : "errors"})`;
|
|
1799
|
+
}
|
|
1800
|
+
var isHttpLike = (error) => {
|
|
1801
|
+
const kind = kindOf(error);
|
|
1802
|
+
return kind !== void 0 && HTTP_KINDS.has(kind);
|
|
1803
|
+
};
|
|
1804
|
+
var ErrorHandler = class {
|
|
1805
|
+
constructor(app) {
|
|
1806
|
+
this.app = app;
|
|
1807
|
+
}
|
|
1808
|
+
app;
|
|
1809
|
+
/** Input keys never flashed back on a validation redirect. */
|
|
1810
|
+
dontFlash = ["current_password", "password", "password_confirmation"];
|
|
1811
|
+
#reportCallbacks = [];
|
|
1812
|
+
#renderCallbacks = [];
|
|
1813
|
+
#dontReport = [];
|
|
1814
|
+
#reported = /* @__PURE__ */ new WeakSet();
|
|
1815
|
+
#jsonWhen;
|
|
1816
|
+
reportUsing(callback) {
|
|
1817
|
+
this.#reportCallbacks.push(callback);
|
|
1818
|
+
}
|
|
1819
|
+
renderUsing(callback) {
|
|
1820
|
+
this.#renderCallbacks.push(callback);
|
|
1821
|
+
}
|
|
1822
|
+
ignore(...classes) {
|
|
1823
|
+
this.#dontReport.push(...classes);
|
|
1824
|
+
}
|
|
1825
|
+
/** Laravel's shouldRenderJsonWhen(): the callback decides JSON or HTML instead of the Accept header. */
|
|
1826
|
+
shouldRenderJsonWhen(callback) {
|
|
1827
|
+
this.#jsonWhen = callback;
|
|
1828
|
+
}
|
|
1829
|
+
/** Laravel's shouldReturnJson(): the shouldRenderJsonWhen() callback when set, else request.expectsJson(). */
|
|
1830
|
+
shouldReturnJson(request2, error) {
|
|
1831
|
+
return this.#jsonWhen !== void 0 ? this.#jsonWhen(request2, error) : request2.expectsJson();
|
|
1832
|
+
}
|
|
1833
|
+
shouldReport(error) {
|
|
1834
|
+
const kind = kindOf(error);
|
|
1835
|
+
if (kind !== void 0 && NOT_REPORTED.has(kind)) return false;
|
|
1836
|
+
return !this.#dontReport.some((cls) => error instanceof cls);
|
|
1837
|
+
}
|
|
1838
|
+
/** Report callbacks first (returning false stops the default log), then LogManager.error(), else console.error. Once per error. */
|
|
1839
|
+
async report(error) {
|
|
1840
|
+
if (!this.shouldReport(error)) return;
|
|
1841
|
+
if (typeof error === "object" && error !== null) {
|
|
1842
|
+
if (this.#reported.has(error)) return;
|
|
1843
|
+
this.#reported.add(error);
|
|
1844
|
+
}
|
|
1845
|
+
try {
|
|
1846
|
+
for (const callback of this.#reportCallbacks) if (await callback(error) === false) return;
|
|
1847
|
+
const container = this.#container();
|
|
1848
|
+
if (container.bound(LogManager)) {
|
|
1849
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1850
|
+
container.make(LogManager).error(message, { exception: error });
|
|
1851
|
+
} else {
|
|
1852
|
+
console.error(error);
|
|
1853
|
+
}
|
|
1854
|
+
} catch (reportError) {
|
|
1855
|
+
console.error(reportError);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
async render(request2, error) {
|
|
1859
|
+
for (const callback of this.#renderCallbacks) {
|
|
1860
|
+
const response3 = await callback(error, request2);
|
|
1861
|
+
if (response3 instanceof Response) return response3;
|
|
1862
|
+
}
|
|
1863
|
+
if (error instanceof HttpResponseException) return error.response;
|
|
1864
|
+
const custom = await this.#renderCustom(request2, error);
|
|
1865
|
+
if (custom !== void 0) return custom;
|
|
1866
|
+
const http = this.#httpRequest(request2);
|
|
1867
|
+
const kind = kindOf(error);
|
|
1868
|
+
let response2;
|
|
1869
|
+
if (kind === "Validation") response2 = await this.#renderValidation(http, error);
|
|
1870
|
+
else if (kind === "Authentication") response2 = await this.#renderAuthentication(http, error);
|
|
1871
|
+
else if (this.shouldReturnJson(http, error)) response2 = this.#renderJson(error);
|
|
1872
|
+
else response2 = await this.#renderHtml(http, error);
|
|
1873
|
+
const headers = error?.headers;
|
|
1874
|
+
if (headers !== void 0) for (const [name, value] of Object.entries(headers)) response2.headers.set(name, value);
|
|
1875
|
+
return response2;
|
|
1876
|
+
}
|
|
1877
|
+
/** An error with its own render(request) or toResponse(request) (Laravel's renderable exceptions / Responsable). */
|
|
1878
|
+
async #renderCustom(request2, error) {
|
|
1879
|
+
if (typeof error !== "object" || error === null) return void 0;
|
|
1880
|
+
const candidate = error;
|
|
1881
|
+
for (const method of [candidate.render, candidate.toResponse]) {
|
|
1882
|
+
if (typeof method !== "function") continue;
|
|
1883
|
+
const response2 = await method.call(error, request2);
|
|
1884
|
+
if (response2 instanceof Response) return response2;
|
|
1885
|
+
}
|
|
1886
|
+
return void 0;
|
|
1887
|
+
}
|
|
1888
|
+
async #renderValidation(request2, error) {
|
|
1889
|
+
if (this.shouldReturnJson(request2, error)) {
|
|
1890
|
+
return this.#json({ message: summarizeValidation(error.errors), errors: error.errors }, statusFor(error));
|
|
1891
|
+
}
|
|
1892
|
+
const input = { ...request2.input() };
|
|
1893
|
+
for (const key of this.dontFlash) delete input[key];
|
|
1894
|
+
const container = this.#container();
|
|
1895
|
+
if (!container.bound(Redirector)) {
|
|
1896
|
+
return this.#redirect(error.redirectTo ?? request2.header("referer") ?? "/");
|
|
1897
|
+
}
|
|
1898
|
+
const redirector = container.make(Redirector);
|
|
1899
|
+
const redirect2 = error.redirectTo !== void 0 ? redirector.to(error.redirectTo) : redirector.back();
|
|
1900
|
+
const response2 = await redirect2.withInput(input).withErrors(error.errors, error.errorBag).toResponse(request2);
|
|
1901
|
+
return response2;
|
|
1902
|
+
}
|
|
1903
|
+
async #renderAuthentication(request2, error) {
|
|
1904
|
+
if (this.shouldReturnJson(request2, error)) return this.#json({ message: error.message || "Unauthenticated." }, 401);
|
|
1905
|
+
const target2 = error.redirectTo ?? this.#guestRedirect(request2);
|
|
1906
|
+
const container = this.#container();
|
|
1907
|
+
if (!container.bound(Redirector)) return this.#redirect(target2);
|
|
1908
|
+
const redirector = container.make(Redirector);
|
|
1909
|
+
const redirect2 = typeof redirector.guest === "function" ? redirector.guest(target2) : redirector.to(target2);
|
|
1910
|
+
return await redirect2.toResponse(request2);
|
|
1911
|
+
}
|
|
1912
|
+
/** config('http.redirect_guests_to'), then route('login'), then '/login'. */
|
|
1913
|
+
#guestRedirect(request2) {
|
|
1914
|
+
const configured = this.#config("http.redirect_guests_to", void 0);
|
|
1915
|
+
if (typeof configured === "function") return String(configured(request2));
|
|
1916
|
+
if (typeof configured === "string" && configured !== "") return configured;
|
|
1917
|
+
const container = this.#container();
|
|
1918
|
+
if (container.bound(UrlGenerator)) {
|
|
1919
|
+
const url2 = container.make(UrlGenerator);
|
|
1920
|
+
if (url2.hasRoute("login")) return url2.route("login");
|
|
1921
|
+
}
|
|
1922
|
+
return "/login";
|
|
1923
|
+
}
|
|
1924
|
+
#renderJson(error) {
|
|
1925
|
+
const status = statusFor(error);
|
|
1926
|
+
if (isHttpLike(error)) {
|
|
1927
|
+
const message = error instanceof Error && error.message !== "" ? error.message : statusText(status);
|
|
1928
|
+
return this.#json({ message }, status);
|
|
1929
|
+
}
|
|
1930
|
+
if (!this.#debug()) return this.#json({ message: "Server Error" }, status);
|
|
1931
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1932
|
+
const [frame] = stackFrames(err);
|
|
1933
|
+
return this.#json(
|
|
1934
|
+
{
|
|
1935
|
+
message: err.message,
|
|
1936
|
+
exception: err.name,
|
|
1937
|
+
file: frame?.file,
|
|
1938
|
+
line: frame?.line,
|
|
1939
|
+
trace: stackFrames(err).map((entry) => entry.text)
|
|
1940
|
+
},
|
|
1941
|
+
status
|
|
1942
|
+
);
|
|
1943
|
+
}
|
|
1944
|
+
async #renderHtml(request2, error) {
|
|
1945
|
+
const status = statusFor(error);
|
|
1946
|
+
if (!isHttpLike(error) && this.#debug()) {
|
|
1947
|
+
return this.#html(await debugPage(error, request2.method(), request2.fullUrl()), status);
|
|
1948
|
+
}
|
|
1949
|
+
const message = status === 403 && error instanceof Error && error.message !== "" ? error.message : statusText(status);
|
|
1950
|
+
const view = await this.#errorView(status, error, message);
|
|
1951
|
+
return this.#html(view ?? minimalPage(status, message), status);
|
|
1952
|
+
}
|
|
1953
|
+
/** resources/views/errors/{status}.tsx, then errors/4xx or errors/5xx, through the ViewFactory. */
|
|
1954
|
+
async #errorView(status, error, message) {
|
|
1955
|
+
const container = this.#container();
|
|
1956
|
+
if (!container.bound(ViewFactory)) return void 0;
|
|
1957
|
+
const views = container.make(ViewFactory);
|
|
1958
|
+
for (const name of [`errors.${status}`, `errors.${Math.floor(status / 100)}xx`]) {
|
|
1959
|
+
if (!views.exists(name)) continue;
|
|
1960
|
+
try {
|
|
1961
|
+
return await views.make(name, { exception: error, status, message }).render();
|
|
1962
|
+
} catch (viewError) {
|
|
1963
|
+
await this.report(viewError);
|
|
1964
|
+
return void 0;
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
return void 0;
|
|
1968
|
+
}
|
|
1969
|
+
#httpRequest(request2) {
|
|
1970
|
+
if (typeof request2 === "object" && request2 !== null && typeof request2.expectsJson === "function") {
|
|
1971
|
+
return request2;
|
|
1972
|
+
}
|
|
1973
|
+
return new IncomingRequest(request2 instanceof Request ? request2 : new Request("http://localhost/"));
|
|
1974
|
+
}
|
|
1975
|
+
#json(body, status) {
|
|
1976
|
+
return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
1977
|
+
}
|
|
1978
|
+
#html(body, status) {
|
|
1979
|
+
return new Response(body, { status, headers: { "content-type": "text/html; charset=utf-8" } });
|
|
1980
|
+
}
|
|
1981
|
+
#redirect(location) {
|
|
1982
|
+
return new Response(null, { status: 302, headers: { Location: location } });
|
|
1983
|
+
}
|
|
1984
|
+
#debug() {
|
|
1985
|
+
return this.#config("app.debug", false) === true;
|
|
1986
|
+
}
|
|
1987
|
+
#config(key, fallback) {
|
|
1988
|
+
return this.app.bound(ConfigRepository) ? this.app.make(ConfigRepository).get(key, fallback) : fallback;
|
|
1989
|
+
}
|
|
1990
|
+
/** The request scope while handling a request (so scoped services resolve), else the application. */
|
|
1991
|
+
#container() {
|
|
1992
|
+
return currentContainer() ?? this.app;
|
|
1993
|
+
}
|
|
1994
|
+
};
|
|
1995
|
+
var ExceptionsConfiguration = class {
|
|
1996
|
+
constructor(handler) {
|
|
1997
|
+
this.handler = handler;
|
|
1998
|
+
}
|
|
1999
|
+
handler;
|
|
2000
|
+
report(callback) {
|
|
2001
|
+
this.handler.reportUsing(callback);
|
|
2002
|
+
return this;
|
|
2003
|
+
}
|
|
2004
|
+
render(callback) {
|
|
2005
|
+
this.handler.renderUsing(callback);
|
|
2006
|
+
return this;
|
|
2007
|
+
}
|
|
2008
|
+
dontReport(...classes) {
|
|
2009
|
+
this.handler.ignore(...classes);
|
|
2010
|
+
return this;
|
|
2011
|
+
}
|
|
2012
|
+
/** shouldRenderJsonWhen((request) => request.is('api/*')): JSON errors for those requests, whatever they accept. */
|
|
2013
|
+
shouldRenderJsonWhen(callback) {
|
|
2014
|
+
this.handler.shouldRenderJsonWhen(callback);
|
|
2015
|
+
return this;
|
|
2016
|
+
}
|
|
2017
|
+
};
|
|
2018
|
+
function applyExceptionCallbacks(app, handler) {
|
|
2019
|
+
if (!app.bound(ExceptionCallbacks)) return;
|
|
2020
|
+
const configurator = new ExceptionsConfiguration(handler);
|
|
2021
|
+
for (const callback of app.make(ExceptionCallbacks)) callback(configurator);
|
|
2022
|
+
}
|
|
2023
|
+
var HIDDEN_SEGMENT = /^\.|\.(ts|tsx|mts|cts)$|[. ]$/i;
|
|
2024
|
+
async function servePublicFile(publicPath, request2) {
|
|
2025
|
+
if (request2.method !== "GET" && request2.method !== "HEAD") return void 0;
|
|
2026
|
+
let pathname;
|
|
2027
|
+
try {
|
|
2028
|
+
pathname = decodeURIComponent(new URL(request2.url).pathname);
|
|
2029
|
+
} catch {
|
|
2030
|
+
return void 0;
|
|
2031
|
+
}
|
|
2032
|
+
if (pathname === "/" || pathname.includes("\0")) return void 0;
|
|
2033
|
+
const root = resolve(publicPath);
|
|
2034
|
+
const file = resolve(root, `.${pathname}`);
|
|
2035
|
+
if (!file.startsWith(root + sep)) return void 0;
|
|
2036
|
+
if (relative(root, file).split(/[\\/]/).some((segment) => HIDDEN_SEGMENT.test(segment))) return void 0;
|
|
2037
|
+
const info = await stat2(file).catch(() => void 0);
|
|
2038
|
+
if (info === void 0 || !info.isFile()) return void 0;
|
|
2039
|
+
const type = mimeFromExtension(file);
|
|
2040
|
+
const headers = new Headers({
|
|
2041
|
+
"content-type": type.startsWith("text/") || type === "application/json" ? `${type}; charset=utf-8` : type,
|
|
2042
|
+
"content-length": String(info.size),
|
|
2043
|
+
"last-modified": info.mtime.toUTCString()
|
|
2044
|
+
});
|
|
2045
|
+
if (pathname.startsWith("/build/assets/")) headers.set("cache-control", "public, max-age=31536000, immutable");
|
|
2046
|
+
if (request2.method === "HEAD") return new Response(null, { status: 200, headers });
|
|
2047
|
+
const body = Readable2.toWeb(createReadStream2(file));
|
|
2048
|
+
return new Response(body, { status: 200, headers });
|
|
2049
|
+
}
|
|
2050
|
+
async function terminateRequest(app, request2, response2) {
|
|
2051
|
+
try {
|
|
2052
|
+
if (app.bound(HttpKernel)) await app.make(HttpKernel).terminate?.(request2, response2);
|
|
2053
|
+
} catch (error) {
|
|
2054
|
+
console.error(error);
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
async function handleSafely(app, request2, publicPath) {
|
|
2058
|
+
try {
|
|
2059
|
+
if (publicPath !== void 0) {
|
|
2060
|
+
const file = await servePublicFile(publicPath, request2);
|
|
2061
|
+
if (file !== void 0) return file;
|
|
2062
|
+
}
|
|
2063
|
+
return await app.handle(request2);
|
|
2064
|
+
} catch (error) {
|
|
2065
|
+
console.error(error);
|
|
2066
|
+
return plainServerError();
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
function exitOnSignals(close, closed) {
|
|
2070
|
+
const onSignal = () => {
|
|
2071
|
+
void close().then(() => process.exit(0));
|
|
2072
|
+
};
|
|
2073
|
+
process.once("SIGINT", onSignal);
|
|
2074
|
+
process.once("SIGTERM", onSignal);
|
|
2075
|
+
void closed.then(() => {
|
|
2076
|
+
process.off("SIGINT", onSignal);
|
|
2077
|
+
process.off("SIGTERM", onSignal);
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
var displayHost = (hostname) => hostname.includes(":") ? `[${hostname}]` : hostname;
|
|
2081
|
+
function bunRuntime() {
|
|
2082
|
+
return globalThis.Bun;
|
|
2083
|
+
}
|
|
2084
|
+
async function serveBun(app, options) {
|
|
2085
|
+
const bun = bunRuntime();
|
|
2086
|
+
if (bun === void 0) throw new HttpServerError("Bun.serve is not available: the process is not running on Bun.");
|
|
2087
|
+
let markClosed = () => void 0;
|
|
2088
|
+
const closed = new Promise((resolve2) => markClosed = resolve2);
|
|
2089
|
+
const server = bun.serve({
|
|
2090
|
+
port: options.port,
|
|
2091
|
+
hostname: options.hostname,
|
|
2092
|
+
async fetch(request2, current) {
|
|
2093
|
+
setClientAddress(request2, current.requestIP(request2)?.address);
|
|
2094
|
+
const response2 = await handleSafely(app, request2, options.publicPath);
|
|
2095
|
+
setTimeout(() => void terminateRequest(app, request2, response2), 0);
|
|
2096
|
+
return response2;
|
|
2097
|
+
},
|
|
2098
|
+
error(error) {
|
|
2099
|
+
console.error(error);
|
|
2100
|
+
return plainServerError();
|
|
2101
|
+
}
|
|
2102
|
+
});
|
|
2103
|
+
let closing;
|
|
2104
|
+
const close = () => closing ??= (async () => {
|
|
2105
|
+
let timer;
|
|
2106
|
+
const forced = new Promise((resolve2) => {
|
|
2107
|
+
timer = setTimeout(() => void Promise.resolve(server.stop(true)).then(resolve2), options.shutdownTimeout);
|
|
2108
|
+
});
|
|
2109
|
+
await Promise.race([Promise.resolve(server.stop(false)), forced]);
|
|
2110
|
+
clearTimeout(timer);
|
|
2111
|
+
markClosed();
|
|
2112
|
+
})();
|
|
2113
|
+
if (options.signals) exitOnSignals(close, closed);
|
|
2114
|
+
return {
|
|
2115
|
+
runtime: "bun",
|
|
2116
|
+
hostname: options.hostname,
|
|
2117
|
+
port: server.port,
|
|
2118
|
+
url: `http://${displayHost(options.hostname)}:${server.port}`,
|
|
2119
|
+
close,
|
|
2120
|
+
closed
|
|
2121
|
+
};
|
|
2122
|
+
}
|
|
2123
|
+
function requestBody(req) {
|
|
2124
|
+
let finished = false;
|
|
2125
|
+
const detach = () => {
|
|
2126
|
+
finished = true;
|
|
2127
|
+
req.off("data", onData);
|
|
2128
|
+
req.off("end", onEnd);
|
|
2129
|
+
req.off("error", onError);
|
|
2130
|
+
req.off("close", onClose);
|
|
2131
|
+
};
|
|
2132
|
+
let controller;
|
|
2133
|
+
const onData = (chunk2) => {
|
|
2134
|
+
if (finished) return;
|
|
2135
|
+
controller.enqueue(new Uint8Array(chunk2.buffer, chunk2.byteOffset, chunk2.byteLength));
|
|
2136
|
+
if ((controller.desiredSize ?? 0) <= 0) req.pause();
|
|
2137
|
+
};
|
|
2138
|
+
const onEnd = () => {
|
|
2139
|
+
if (finished) return;
|
|
2140
|
+
detach();
|
|
2141
|
+
controller.close();
|
|
2142
|
+
};
|
|
2143
|
+
const onError = (error) => {
|
|
2144
|
+
if (finished) return;
|
|
2145
|
+
detach();
|
|
2146
|
+
controller.error(error);
|
|
2147
|
+
};
|
|
2148
|
+
const onClose = () => {
|
|
2149
|
+
if (!finished && !req.complete) onError(new Error("The request was aborted."));
|
|
2150
|
+
};
|
|
2151
|
+
return new ReadableStream(
|
|
2152
|
+
{
|
|
2153
|
+
start(c) {
|
|
2154
|
+
controller = c;
|
|
2155
|
+
req.on("data", onData);
|
|
2156
|
+
req.on("end", onEnd);
|
|
2157
|
+
req.on("error", onError);
|
|
2158
|
+
req.on("close", onClose);
|
|
2159
|
+
req.pause();
|
|
2160
|
+
},
|
|
2161
|
+
pull() {
|
|
2162
|
+
req.resume();
|
|
2163
|
+
},
|
|
2164
|
+
cancel() {
|
|
2165
|
+
detach();
|
|
2166
|
+
}
|
|
2167
|
+
},
|
|
2168
|
+
{ highWaterMark: 0 }
|
|
2169
|
+
);
|
|
2170
|
+
}
|
|
2171
|
+
function toWebRequest(req, res, fallbackHost) {
|
|
2172
|
+
const target2 = req.url ?? "/";
|
|
2173
|
+
const url2 = new URL(`http://${req.headers.host ?? fallbackHost}${target2.startsWith("/") ? target2 : `/${target2}`}`);
|
|
2174
|
+
const headers = new Headers();
|
|
2175
|
+
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
2176
|
+
const name = req.rawHeaders[i];
|
|
2177
|
+
if (!name.startsWith(":")) headers.append(name, req.rawHeaders[i + 1]);
|
|
2178
|
+
}
|
|
2179
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
2180
|
+
const controller = new AbortController();
|
|
2181
|
+
res.once("close", () => {
|
|
2182
|
+
if (!res.writableFinished) controller.abort();
|
|
2183
|
+
});
|
|
2184
|
+
const init = { method, headers, signal: controller.signal };
|
|
2185
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
2186
|
+
init.body = requestBody(req);
|
|
2187
|
+
init.duplex = "half";
|
|
2188
|
+
}
|
|
2189
|
+
return new Request(url2, init);
|
|
2190
|
+
}
|
|
2191
|
+
async function writeResponse(res, response2) {
|
|
2192
|
+
res.statusCode = response2.status;
|
|
2193
|
+
if (response2.statusText !== "") res.statusMessage = response2.statusText;
|
|
2194
|
+
response2.headers.forEach((value, name) => {
|
|
2195
|
+
if (name !== "set-cookie") res.setHeader(name, value);
|
|
2196
|
+
});
|
|
2197
|
+
const cookies = response2.headers.getSetCookie();
|
|
2198
|
+
if (cookies.length > 0) res.setHeader("set-cookie", cookies);
|
|
2199
|
+
if (response2.body === null) {
|
|
2200
|
+
res.end();
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2203
|
+
await pipeline(Readable3.fromWeb(response2.body), res);
|
|
2204
|
+
}
|
|
2205
|
+
async function serveNode(app, options) {
|
|
2206
|
+
const fallbackHost = `${displayHost(options.hostname)}:${options.port}`;
|
|
2207
|
+
const server = createServer((req, res) => {
|
|
2208
|
+
void (async () => {
|
|
2209
|
+
let request2;
|
|
2210
|
+
try {
|
|
2211
|
+
request2 = toWebRequest(req, res, fallbackHost);
|
|
2212
|
+
} catch {
|
|
2213
|
+
res.statusCode = 400;
|
|
2214
|
+
res.end("Bad Request");
|
|
2215
|
+
return;
|
|
2216
|
+
}
|
|
2217
|
+
setClientAddress(request2, req.socket.remoteAddress);
|
|
2218
|
+
const response2 = await handleSafely(app, request2, options.publicPath);
|
|
2219
|
+
try {
|
|
2220
|
+
await writeResponse(res, response2);
|
|
2221
|
+
} catch {
|
|
2222
|
+
res.destroy();
|
|
2223
|
+
}
|
|
2224
|
+
await terminateRequest(app, request2, response2);
|
|
2225
|
+
})();
|
|
2226
|
+
});
|
|
2227
|
+
await new Promise((resolve2, reject) => {
|
|
2228
|
+
server.once("error", reject);
|
|
2229
|
+
server.listen(options.port, options.hostname, () => {
|
|
2230
|
+
server.off("error", reject);
|
|
2231
|
+
resolve2();
|
|
2232
|
+
});
|
|
2233
|
+
});
|
|
2234
|
+
const address = server.address();
|
|
2235
|
+
const closed = new Promise((resolve2) => server.once("close", () => resolve2()));
|
|
2236
|
+
let closing;
|
|
2237
|
+
const close = () => closing ??= (async () => {
|
|
2238
|
+
const done = new Promise((resolve2) => server.close(() => resolve2()));
|
|
2239
|
+
server.closeIdleConnections();
|
|
2240
|
+
const idle = setInterval(() => server.closeIdleConnections(), 50);
|
|
2241
|
+
const force = setTimeout(() => server.closeAllConnections(), options.shutdownTimeout);
|
|
2242
|
+
await done;
|
|
2243
|
+
clearInterval(idle);
|
|
2244
|
+
clearTimeout(force);
|
|
2245
|
+
})();
|
|
2246
|
+
if (options.signals) exitOnSignals(close, closed);
|
|
2247
|
+
return {
|
|
2248
|
+
runtime: "node",
|
|
2249
|
+
hostname: options.hostname,
|
|
2250
|
+
port: address.port,
|
|
2251
|
+
url: `http://${displayHost(options.hostname)}:${address.port}`,
|
|
2252
|
+
close,
|
|
2253
|
+
closed
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
function detectRuntime() {
|
|
2257
|
+
return bunRuntime() !== void 0 ? "bun" : "node";
|
|
2258
|
+
}
|
|
2259
|
+
async function serve(app, options = {}) {
|
|
2260
|
+
const resolved = {
|
|
2261
|
+
port: options.port ?? Number(process.env.PORT ?? process.env.SERVER_PORT ?? 8e3),
|
|
2262
|
+
hostname: options.hostname ?? "127.0.0.1",
|
|
2263
|
+
signals: options.signals ?? true,
|
|
2264
|
+
shutdownTimeout: options.shutdownTimeout ?? 1e4,
|
|
2265
|
+
...options.publicPath !== false && { publicPath: options.publicPath ?? app.publicPath() }
|
|
2266
|
+
};
|
|
2267
|
+
await app.boot();
|
|
2268
|
+
const runtime = options.runtime ?? detectRuntime();
|
|
2269
|
+
const server = runtime === "bun" ? await serveBun(app, resolved) : await serveNode(app, resolved);
|
|
2270
|
+
const onListen = options.onListen ?? ((running) => console.log(`Server running on [${running.url}].`));
|
|
2271
|
+
onListen(server);
|
|
2272
|
+
return server;
|
|
2273
|
+
}
|
|
2274
|
+
function io(command) {
|
|
2275
|
+
return {
|
|
2276
|
+
option: (name) => {
|
|
2277
|
+
const value = command.option(name);
|
|
2278
|
+
return value === void 0 || value === null || value === false || value === "" ? void 0 : String(value);
|
|
2279
|
+
},
|
|
2280
|
+
flag: (name) => {
|
|
2281
|
+
const value = command.option(name);
|
|
2282
|
+
return value === true || value === "true" || value === "1";
|
|
2283
|
+
},
|
|
2284
|
+
info: (message) => command.info(message),
|
|
2285
|
+
line: (message) => command.line(message),
|
|
2286
|
+
warn: (message) => command.warn(message),
|
|
2287
|
+
error: (message) => command.error(message)
|
|
2288
|
+
};
|
|
2289
|
+
}
|
|
2290
|
+
var middlewareName = (reference) => typeof reference === "string" ? reference : reference.name;
|
|
2291
|
+
function routeListRows(routes) {
|
|
2292
|
+
const rows = routes.map((route2) => {
|
|
2293
|
+
const listable = route2;
|
|
2294
|
+
return {
|
|
2295
|
+
method: route2.methods.join("|"),
|
|
2296
|
+
uri: route2.uri,
|
|
2297
|
+
name: route2.getName() ?? null,
|
|
2298
|
+
action: typeof listable.getActionName === "function" ? listable.getActionName() : "Closure",
|
|
2299
|
+
middleware: typeof listable.gatherMiddleware === "function" ? listable.gatherMiddleware().map(middlewareName) : []
|
|
2300
|
+
};
|
|
2301
|
+
});
|
|
2302
|
+
return rows.sort((a, b) => a.uri < b.uri ? -1 : a.uri > b.uri ? 1 : 0);
|
|
2303
|
+
}
|
|
2304
|
+
function filterRouteList(rows, filters) {
|
|
2305
|
+
const excluded = (filters.exceptPath ?? "").split(",").filter((path) => path !== "");
|
|
2306
|
+
return rows.filter((row) => {
|
|
2307
|
+
if (filters.name !== void 0 && !(row.name ?? "").includes(filters.name)) return false;
|
|
2308
|
+
if (filters.path !== void 0 && !row.uri.includes(filters.path)) return false;
|
|
2309
|
+
if (filters.method !== void 0 && !row.method.includes(filters.method.toUpperCase())) return false;
|
|
2310
|
+
return !excluded.some((path) => row.uri.includes(path));
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
function formatRouteList(rows, width) {
|
|
2314
|
+
const methodWidth = Math.max(...rows.map((row) => row.method.length));
|
|
2315
|
+
const lines = rows.map((row) => {
|
|
2316
|
+
const spaces = " ".repeat(Math.max(methodWidth + 6 - row.method.length, 0));
|
|
2317
|
+
const action = row.action === "Closure" ? row.name ?? "" : row.name === null ? row.action : `${row.name} \u203A ${row.action}`;
|
|
2318
|
+
const used = row.method.length + spaces.length + row.uri.length + action.length + 6 + (action === "" ? 0 : 1);
|
|
2319
|
+
const dots = ".".repeat(Math.max(width - used, 0));
|
|
2320
|
+
return ` ${row.method} ${spaces}${row.uri}${dots === "" ? "" : ` ${dots}`}${action === "" ? "" : ` ${action}`}`;
|
|
2321
|
+
});
|
|
2322
|
+
const count = `Showing [${rows.length}] ${rows.length === 1 ? "route" : "routes"}`;
|
|
2323
|
+
return [...lines, "", `${" ".repeat(Math.max(width - count.length - 2, 0))}${count}`];
|
|
2324
|
+
}
|
|
2325
|
+
var API_ROUTES_STUB = `import { request, Route } from '@orieljs/framework'
|
|
2326
|
+
|
|
2327
|
+
Route.get('/user', () => request().user()).middleware('auth:token')
|
|
2328
|
+
`;
|
|
2329
|
+
function addApiRouting(source) {
|
|
2330
|
+
const code = source.replace(/^[ \t]*\/\/.*$/gm, "");
|
|
2331
|
+
if (/withRouting\(\s*\{[^}]*\bapi\s*:/.test(code)) return source;
|
|
2332
|
+
const commented = /^([ \t]*)\/\/\s*(api:\s*['"`][^'"`\n]*['"`],?)[ \t]*$/m.exec(source);
|
|
2333
|
+
if (commented !== null) return source.replace(commented[0], () => `${commented[1]}${commented[2]}`);
|
|
2334
|
+
const web = /^([ \t]*)web:\s*(['"`])[^'"`\n]*\2[ \t]*,?[ \t]*$/m.exec(source);
|
|
2335
|
+
if (web !== null) {
|
|
2336
|
+
const line = web[0].trimEnd();
|
|
2337
|
+
return source.replace(web[0], () => `${line.endsWith(",") ? line : `${line},`}
|
|
2338
|
+
${web[1]}api: 'routes/api.ts',`);
|
|
2339
|
+
}
|
|
2340
|
+
const inline = /withRouting\(\s*\{/.exec(source);
|
|
2341
|
+
if (inline !== null) return source.replace(inline[0], () => `${inline[0]} api: 'routes/api.ts',`);
|
|
2342
|
+
const create = /^([ \t]*)\.create\(\)/m.exec(source);
|
|
2343
|
+
if (create !== null) return source.replace(create[0], () => `${create[1]}.withRouting({ api: 'routes/api.ts' })
|
|
2344
|
+
${create[0]}`);
|
|
2345
|
+
return void 0;
|
|
2346
|
+
}
|
|
2347
|
+
function registerHttpCommands(kernel, app, options = {}) {
|
|
2348
|
+
const serveApp = options.serve ?? serve;
|
|
2349
|
+
const terminalWidth = options.terminalWidth ?? (() => process.stdout.columns ?? 120);
|
|
2350
|
+
kernel.command(
|
|
2351
|
+
"serve {--host=127.0.0.1 : The host address to serve the application on} {--port= : The port to serve the application on}",
|
|
2352
|
+
async function() {
|
|
2353
|
+
const out = io(this);
|
|
2354
|
+
const port = out.option("port");
|
|
2355
|
+
if (port !== void 0 && !/^\d+$/.test(port)) {
|
|
2356
|
+
out.error("The --port option must be a number.");
|
|
2357
|
+
return 1;
|
|
2358
|
+
}
|
|
2359
|
+
const server = await serveApp(app, {
|
|
2360
|
+
hostname: out.option("host") ?? "127.0.0.1",
|
|
2361
|
+
...port !== void 0 && { port: Number(port) },
|
|
2362
|
+
onListen: (running) => {
|
|
2363
|
+
out.info(`Server running on [${running.url}].`);
|
|
2364
|
+
out.line("Press Ctrl+C to stop the server");
|
|
2365
|
+
}
|
|
2366
|
+
});
|
|
2367
|
+
await server.closed;
|
|
2368
|
+
return 0;
|
|
2369
|
+
}
|
|
2370
|
+
).purpose("Serve the application on the Oriel development server");
|
|
2371
|
+
kernel.command(
|
|
2372
|
+
"route:list {--method= : Filter the routes by method} {--name= : Filter the routes by name} {--path= : Only show routes matching the given path pattern} {--except-path= : Do not display the routes matching the given path pattern} {--reverse : Reverse the ordering of the routes} {--json : Output the route list as JSON}",
|
|
2373
|
+
function() {
|
|
2374
|
+
const out = io(this);
|
|
2375
|
+
if (!app.bound(Router)) {
|
|
2376
|
+
out.error("The router is not available. Is the Routing service provider registered?");
|
|
2377
|
+
return 1;
|
|
2378
|
+
}
|
|
2379
|
+
const all = routeListRows(app.make(Router).getRoutes());
|
|
2380
|
+
if (all.length === 0) {
|
|
2381
|
+
out.error("Your application doesn't have any routes.");
|
|
2382
|
+
return 0;
|
|
2383
|
+
}
|
|
2384
|
+
const rows = filterRouteList(all, {
|
|
2385
|
+
...out.option("method") !== void 0 && { method: out.option("method") },
|
|
2386
|
+
...out.option("name") !== void 0 && { name: out.option("name") },
|
|
2387
|
+
...out.option("path") !== void 0 && { path: out.option("path") },
|
|
2388
|
+
...out.option("except-path") !== void 0 && { exceptPath: out.option("except-path") }
|
|
2389
|
+
});
|
|
2390
|
+
if (rows.length === 0) {
|
|
2391
|
+
out.error("Your application doesn't have any routes matching the given criteria.");
|
|
2392
|
+
return 0;
|
|
2393
|
+
}
|
|
2394
|
+
if (out.flag("reverse")) rows.reverse();
|
|
2395
|
+
if (out.flag("json")) {
|
|
2396
|
+
out.line(JSON.stringify(rows));
|
|
2397
|
+
return 0;
|
|
2398
|
+
}
|
|
2399
|
+
for (const line of formatRouteList(rows, terminalWidth())) out.line(line);
|
|
2400
|
+
return 0;
|
|
2401
|
+
}
|
|
2402
|
+
).purpose("List all registered routes");
|
|
2403
|
+
kernel.command("install:api {--force : Overwrite any existing API routes file}", async function() {
|
|
2404
|
+
const out = io(this);
|
|
2405
|
+
const routesFile = app.basePath("routes/api.ts");
|
|
2406
|
+
if (existsSync4(routesFile) && !out.flag("force")) {
|
|
2407
|
+
out.error("API routes file already exists.");
|
|
2408
|
+
} else {
|
|
2409
|
+
await mkdir(dirname(routesFile), { recursive: true });
|
|
2410
|
+
await writeFile(routesFile, API_ROUTES_STUB);
|
|
2411
|
+
out.info("Published API routes file.");
|
|
2412
|
+
}
|
|
2413
|
+
const bootstrapFile = app.bootstrapPath("app.ts");
|
|
2414
|
+
const source = existsSync4(bootstrapFile) ? await readFile2(bootstrapFile, "utf8") : void 0;
|
|
2415
|
+
const updated = source === void 0 ? void 0 : addApiRouting(source);
|
|
2416
|
+
if (updated === void 0) {
|
|
2417
|
+
out.warn(
|
|
2418
|
+
"Unable to automatically add API route definition to bootstrap file. Add api: 'routes/api.ts' to withRouting() in bootstrap/app.ts."
|
|
2419
|
+
);
|
|
2420
|
+
} else if (updated !== source) {
|
|
2421
|
+
await writeFile(bootstrapFile, updated);
|
|
2422
|
+
}
|
|
2423
|
+
out.info("API scaffolding installed. Run [oriel migrate] to create the personal access tokens table.");
|
|
2424
|
+
return 0;
|
|
2425
|
+
}).purpose("Create an API routes file and prepare the application for API authentication");
|
|
2426
|
+
}
|
|
2427
|
+
var HttpServiceProvider = class extends ServiceProvider {
|
|
2428
|
+
register() {
|
|
2429
|
+
this.mergeConfigFrom(
|
|
2430
|
+
{
|
|
2431
|
+
trusted_proxies: [],
|
|
2432
|
+
max_body_size: 8 * 1024 * 1024,
|
|
2433
|
+
max_json_depth: 512,
|
|
2434
|
+
csrf_except: [],
|
|
2435
|
+
redirect_guests_to: void 0,
|
|
2436
|
+
redirect_users_to: void 0
|
|
2437
|
+
},
|
|
2438
|
+
"http"
|
|
2439
|
+
);
|
|
2440
|
+
this.app.singleton(HttpKernel, () => new Kernel(this.app));
|
|
2441
|
+
this.app.singleton(ExceptionHandler, () => {
|
|
2442
|
+
const handler = new ErrorHandler(this.app);
|
|
2443
|
+
applyExceptionCallbacks(this.app, handler);
|
|
2444
|
+
return handler;
|
|
2445
|
+
});
|
|
2446
|
+
this.app.singleton(ResponseFactory, () => new HttpResponseFactory());
|
|
2447
|
+
this.app.singleton(Redirector, () => new HttpRedirector());
|
|
2448
|
+
}
|
|
2449
|
+
boot() {
|
|
2450
|
+
this.app.booted(() => {
|
|
2451
|
+
const kernel = this.app.make(HttpKernel);
|
|
2452
|
+
if (kernel instanceof Kernel && this.app.bound(Router)) applyMiddlewareCallbacks(this.app, kernel);
|
|
2453
|
+
});
|
|
2454
|
+
if (this.app.bound(ConsoleKernel)) registerHttpCommands(this.app.make(ConsoleKernel), this.app);
|
|
2455
|
+
}
|
|
2456
|
+
};
|
|
2457
|
+
|
|
2458
|
+
// ../Live/dist/index.js
|
|
2459
|
+
import { AssertionError } from "assert";
|
|
2460
|
+
import { isDeepStrictEqual } from "util";
|
|
2461
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2462
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
2463
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
2464
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
|
|
2465
|
+
import { join as join5, relative as relative2, sep as sep2 } from "path";
|
|
2466
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2467
|
+
import { existsSync as existsSync22 } from "fs";
|
|
2468
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
2469
|
+
import { dirname as dirname2, join as join23, relative as relative22, sep as sep22 } from "path";
|
|
2470
|
+
Symbol.metadata ??= /* @__PURE__ */ Symbol.for("Symbol.metadata");
|
|
2471
|
+
var LISTENERS = /* @__PURE__ */ Symbol.for("oriel:live.listeners");
|
|
2472
|
+
var LOCKED = /* @__PURE__ */ Symbol.for("oriel:live.locked");
|
|
2473
|
+
var COMPUTED = /* @__PURE__ */ Symbol.for("oriel:live.computed");
|
|
2474
|
+
function own(metadata, key, empty2, copy) {
|
|
2475
|
+
if (!Object.hasOwn(metadata, key)) metadata[key] = metadata[key] === void 0 ? empty2() : copy(metadata[key]);
|
|
2476
|
+
return metadata[key];
|
|
2477
|
+
}
|
|
2478
|
+
function On(event) {
|
|
2479
|
+
return function(_method, context) {
|
|
2480
|
+
const listeners = own(
|
|
2481
|
+
context.metadata,
|
|
2482
|
+
LISTENERS,
|
|
2483
|
+
() => ({}),
|
|
2484
|
+
(inherited) => Object.fromEntries(Object.entries(inherited).map(([name, methods2]) => [name, [...methods2]]))
|
|
2485
|
+
);
|
|
2486
|
+
const methods = listeners[event] ??= [];
|
|
2487
|
+
if (!methods.includes(String(context.name))) methods.push(String(context.name));
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
function Locked(_target, context) {
|
|
2491
|
+
own(context.metadata, LOCKED, () => [], (inherited) => [...inherited]).push(String(context.name));
|
|
2492
|
+
}
|
|
2493
|
+
var computedCache = /* @__PURE__ */ new WeakMap();
|
|
2494
|
+
function cachedGetter(name, getter) {
|
|
2495
|
+
return function() {
|
|
2496
|
+
let cache = computedCache.get(this);
|
|
2497
|
+
if (cache === void 0) computedCache.set(this, cache = /* @__PURE__ */ new Map());
|
|
2498
|
+
if (!cache.has(name)) cache.set(name, getter.call(this));
|
|
2499
|
+
return cache.get(name);
|
|
2500
|
+
};
|
|
2501
|
+
}
|
|
2502
|
+
function Computed(getter, context) {
|
|
2503
|
+
own(context.metadata, COMPUTED, () => [], (inherited) => [...inherited]).push(String(context.name));
|
|
2504
|
+
return cachedGetter(String(context.name), getter);
|
|
2505
|
+
}
|
|
2506
|
+
function forgetComputed(instance) {
|
|
2507
|
+
computedCache.delete(instance);
|
|
2508
|
+
}
|
|
2509
|
+
function metadataOf(cls) {
|
|
2510
|
+
return cls[Symbol.metadata] ?? {};
|
|
2511
|
+
}
|
|
2512
|
+
function chainOf(cls) {
|
|
2513
|
+
const chain = [];
|
|
2514
|
+
for (let c = cls; c !== null && c !== Function.prototype; c = Object.getPrototypeOf(c)) {
|
|
2515
|
+
chain.unshift(c);
|
|
2516
|
+
}
|
|
2517
|
+
return chain;
|
|
2518
|
+
}
|
|
2519
|
+
function staticsOf(cls, key) {
|
|
2520
|
+
return chainOf(cls).flatMap((c) => Object.hasOwn(c, key) && c[key] !== void 0 ? [c[key]] : []);
|
|
2521
|
+
}
|
|
2522
|
+
function listenersOf(cls) {
|
|
2523
|
+
const result = {};
|
|
2524
|
+
for (const [event, methods] of Object.entries(metadataOf(cls)[LISTENERS] ?? {})) {
|
|
2525
|
+
result[event] = [...methods];
|
|
2526
|
+
}
|
|
2527
|
+
for (const listeners of staticsOf(cls, "listeners")) {
|
|
2528
|
+
for (const [event, method] of Object.entries(listeners)) {
|
|
2529
|
+
const methods = result[event] ??= [];
|
|
2530
|
+
if (!methods.includes(method)) methods.push(method);
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
return result;
|
|
2534
|
+
}
|
|
2535
|
+
function lockedOf(cls) {
|
|
2536
|
+
return /* @__PURE__ */ new Set([...metadataOf(cls)[LOCKED] ?? [], ...staticsOf(cls, "locked").flat()]);
|
|
2537
|
+
}
|
|
2538
|
+
function computedOf(cls) {
|
|
2539
|
+
return /* @__PURE__ */ new Set([...metadataOf(cls)[COMPUTED] ?? [], ...staticsOf(cls, "computed").flat()]);
|
|
2540
|
+
}
|
|
2541
|
+
var COMPONENT = /* @__PURE__ */ Symbol.for("oriel:live.component");
|
|
2542
|
+
var states = /* @__PURE__ */ new WeakMap();
|
|
2543
|
+
function stateOf(component) {
|
|
2544
|
+
let state = states.get(component);
|
|
2545
|
+
if (state === void 0) states.set(component, state = { id: "", name: "", errors: {}, dispatches: [], skipRender: false });
|
|
2546
|
+
return state;
|
|
2547
|
+
}
|
|
2548
|
+
function isComponentClass(value) {
|
|
2549
|
+
return typeof value === "function" && value[COMPONENT] === true;
|
|
2550
|
+
}
|
|
2551
|
+
var ErrorBag = class {
|
|
2552
|
+
constructor(messages) {
|
|
2553
|
+
this.messages = messages;
|
|
2554
|
+
}
|
|
2555
|
+
messages;
|
|
2556
|
+
has(key) {
|
|
2557
|
+
return (this.messages[key]?.length ?? 0) > 0;
|
|
2558
|
+
}
|
|
2559
|
+
first(key) {
|
|
2560
|
+
return key === void 0 ? this.all()[0] : this.messages[key]?.[0];
|
|
2561
|
+
}
|
|
2562
|
+
get(key) {
|
|
2563
|
+
return [...this.messages[key] ?? []];
|
|
2564
|
+
}
|
|
2565
|
+
all() {
|
|
2566
|
+
return Object.values(this.messages).flat();
|
|
2567
|
+
}
|
|
2568
|
+
any() {
|
|
2569
|
+
return !this.isEmpty();
|
|
2570
|
+
}
|
|
2571
|
+
isEmpty() {
|
|
2572
|
+
return this.count() === 0;
|
|
2573
|
+
}
|
|
2574
|
+
count() {
|
|
2575
|
+
return this.all().length;
|
|
2576
|
+
}
|
|
2577
|
+
toObject() {
|
|
2578
|
+
return Object.fromEntries(Object.entries(this.messages).map(([key, messages]) => [key, [...messages]]));
|
|
2579
|
+
}
|
|
2580
|
+
};
|
|
2581
|
+
var PendingEvent = class {
|
|
2582
|
+
constructor(event) {
|
|
2583
|
+
this.event = event;
|
|
2584
|
+
}
|
|
2585
|
+
event;
|
|
2586
|
+
/** Only this component's own listeners receive the event. */
|
|
2587
|
+
self() {
|
|
2588
|
+
this.event.self = true;
|
|
2589
|
+
delete this.event.to;
|
|
2590
|
+
return this;
|
|
2591
|
+
}
|
|
2592
|
+
/** Only components of this class (or registered name) receive the event. */
|
|
2593
|
+
to(component) {
|
|
2594
|
+
this.event.to = component;
|
|
2595
|
+
delete this.event.self;
|
|
2596
|
+
return this;
|
|
2597
|
+
}
|
|
2598
|
+
};
|
|
2599
|
+
var Component = class {
|
|
2600
|
+
static [COMPONENT] = true;
|
|
2601
|
+
/** The view that wraps a full-page component (default: config `live.layout`). */
|
|
2602
|
+
static layout;
|
|
2603
|
+
/** The page title passed to the layout as `title`. */
|
|
2604
|
+
static title;
|
|
2605
|
+
/** Static fallbacks for runtimes without decorators: { event: 'method' }, locked names, computed getter names. */
|
|
2606
|
+
static listeners;
|
|
2607
|
+
static locked;
|
|
2608
|
+
static computed;
|
|
2609
|
+
getId() {
|
|
2610
|
+
return stateOf(this).id;
|
|
2611
|
+
}
|
|
2612
|
+
getName() {
|
|
2613
|
+
return stateOf(this).name;
|
|
2614
|
+
}
|
|
2615
|
+
/** Dispatches a browser event; every component listening for it (@On) runs its handler. */
|
|
2616
|
+
dispatch(name, params = {}) {
|
|
2617
|
+
const event = { name, params };
|
|
2618
|
+
stateOf(this).dispatches.push(event);
|
|
2619
|
+
return new PendingEvent(event);
|
|
2620
|
+
}
|
|
2621
|
+
/** Sends the browser to `url` after this request; `navigate` uses o:navigate instead of a full page load. */
|
|
2622
|
+
redirect(url2, options = {}) {
|
|
2623
|
+
const state = stateOf(this);
|
|
2624
|
+
state.redirect = url2;
|
|
2625
|
+
if (options.navigate === true) state.redirectUsingNavigate = true;
|
|
2626
|
+
else delete state.redirectUsingNavigate;
|
|
2627
|
+
}
|
|
2628
|
+
redirectRoute(name, parameters, options = {}) {
|
|
2629
|
+
this.redirect(inject(UrlGenerator).route(name, parameters), options);
|
|
2630
|
+
}
|
|
2631
|
+
/**
|
|
2632
|
+
* Validates the public state. On success clears the error bag and returns the validated data; on failure fills the
|
|
2633
|
+
* error bag and throws (the rest of the action is skipped and the component re-renders with `errors`).
|
|
2634
|
+
*/
|
|
2635
|
+
async validate(rules, messages, attributes) {
|
|
2636
|
+
const validator = inject(ValidationFactory).make(Object.fromEntries(Object.entries(this)), rules, messages, attributes);
|
|
2637
|
+
try {
|
|
2638
|
+
const data = await validator.validated();
|
|
2639
|
+
stateOf(this).errors = {};
|
|
2640
|
+
return data;
|
|
2641
|
+
} catch (error) {
|
|
2642
|
+
const errors = error.errors;
|
|
2643
|
+
if (error.kind === "Validation" && errors !== void 0) stateOf(this).errors = { ...errors };
|
|
2644
|
+
throw error;
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
addError(key, message) {
|
|
2648
|
+
;
|
|
2649
|
+
(stateOf(this).errors[key] ??= []).push(message);
|
|
2650
|
+
}
|
|
2651
|
+
/** Clears every error, or only those of `keys`. */
|
|
2652
|
+
resetErrorBag(...keys) {
|
|
2653
|
+
const state = stateOf(this);
|
|
2654
|
+
if (keys.length === 0) state.errors = {};
|
|
2655
|
+
else for (const key of keys) delete state.errors[key];
|
|
2656
|
+
}
|
|
2657
|
+
get errors() {
|
|
2658
|
+
return new ErrorBag(stateOf(this).errors);
|
|
2659
|
+
}
|
|
2660
|
+
/** Puts public properties (all of them when none are named) back to their initial values. */
|
|
2661
|
+
reset(...properties) {
|
|
2662
|
+
const fresh = new this.constructor();
|
|
2663
|
+
const names = properties.length > 0 ? properties : Object.keys(this);
|
|
2664
|
+
for (const name of names) if (Object.hasOwn(fresh, name)) this[name] = fresh[name];
|
|
2665
|
+
forgetComputed(this);
|
|
2666
|
+
}
|
|
2667
|
+
/** Keeps the current HTML: the response carries the new state but no `html` effect. */
|
|
2668
|
+
skipRender() {
|
|
2669
|
+
stateOf(this).skipRender = true;
|
|
2670
|
+
}
|
|
2671
|
+
/** A full-page component as a route action: Route.get('/counter', Counter). Route parameters go to mount(). */
|
|
2672
|
+
async __invoke() {
|
|
2673
|
+
return inject(LiveManager).page(this);
|
|
2674
|
+
}
|
|
2675
|
+
};
|
|
2676
|
+
var LiveError = class extends Error {
|
|
2677
|
+
name = "LiveError";
|
|
2678
|
+
kind = "Server";
|
|
2679
|
+
status = 500;
|
|
2680
|
+
};
|
|
2681
|
+
var CorruptComponentPayloadError = class extends LiveError {
|
|
2682
|
+
name = "CorruptComponentPayloadError";
|
|
2683
|
+
kind = "Authorization";
|
|
2684
|
+
status = 403;
|
|
2685
|
+
constructor(component = "unknown") {
|
|
2686
|
+
super(`Live encountered corrupt data when trying to hydrate the [${component}] component.`);
|
|
2687
|
+
}
|
|
2688
|
+
};
|
|
2689
|
+
var MethodNotCallableError = class extends LiveError {
|
|
2690
|
+
name = "MethodNotCallableError";
|
|
2691
|
+
kind = "Authorization";
|
|
2692
|
+
status = 403;
|
|
2693
|
+
constructor(method, component) {
|
|
2694
|
+
super(`Unable to call component method. Public method [${method}] not found on component [${component}].`);
|
|
2695
|
+
}
|
|
2696
|
+
};
|
|
2697
|
+
var PropertyNotUpdatableError = class extends LiveError {
|
|
2698
|
+
name = "PropertyNotUpdatableError";
|
|
2699
|
+
kind = "Authorization";
|
|
2700
|
+
status = 403;
|
|
2701
|
+
constructor(path, component) {
|
|
2702
|
+
super(`Cannot update property [${path}] on component [${component}]: it is not a public, unlocked property.`);
|
|
2703
|
+
}
|
|
2704
|
+
};
|
|
2705
|
+
var LiveComponentNotFoundError = class extends LiveError {
|
|
2706
|
+
name = "LiveComponentNotFoundError";
|
|
2707
|
+
kind = "Http";
|
|
2708
|
+
status = 404;
|
|
2709
|
+
constructor(component) {
|
|
2710
|
+
super(`Unable to find component: [${component}].`);
|
|
2711
|
+
}
|
|
2712
|
+
};
|
|
2713
|
+
var InvalidLiveRequestError = class extends LiveError {
|
|
2714
|
+
name = "InvalidLiveRequestError";
|
|
2715
|
+
kind = "Http";
|
|
2716
|
+
status = 400;
|
|
2717
|
+
};
|
|
2718
|
+
var ESCAPES = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
2719
|
+
function escapeAttribute2(value) {
|
|
2720
|
+
return value.replace(/[&<>"']/g, (char) => ESCAPES[char]);
|
|
2721
|
+
}
|
|
2722
|
+
var VOID = /* @__PURE__ */ new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr"]);
|
|
2723
|
+
var RAW_TEXT = /* @__PURE__ */ new Set(["script", "style", "textarea", "title"]);
|
|
2724
|
+
var TOKEN = /<\/([A-Za-z][\w:-]*)\s*>|<([A-Za-z][\w:-]*)(?:\s+[^\s"'<>/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*\s*(\/?)>/g;
|
|
2725
|
+
function rootTag(html, component) {
|
|
2726
|
+
const fail22 = () => {
|
|
2727
|
+
throw new LiveError(`Live only supports one HTML element per component. Multiple root elements were detected for component [${component}].`);
|
|
2728
|
+
};
|
|
2729
|
+
const source = html.replace(/<!--[\s\S]*?-->/g, "").trim();
|
|
2730
|
+
let depth = 0;
|
|
2731
|
+
let root;
|
|
2732
|
+
let rootEnd = -1;
|
|
2733
|
+
TOKEN.lastIndex = 0;
|
|
2734
|
+
for (let match = TOKEN.exec(source); match !== null; match = TOKEN.exec(source)) {
|
|
2735
|
+
const [token2, closing, opening, selfClosing] = match;
|
|
2736
|
+
if (root === void 0 && match.index !== 0) fail22();
|
|
2737
|
+
if (depth === 0 && root !== void 0) fail22();
|
|
2738
|
+
if (opening !== void 0) {
|
|
2739
|
+
const tag = opening.toLowerCase();
|
|
2740
|
+
root ??= tag;
|
|
2741
|
+
if (selfClosing === "/" || VOID.has(tag)) {
|
|
2742
|
+
if (depth === 0) rootEnd = match.index + token2.length;
|
|
2743
|
+
continue;
|
|
2744
|
+
}
|
|
2745
|
+
if (RAW_TEXT.has(tag)) {
|
|
2746
|
+
const closer = new RegExp(`</${tag}`, "gi");
|
|
2747
|
+
closer.lastIndex = TOKEN.lastIndex;
|
|
2748
|
+
const end = closer.exec(source);
|
|
2749
|
+
if (end !== null) TOKEN.lastIndex = end.index;
|
|
2750
|
+
}
|
|
2751
|
+
depth++;
|
|
2752
|
+
} else if (closing !== void 0) {
|
|
2753
|
+
if (--depth < 0) fail22();
|
|
2754
|
+
if (depth === 0) rootEnd = match.index + token2.length;
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
if (root === void 0 || depth !== 0 || source.slice(rootEnd).trim() !== "") fail22();
|
|
2758
|
+
return root;
|
|
2759
|
+
}
|
|
2760
|
+
function insertAttributesIntoRoot(html, attributes) {
|
|
2761
|
+
const source = html.trim();
|
|
2762
|
+
const match = /^((?:<!--[\s\S]*?-->\s*)*<[A-Za-z][\w:-]*)/.exec(source);
|
|
2763
|
+
if (match === null) throw new LiveError("Live could not find the root element of the component.");
|
|
2764
|
+
const rendered = Object.entries(attributes).map(([name, value]) => ` ${name}="${escapeAttribute2(value)}"`).join("");
|
|
2765
|
+
return match[1] + rendered + source.slice(match[1].length);
|
|
2766
|
+
}
|
|
2767
|
+
var fail = (message, actual, expected) => {
|
|
2768
|
+
throw new AssertionError({ message, actual, expected, operator: "live" });
|
|
2769
|
+
};
|
|
2770
|
+
var getPath = (target2, path) => path.split(".").reduce((node, segment) => typeof node === "object" && node !== null ? node[segment] : void 0, target2);
|
|
2771
|
+
var TestRequestScope = token("Testing.RequestScope");
|
|
2772
|
+
function requestScope2() {
|
|
2773
|
+
const container = currentContainer() ?? Container.getInstance();
|
|
2774
|
+
return container?.bound(TestRequestScope) === true ? container.make(TestRequestScope) : (run) => run();
|
|
2775
|
+
}
|
|
2776
|
+
var Testable = class _Testable {
|
|
2777
|
+
constructor(manager, scope, result) {
|
|
2778
|
+
this.manager = manager;
|
|
2779
|
+
this.scope = scope;
|
|
2780
|
+
this.#snapshot = JSON.stringify(result.snapshot);
|
|
2781
|
+
this.#html = result.html ?? "";
|
|
2782
|
+
this.#effects = result.effects;
|
|
2783
|
+
this.#instance = result.instance;
|
|
485
2784
|
}
|
|
486
|
-
|
|
487
|
-
|
|
2785
|
+
manager;
|
|
2786
|
+
scope;
|
|
2787
|
+
#snapshot;
|
|
2788
|
+
#html;
|
|
2789
|
+
#effects;
|
|
2790
|
+
#instance;
|
|
2791
|
+
/** Mounts `component` with `params`, as `<Live component={…} {...params} />` would. */
|
|
2792
|
+
static async create(component, params = {}) {
|
|
2793
|
+
const scope = requestScope2();
|
|
2794
|
+
const manager = inject(LiveManager);
|
|
2795
|
+
return new _Testable(manager, scope, await scope(() => manager.mountComponent(component, params)));
|
|
2796
|
+
}
|
|
2797
|
+
/** The component instance after the last request. */
|
|
2798
|
+
instance() {
|
|
2799
|
+
return this.#instance;
|
|
2800
|
+
}
|
|
2801
|
+
/** A public property (dotted paths reach into arrays and objects). */
|
|
2802
|
+
get(path) {
|
|
2803
|
+
return getPath(this.#instance, path);
|
|
488
2804
|
}
|
|
489
|
-
|
|
490
|
-
|
|
2805
|
+
/** The last rendered HTML, without the o:id, o:snapshot and o:effects attributes (Livewire strips them too). */
|
|
2806
|
+
html() {
|
|
2807
|
+
return this.#html.replace(/ o:(id|snapshot|effects)="[^"]*"/g, "");
|
|
491
2808
|
}
|
|
492
|
-
|
|
493
|
-
|
|
2809
|
+
/** The effects of the last request. */
|
|
2810
|
+
effects() {
|
|
2811
|
+
return this.#effects;
|
|
494
2812
|
}
|
|
495
|
-
|
|
496
|
-
return this
|
|
2813
|
+
async set(path, value) {
|
|
2814
|
+
return this.#update(typeof path === "string" ? { [path]: value } : path, []);
|
|
497
2815
|
}
|
|
498
|
-
|
|
499
|
-
return this
|
|
2816
|
+
async call(method, ...params) {
|
|
2817
|
+
return this.#update({}, [{ method, params }]);
|
|
500
2818
|
}
|
|
501
|
-
|
|
502
|
-
return this
|
|
2819
|
+
async toggle(path) {
|
|
2820
|
+
return this.#update({}, [{ method: "$toggle", params: [path] }]);
|
|
503
2821
|
}
|
|
504
|
-
|
|
505
|
-
|
|
2822
|
+
/** Dispatches an event to this component, as the browser does for its listeners. */
|
|
2823
|
+
async dispatch(event, params = {}) {
|
|
2824
|
+
return this.#update({}, [{ method: "__dispatch", params: [event, params] }]);
|
|
506
2825
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
const current = this.bound(ConfigRepository) ? String(this.make(ConfigRepository).get("app.env", fallback)) : fallback;
|
|
510
|
-
return environments.length === 0 ? current : environments.some((pattern) => Str.is(pattern, current));
|
|
2826
|
+
async refresh() {
|
|
2827
|
+
return this.#update({}, [{ method: "$refresh", params: [] }]);
|
|
511
2828
|
}
|
|
512
|
-
|
|
513
|
-
|
|
2829
|
+
async #update(updates, calls) {
|
|
2830
|
+
const result = await this.scope(() => this.manager.updateComponent(this.#snapshot, updates, calls));
|
|
2831
|
+
this.#snapshot = JSON.stringify(result.snapshot);
|
|
2832
|
+
if (result.html !== void 0) this.#html = result.html;
|
|
2833
|
+
this.#effects = result.effects;
|
|
2834
|
+
this.#instance = result.instance;
|
|
2835
|
+
return this;
|
|
514
2836
|
}
|
|
515
|
-
|
|
516
|
-
|
|
2837
|
+
assertSet(path, value) {
|
|
2838
|
+
const actual = this.get(path);
|
|
2839
|
+
if (!isDeepStrictEqual(actual, value)) fail(`Failed asserting that property [${path}] is set to the expected value.`, actual, value);
|
|
2840
|
+
return this;
|
|
517
2841
|
}
|
|
518
|
-
|
|
519
|
-
|
|
2842
|
+
assertNotSet(path, value) {
|
|
2843
|
+
if (isDeepStrictEqual(this.get(path), value)) fail(`Failed asserting that property [${path}] is not set to the given value.`, this.get(path), value);
|
|
2844
|
+
return this;
|
|
520
2845
|
}
|
|
521
|
-
|
|
522
|
-
|
|
2846
|
+
/** Each value, HTML-escaped, appears in the rendered HTML. */
|
|
2847
|
+
assertSee(...values) {
|
|
2848
|
+
for (const value of values) if (!this.html().includes(escapeAttribute2(value))) fail(`Failed asserting that [${value}] is in the rendered HTML.`, this.html(), value);
|
|
2849
|
+
return this;
|
|
523
2850
|
}
|
|
524
|
-
|
|
525
|
-
this.
|
|
2851
|
+
assertDontSee(...values) {
|
|
2852
|
+
for (const value of values) if (this.html().includes(escapeAttribute2(value))) fail(`Failed asserting that [${value}] is not in the rendered HTML.`, this.html(), value);
|
|
2853
|
+
return this;
|
|
526
2854
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
2855
|
+
/** Each value appears in the rendered HTML as written (not escaped). */
|
|
2856
|
+
assertSeeHtml(...values) {
|
|
2857
|
+
for (const value of values) if (!this.html().includes(value)) fail(`Failed asserting that [${value}] is in the rendered HTML.`, this.html(), value);
|
|
2858
|
+
return this;
|
|
2859
|
+
}
|
|
2860
|
+
assertDispatched(name, params) {
|
|
2861
|
+
const dispatched = (this.#effects.dispatches ?? []).filter((event) => event.name === name);
|
|
2862
|
+
if (dispatched.length === 0) fail(`Failed asserting that an event [${name}] was dispatched.`, this.#effects.dispatches ?? [], name);
|
|
2863
|
+
if (params !== void 0 && !dispatched.some((event) => isDeepStrictEqual(event.params, params))) {
|
|
2864
|
+
fail(`Failed asserting that event [${name}] was dispatched with the expected params.`, dispatched.map((event) => event.params), params);
|
|
536
2865
|
}
|
|
537
|
-
this
|
|
538
|
-
if (this.hasBooted) await this.bootProvider(instance);
|
|
539
|
-
return instance;
|
|
2866
|
+
return this;
|
|
540
2867
|
}
|
|
541
|
-
|
|
542
|
-
|
|
2868
|
+
assertNotDispatched(name) {
|
|
2869
|
+
if ((this.#effects.dispatches ?? []).some((event) => event.name === name)) fail(`Failed asserting that an event [${name}] was not dispatched.`);
|
|
2870
|
+
return this;
|
|
543
2871
|
}
|
|
544
|
-
|
|
545
|
-
|
|
2872
|
+
/** The error bag has errors for every key given (or any error at all with no keys). */
|
|
2873
|
+
assertHasErrors(keys = []) {
|
|
2874
|
+
const errors = stateOf(this.#instance).errors;
|
|
2875
|
+
const wanted = [keys].flat();
|
|
2876
|
+
if (wanted.length === 0 && Object.keys(errors).length === 0) fail("Component has no errors.", errors);
|
|
2877
|
+
for (const key of wanted) if ((errors[key]?.length ?? 0) === 0) fail(`Component missing error: ${key}`, errors, key);
|
|
2878
|
+
return this;
|
|
546
2879
|
}
|
|
547
|
-
|
|
548
|
-
|
|
2880
|
+
/** No errors for the keys given (or none at all with no keys). */
|
|
2881
|
+
assertHasNoErrors(keys = []) {
|
|
2882
|
+
const errors = stateOf(this.#instance).errors;
|
|
2883
|
+
const wanted = [keys].flat();
|
|
2884
|
+
const found = wanted.length === 0 ? Object.keys(errors).filter((key) => (errors[key]?.length ?? 0) > 0) : wanted.filter((key) => (errors[key]?.length ?? 0) > 0);
|
|
2885
|
+
if (found.length > 0) fail(`Component has errors: "${found.join('", "')}"`, errors);
|
|
2886
|
+
return this;
|
|
549
2887
|
}
|
|
550
|
-
|
|
551
|
-
this.
|
|
2888
|
+
assertRedirect(url2) {
|
|
2889
|
+
const redirect2 = this.#effects.redirect;
|
|
2890
|
+
if (redirect2 === void 0) fail("Component did not perform a redirect.");
|
|
2891
|
+
if (url2 !== void 0 && redirect2 !== url2) fail("Component redirected to an unexpected URL.", redirect2, url2);
|
|
2892
|
+
return this;
|
|
552
2893
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
this
|
|
556
|
-
if (this.hasBooted) void callback(this);
|
|
2894
|
+
assertNoRedirect() {
|
|
2895
|
+
if (this.#effects.redirect !== void 0) fail("Component performed a redirect.", this.#effects.redirect);
|
|
2896
|
+
return this;
|
|
557
2897
|
}
|
|
558
|
-
|
|
559
|
-
|
|
2898
|
+
/** The last call returned `value`. */
|
|
2899
|
+
assertReturned(value) {
|
|
2900
|
+
const returned = this.#effects.returns?.at(-1);
|
|
2901
|
+
if (!isDeepStrictEqual(returned, value)) fail("Failed asserting that the method returned the expected value.", returned, value);
|
|
2902
|
+
return this;
|
|
560
2903
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
2904
|
+
};
|
|
2905
|
+
async function live(component, params = {}, key) {
|
|
2906
|
+
const html = await inject(LiveManager).mount(component, params, key === void 0 ? void 0 : String(key));
|
|
2907
|
+
return { toHtml: () => html };
|
|
2908
|
+
}
|
|
2909
|
+
function LiveComponent({ component, "o:key": key, children: _children, ...params }) {
|
|
2910
|
+
return live(component, params, key);
|
|
2911
|
+
}
|
|
2912
|
+
var Live = Object.assign(LiveComponent, {
|
|
2913
|
+
/** Registers a component under a name (components in app/Live are registered at boot). */
|
|
2914
|
+
component(name, component) {
|
|
2915
|
+
inject(LiveManager).register(component, name);
|
|
2916
|
+
},
|
|
2917
|
+
/** Livewire::test(): mounts `component` for assertions. */
|
|
2918
|
+
test(component, params = {}) {
|
|
2919
|
+
return Testable.create(component, params);
|
|
565
2920
|
}
|
|
566
|
-
|
|
567
|
-
|
|
2921
|
+
});
|
|
2922
|
+
function canonicalJson(value) {
|
|
2923
|
+
if (Array.isArray(value)) return `[${value.map((item) => canonicalJson(item === void 0 ? null : item)).join(",")}]`;
|
|
2924
|
+
if (typeof value === "object" && value !== null) {
|
|
2925
|
+
const entries = Object.keys(value).sort().filter((key) => value[key] !== void 0).map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`);
|
|
2926
|
+
return `{${entries.join(",")}}`;
|
|
568
2927
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
2928
|
+
return JSON.stringify(value);
|
|
2929
|
+
}
|
|
2930
|
+
function checksum(key, payload) {
|
|
2931
|
+
return createHmac("sha256", key).update(canonicalJson({ data: payload.data, memo: payload.memo })).digest("hex");
|
|
2932
|
+
}
|
|
2933
|
+
function verifyChecksum(key, payload, given) {
|
|
2934
|
+
if (typeof given !== "string") return false;
|
|
2935
|
+
const expected = Buffer.from(checksum(key, payload), "utf8");
|
|
2936
|
+
const actual = Buffer.from(given, "utf8");
|
|
2937
|
+
return expected.length === actual.length && timingSafeEqual(expected, actual);
|
|
2938
|
+
}
|
|
2939
|
+
function componentName(file) {
|
|
2940
|
+
return file.replace(/\.(tsx|ts|jsx|js|mts|mjs)$/, "").split(/[\\/]/).map((segment) => Str.kebab(segment)).join(".");
|
|
2941
|
+
}
|
|
2942
|
+
function defaultName(cls) {
|
|
2943
|
+
const name = cls.name;
|
|
2944
|
+
return typeof name === "string" && name !== "" ? Str.kebab(name) : void 0;
|
|
2945
|
+
}
|
|
2946
|
+
function componentFiles(directory) {
|
|
2947
|
+
if (!existsSync5(directory)) return [];
|
|
2948
|
+
return readdirSync2(directory, { recursive: true, encoding: "utf8" }).filter((file) => /\.(tsx|ts|jsx|js|mts|mjs)$/.test(file) && !/\.(test|spec|d)\.[^.]+$/.test(file)).map((file) => file.split(sep2).join("/")).sort();
|
|
2949
|
+
}
|
|
2950
|
+
async function discover(directory) {
|
|
2951
|
+
const found = [];
|
|
2952
|
+
for (const file of componentFiles(directory)) {
|
|
2953
|
+
const module = await import(
|
|
2954
|
+
/* @vite-ignore */
|
|
2955
|
+
pathToFileURL2(join5(directory, file)).href
|
|
2956
|
+
);
|
|
2957
|
+
found.push([componentName(relative2(directory, join5(directory, file))), module.default]);
|
|
574
2958
|
}
|
|
575
|
-
|
|
2959
|
+
return found;
|
|
2960
|
+
}
|
|
2961
|
+
var PERSISTENT_MIDDLEWARE = ["auth", "verified", "guest"];
|
|
2962
|
+
function canMatch(router) {
|
|
2963
|
+
const methods = router;
|
|
2964
|
+
return ["getRoutesByMatchOrder", "getPatterns", "getPatternVersion", "getMiddleware", "getMiddlewareGroups"].every((name) => typeof methods[name] === "function");
|
|
2965
|
+
}
|
|
2966
|
+
function isMatchingRoute(route2) {
|
|
2967
|
+
const candidate = route2;
|
|
2968
|
+
return typeof candidate === "object" && candidate !== null && Array.isArray(candidate.methods) && ["match", "gatherMiddleware", "excludedMiddleware"].every((name) => typeof candidate[name] === "function");
|
|
2969
|
+
}
|
|
2970
|
+
function routePath(path) {
|
|
2971
|
+
const trimmed = path.replace(/^\/+|\/+$/g, "").replace(/\/{2,}/g, "/");
|
|
2972
|
+
return trimmed === "" ? "" : `/${trimmed}`;
|
|
2973
|
+
}
|
|
2974
|
+
function expand(references, aliases, groups, out = [], visiting = /* @__PURE__ */ new Set()) {
|
|
2975
|
+
for (const reference of references) {
|
|
2976
|
+
if (typeof reference === "function") {
|
|
2977
|
+
out.push({ cls: reference, parameters: [] });
|
|
2978
|
+
continue;
|
|
2979
|
+
}
|
|
2980
|
+
const index = reference.indexOf(":");
|
|
2981
|
+
const name = index === -1 ? reference : reference.slice(0, index);
|
|
2982
|
+
if (index === -1 && Object.hasOwn(groups, name)) {
|
|
2983
|
+
if (visiting.has(name)) continue;
|
|
2984
|
+
visiting.add(name);
|
|
2985
|
+
expand(groups[name], aliases, groups, out, visiting);
|
|
2986
|
+
visiting.delete(name);
|
|
2987
|
+
} else if (Object.hasOwn(aliases, name)) {
|
|
2988
|
+
out.push({ cls: aliases[name], parameters: index === -1 ? [] : reference.slice(index + 1).split(",") });
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
return out;
|
|
2992
|
+
}
|
|
2993
|
+
function persistentMiddleware(router, persistent, method, path) {
|
|
2994
|
+
if (!canMatch(router)) throw new LiveError("Live re-applies the page route's middleware to updates, which needs Oriel's router.");
|
|
2995
|
+
const routes = router.getRoutesByMatchOrder();
|
|
2996
|
+
if (!Array.isArray(routes) || !routes.every(isMatchingRoute)) throw new LiveError("Live re-applies the page route's middleware to updates, which needs Oriel's routes.");
|
|
2997
|
+
const verb = method.toUpperCase();
|
|
2998
|
+
const target2 = routePath(path);
|
|
2999
|
+
const patterns = router.getPatterns();
|
|
3000
|
+
const version = router.getPatternVersion();
|
|
3001
|
+
const route2 = routes.find((candidate) => candidate.methods.includes(verb) && candidate.match(target2, patterns, version) !== void 0);
|
|
3002
|
+
if (route2 === void 0) return [];
|
|
3003
|
+
const aliases = router.getMiddleware();
|
|
3004
|
+
const groups = router.getMiddlewareGroups();
|
|
3005
|
+
const wanted = new Set(persistent.flatMap((name) => Object.hasOwn(aliases, name) ? [aliases[name]] : []));
|
|
3006
|
+
const excluded = new Set(expand(route2.excludedMiddleware(), aliases, groups).map((entry) => entry.cls));
|
|
3007
|
+
const ids = /* @__PURE__ */ new Map();
|
|
3008
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3009
|
+
return expand(route2.gatherMiddleware(), aliases, groups).filter((entry) => {
|
|
3010
|
+
if (!ids.has(entry.cls)) ids.set(entry.cls, ids.size);
|
|
3011
|
+
const key = `${ids.get(entry.cls)}:${entry.parameters.join(",")}`;
|
|
3012
|
+
if (!wanted.has(entry.cls) || excluded.has(entry.cls) || seen.has(key)) return false;
|
|
3013
|
+
seen.add(key);
|
|
3014
|
+
return true;
|
|
3015
|
+
});
|
|
3016
|
+
}
|
|
3017
|
+
async function throughMiddleware(container, request2, middleware, destination) {
|
|
3018
|
+
let ran;
|
|
3019
|
+
const run = async (index) => {
|
|
3020
|
+
if (index === middleware.length) {
|
|
3021
|
+
ran = { result: await destination() };
|
|
3022
|
+
return new Response(null, { status: 204 });
|
|
3023
|
+
}
|
|
3024
|
+
const { cls, parameters } = middleware[index];
|
|
3025
|
+
const instance = container.make(cls);
|
|
3026
|
+
const next = () => run(index + 1);
|
|
3027
|
+
return instance.handle(request2, next, ...parameters);
|
|
3028
|
+
};
|
|
3029
|
+
const response2 = await run(0);
|
|
3030
|
+
return ran ? ran.result : response2;
|
|
3031
|
+
}
|
|
3032
|
+
function isModel(value) {
|
|
3033
|
+
if (typeof value !== "object" || value === null) return false;
|
|
3034
|
+
const candidate = value;
|
|
3035
|
+
return typeof candidate.getKey === "function" && typeof candidate.getKeyName === "function" && typeof candidate.getTable === "function" && typeof candidate.exists === "boolean";
|
|
3036
|
+
}
|
|
3037
|
+
var isPlainObject2 = (value) => {
|
|
3038
|
+
const proto = Object.getPrototypeOf(value);
|
|
3039
|
+
return proto === Object.prototype || proto === null;
|
|
3040
|
+
};
|
|
3041
|
+
var isTuple = (value) => Array.isArray(value) && value.length === 2 && typeof value[1] === "object" && value[1] !== null && !Array.isArray(value[1]) && typeof value[1].s === "string";
|
|
3042
|
+
function classNameOf(registry, cls) {
|
|
3043
|
+
return registry.nameOf(cls) ?? registry.register(cls);
|
|
3044
|
+
}
|
|
3045
|
+
function dehydrate(value, registry, path, seen = /* @__PURE__ */ new Set()) {
|
|
3046
|
+
switch (typeof value) {
|
|
3047
|
+
case "undefined":
|
|
3048
|
+
return [null, { s: "u" }];
|
|
3049
|
+
case "string":
|
|
3050
|
+
case "boolean":
|
|
3051
|
+
return value;
|
|
3052
|
+
case "number":
|
|
3053
|
+
if (!Number.isFinite(value)) throw new LiveError(`Property [${path}] is ${value}: Live state must be JSON (finite numbers only).`);
|
|
3054
|
+
return value;
|
|
3055
|
+
case "bigint":
|
|
3056
|
+
case "symbol":
|
|
3057
|
+
case "function":
|
|
3058
|
+
throw new LiveError(`Property [${path}] holds a ${typeof value}: Live state may only hold primitives, arrays, plain objects, Dates, models and collections.`);
|
|
3059
|
+
}
|
|
3060
|
+
if (value === null) return null;
|
|
3061
|
+
const object = value;
|
|
3062
|
+
if (seen.has(object)) throw new LiveError(`Property [${path}] contains a circular reference.`);
|
|
3063
|
+
seen.add(object);
|
|
3064
|
+
try {
|
|
3065
|
+
if (object instanceof Date) return [object.toISOString(), { s: "dt" }];
|
|
3066
|
+
if (Array.isArray(object)) return [object.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), { s: "arr" }];
|
|
3067
|
+
if (isModel(object)) {
|
|
3068
|
+
const name = classNameOf(registry, object.constructor);
|
|
3069
|
+
if (!object.exists) throw new LiveError(`Property [${path}] holds an unsaved [${name}] model: only saved models can be kept between requests.`);
|
|
3070
|
+
return [null, { s: "mdl", class: name, key: object.getKey() }];
|
|
3071
|
+
}
|
|
3072
|
+
if (object instanceof Collection) {
|
|
3073
|
+
const items = object.all();
|
|
3074
|
+
const first = items[0];
|
|
3075
|
+
if (items.length > 0 && isModel(first) && items.every((item) => isModel(item) && item.constructor === first.constructor)) {
|
|
3076
|
+
for (const [i, item] of items.entries()) {
|
|
3077
|
+
if (!item.exists) throw new LiveError(`Property [${path}.${i}] holds an unsaved model.`);
|
|
3078
|
+
}
|
|
3079
|
+
const name = classNameOf(registry, first.constructor);
|
|
3080
|
+
return [null, { s: "elcln", class: name, keys: items.map((item) => item.getKey()) }];
|
|
3081
|
+
}
|
|
3082
|
+
return [items.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), { s: "clctn" }];
|
|
3083
|
+
}
|
|
3084
|
+
if (isPlainObject2(object)) {
|
|
3085
|
+
const out = {};
|
|
3086
|
+
for (const [key, item] of Object.entries(object)) out[key] = dehydrate(item, registry, `${path}.${key}`, seen);
|
|
3087
|
+
return [out, { s: "obj" }];
|
|
3088
|
+
}
|
|
3089
|
+
const type = object.constructor?.name || "object";
|
|
3090
|
+
throw new LiveError(
|
|
3091
|
+
`Property [${path}] holds a [${type}] instance: Live state may only hold primitives, arrays, plain objects, Dates, models and collections.`
|
|
3092
|
+
);
|
|
3093
|
+
} finally {
|
|
3094
|
+
seen.delete(object);
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
function modelClass(registry, name) {
|
|
3098
|
+
if (typeof name !== "string" || !registry.has(name)) throw new LiveError(`Unable to hydrate the [${String(name)}] model: it is not registered.`);
|
|
3099
|
+
return registry.resolve(name);
|
|
3100
|
+
}
|
|
3101
|
+
async function hydrate(value, registry) {
|
|
3102
|
+
if (!isTuple(value)) return value;
|
|
3103
|
+
const [inner, meta] = value;
|
|
3104
|
+
switch (meta.s) {
|
|
3105
|
+
case "u":
|
|
3106
|
+
return void 0;
|
|
3107
|
+
case "dt":
|
|
3108
|
+
return new Date(String(inner));
|
|
3109
|
+
case "arr":
|
|
3110
|
+
return Promise.all(inner.map((item) => hydrate(item, registry)));
|
|
3111
|
+
case "clctn":
|
|
3112
|
+
return new Collection(await Promise.all(inner.map((item) => hydrate(item, registry))));
|
|
3113
|
+
case "obj": {
|
|
3114
|
+
const out = {};
|
|
3115
|
+
for (const [key, item] of Object.entries(inner)) {
|
|
3116
|
+
Object.defineProperty(out, key, { value: await hydrate(item, registry), enumerable: true, writable: true, configurable: true });
|
|
3117
|
+
}
|
|
3118
|
+
return out;
|
|
3119
|
+
}
|
|
3120
|
+
case "mdl":
|
|
3121
|
+
return modelClass(registry, meta.class).findOrFail(meta.key);
|
|
3122
|
+
case "elcln": {
|
|
3123
|
+
const cls = modelClass(registry, meta.class);
|
|
3124
|
+
const keys = meta.keys ?? [];
|
|
3125
|
+
const keyName = new cls().getKeyName();
|
|
3126
|
+
const found = [...await cls.query().whereIn(keyName, keys).get()];
|
|
3127
|
+
const byKey = new Map(found.map((model) => [String(model.getKey()), model]));
|
|
3128
|
+
return new Collection(keys.map((key) => byKey.get(String(key))).filter((model) => model !== void 0));
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3131
|
+
throw new LiveError(`Unknown snapshot value type [${String(meta.s)}].`);
|
|
3132
|
+
}
|
|
3133
|
+
var HOOKS = /* @__PURE__ */ new Set(["constructor", "render", "mount", "boot", "booted", "hydrate", "dehydrate", "updating", "updated", "rendering", "rendered", "exception"]);
|
|
3134
|
+
var HOOK_PREFIX = /^(updating|updated|hydrate|dehydrate)[A-Z_]/;
|
|
3135
|
+
var UNSAFE_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
3136
|
+
var BASE_API = new Set(Object.getOwnPropertyNames(Component.prototype));
|
|
3137
|
+
var INDEX = /^(?:0|[1-9]\d*)$/;
|
|
3138
|
+
function isArraySlot(array, segment) {
|
|
3139
|
+
return INDEX.test(segment) && Number(segment) <= array.length;
|
|
3140
|
+
}
|
|
3141
|
+
function isCallable(instance, method) {
|
|
3142
|
+
if (method === "" || method.startsWith("_") || method.startsWith("$") || method.startsWith("#")) return false;
|
|
3143
|
+
if (HOOKS.has(method) || HOOK_PREFIX.test(method) || BASE_API.has(method)) return false;
|
|
3144
|
+
if (lockedOf(instance.constructor).has(method)) return false;
|
|
3145
|
+
const isBase = (proto) => proto === Component.prototype || proto === Object.prototype;
|
|
3146
|
+
for (let proto = Object.getPrototypeOf(instance); proto !== null && !isBase(proto); proto = Object.getPrototypeOf(proto)) {
|
|
3147
|
+
const descriptor = Object.getOwnPropertyDescriptor(proto, method);
|
|
3148
|
+
if (descriptor !== void 0) return typeof descriptor.value === "function";
|
|
3149
|
+
}
|
|
3150
|
+
return false;
|
|
3151
|
+
}
|
|
3152
|
+
function isUpdatable(instance, path) {
|
|
3153
|
+
const segments = path.split(".");
|
|
3154
|
+
const root = segments[0];
|
|
3155
|
+
if (root === "" || root.startsWith("_") || segments.some((segment) => segment === "" || UNSAFE_SEGMENTS.has(segment))) return false;
|
|
3156
|
+
if (!Object.hasOwn(instance, root)) return false;
|
|
3157
|
+
const descriptor = Object.getOwnPropertyDescriptor(instance, root);
|
|
3158
|
+
if (!descriptor.enumerable || descriptor.get !== void 0) return false;
|
|
3159
|
+
const cls = instance.constructor;
|
|
3160
|
+
if (lockedOf(cls).has(root) || computedOf(cls).has(root)) return false;
|
|
3161
|
+
let node = descriptor.value;
|
|
3162
|
+
for (const segment of segments.slice(1)) {
|
|
3163
|
+
if (isModel(node) || node instanceof Collection) return false;
|
|
3164
|
+
if (Array.isArray(node) && !isArraySlot(node, segment)) return false;
|
|
3165
|
+
if (typeof node !== "object" || node === null || !Object.hasOwn(node, segment)) return true;
|
|
3166
|
+
node = node[segment];
|
|
3167
|
+
}
|
|
3168
|
+
return !isModel(node) && !(node instanceof Collection);
|
|
3169
|
+
}
|
|
3170
|
+
var renderContext = new AsyncLocalStorage();
|
|
3171
|
+
var pageRequest = new AsyncLocalStorage();
|
|
3172
|
+
function optional(container, token2) {
|
|
3173
|
+
if (!container.bound(token2)) return void 0;
|
|
3174
|
+
try {
|
|
3175
|
+
return container.make(token2);
|
|
3176
|
+
} catch {
|
|
3177
|
+
return void 0;
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3181
|
+
function setOwn(object, key, value) {
|
|
3182
|
+
Object.defineProperty(object, key, { value, enumerable: true, writable: true, configurable: true });
|
|
3183
|
+
}
|
|
3184
|
+
async function hook(instance, name, ...args) {
|
|
3185
|
+
const method = instance[name];
|
|
3186
|
+
if (typeof method === "function") await method.apply(instance, args);
|
|
3187
|
+
}
|
|
3188
|
+
var LiveComponentManager = class {
|
|
3189
|
+
constructor(app) {
|
|
3190
|
+
this.app = app;
|
|
3191
|
+
}
|
|
3192
|
+
app;
|
|
3193
|
+
/** The request scope when there is one (sessions, the current route), else the application. */
|
|
3194
|
+
container() {
|
|
3195
|
+
return currentContainer() ?? this.app;
|
|
3196
|
+
}
|
|
3197
|
+
registry() {
|
|
3198
|
+
return this.container().make(ClassRegistry);
|
|
3199
|
+
}
|
|
3200
|
+
/** Live.component('counter', Counter): registers a component under a name the snapshots use. */
|
|
3201
|
+
register(component, name) {
|
|
3202
|
+
if (!isComponentClass(component)) throw new LiveError(`[${component.name}] is not a Live component: it must extend Component.`);
|
|
3203
|
+
const resolved = name ?? defaultName(component);
|
|
3204
|
+
if (resolved === void 0) throw new LiveError("Anonymous component classes need a name: Live.component(name, Class).");
|
|
3205
|
+
this.registry().register(component, resolved);
|
|
3206
|
+
}
|
|
3207
|
+
/** The registered name of a component class, registering it under its kebab-case class name the first time. */
|
|
3208
|
+
nameOf(component) {
|
|
3209
|
+
const registry = this.registry();
|
|
3210
|
+
const existing = registry.nameOf(component);
|
|
3211
|
+
if (existing !== void 0) return existing;
|
|
3212
|
+
this.register(component);
|
|
3213
|
+
return registry.nameOf(component);
|
|
3214
|
+
}
|
|
3215
|
+
resolve(name) {
|
|
3216
|
+
const registry = this.registry();
|
|
3217
|
+
if (!registry.has(name)) throw new LiveComponentNotFoundError(name);
|
|
3218
|
+
const cls = registry.resolve(name);
|
|
3219
|
+
if (!isComponentClass(cls)) throw new LiveComponentNotFoundError(name);
|
|
3220
|
+
return cls;
|
|
3221
|
+
}
|
|
3222
|
+
isLiveRequest(request2) {
|
|
3223
|
+
return request2.header("X-Oriel-Live") === "true";
|
|
3224
|
+
}
|
|
3225
|
+
/** The first render: HTML with o:id, o:snapshot and o:effects on the root element. */
|
|
3226
|
+
async mount(component, params = {}, key) {
|
|
3227
|
+
const context = renderContext.getStore();
|
|
3228
|
+
if (context === void 0) return (await this.mountComponent(component, params)).html;
|
|
3229
|
+
const childKey = key ?? `lw-${context.index++}`;
|
|
3230
|
+
const existing = Object.hasOwn(context.previous, childKey) ? context.previous[childKey] : void 0;
|
|
3231
|
+
if (existing !== void 0) {
|
|
3232
|
+
setOwn(context.children, childKey, existing);
|
|
3233
|
+
return `<${existing[0]} o:id="${escapeAttribute2(existing[1])}"></${existing[0]}>`;
|
|
3234
|
+
}
|
|
3235
|
+
const result = await this.mountComponent(component, params);
|
|
3236
|
+
setOwn(context.children, childKey, [result.tag, result.snapshot.memo.id]);
|
|
3237
|
+
return result.html;
|
|
3238
|
+
}
|
|
3239
|
+
async mountComponent(component, params = {}, existing) {
|
|
3240
|
+
const cls = typeof component === "string" ? this.resolve(component) : component;
|
|
3241
|
+
if (!isComponentClass(cls)) throw new LiveError(`[${cls.name}] is not a Live component: it must extend Component.`);
|
|
3242
|
+
const name = this.nameOf(cls);
|
|
3243
|
+
const instance = existing ?? this.container().make(cls);
|
|
3244
|
+
const state = stateOf(instance);
|
|
3245
|
+
state.id = randomBytes2(15).toString("base64url");
|
|
3246
|
+
state.name = name;
|
|
3247
|
+
await hook(instance, "boot");
|
|
3248
|
+
for (const [property, value] of Object.entries(params)) {
|
|
3249
|
+
if (Object.hasOwn(instance, property)) instance[property] = value;
|
|
3250
|
+
}
|
|
3251
|
+
this.prepareComputed(instance);
|
|
3252
|
+
await hook(instance, "mount", params);
|
|
3253
|
+
const request2 = optional(this.container(), HttpRequest);
|
|
3254
|
+
const page = pageRequest.getStore() ?? { path: request2?.path() ?? "", method: request2?.method() ?? "GET" };
|
|
3255
|
+
const memo = { id: state.id, name, path: page.path, method: page.method, children: {}, errors: {} };
|
|
3256
|
+
const { html, tag } = await this.render(instance, memo, {});
|
|
3257
|
+
const effects = this.effects(instance);
|
|
3258
|
+
const listeners = Object.keys(listenersOf(cls));
|
|
3259
|
+
if (listeners.length > 0) effects.listeners = listeners;
|
|
3260
|
+
const snapshot = await this.snapshot(instance, memo);
|
|
3261
|
+
const attributes = { "o:snapshot": JSON.stringify(snapshot), "o:effects": JSON.stringify(effects), "o:id": state.id };
|
|
3262
|
+
return { instance, snapshot, effects, html: insertAttributesIntoRoot(html, attributes), tag };
|
|
3263
|
+
}
|
|
3264
|
+
/** Applies each component's updates and calls, re-renders it and returns its new snapshot and effects. */
|
|
3265
|
+
async update(request2) {
|
|
3266
|
+
const components = [];
|
|
3267
|
+
for (const component of request2.components) {
|
|
3268
|
+
const { memo } = this.verify(component.snapshot);
|
|
3269
|
+
const result = await pageRequest.run({ path: memo.path, method: memo.method }, () => this.updateComponent(component.snapshot, component.updates, component.calls));
|
|
3270
|
+
components.push({ snapshot: JSON.stringify(result.snapshot), effects: result.effects });
|
|
3271
|
+
}
|
|
3272
|
+
return { components };
|
|
3273
|
+
}
|
|
3274
|
+
async updateComponent(snapshotJson, updates, calls) {
|
|
3275
|
+
const snapshot = this.verify(snapshotJson);
|
|
3276
|
+
const cls = this.resolve(snapshot.memo.name);
|
|
3277
|
+
const instance = this.container().make(cls);
|
|
3278
|
+
const state = stateOf(instance);
|
|
3279
|
+
state.id = snapshot.memo.id;
|
|
3280
|
+
state.name = snapshot.memo.name;
|
|
3281
|
+
state.errors = { ...snapshot.memo.errors };
|
|
3282
|
+
await hook(instance, "boot");
|
|
3283
|
+
const registry = this.registry();
|
|
3284
|
+
for (const [property, value] of Object.entries(snapshot.data)) {
|
|
3285
|
+
Object.defineProperty(instance, property, { value: await hydrate(value, registry), enumerable: true, writable: true, configurable: true });
|
|
3286
|
+
}
|
|
3287
|
+
this.prepareComputed(instance);
|
|
3288
|
+
await hook(instance, "hydrate");
|
|
3289
|
+
const returns = [];
|
|
576
3290
|
try {
|
|
577
|
-
await
|
|
3291
|
+
for (const [path, value] of Object.entries(updates)) await this.applyUpdate(instance, path, value);
|
|
3292
|
+
for (const call of calls) returns.push(await this.call(instance, call.method, call.params));
|
|
578
3293
|
} catch (error) {
|
|
579
|
-
|
|
3294
|
+
const errors = error.errors;
|
|
3295
|
+
if (error.kind !== "Validation" || !isRecord(errors)) throw error;
|
|
3296
|
+
state.errors = { ...errors };
|
|
3297
|
+
}
|
|
3298
|
+
const memo = { ...snapshot.memo, children: { ...snapshot.memo.children } };
|
|
3299
|
+
const effects = this.effects(instance);
|
|
3300
|
+
let html;
|
|
3301
|
+
let tag;
|
|
3302
|
+
if (!state.skipRender) {
|
|
3303
|
+
;
|
|
3304
|
+
({ html, tag } = await this.render(instance, memo, snapshot.memo.children));
|
|
3305
|
+
html = insertAttributesIntoRoot(html, { "o:id": state.id });
|
|
3306
|
+
effects.html = html;
|
|
580
3307
|
}
|
|
3308
|
+
if (calls.length > 0) effects.returns = returns;
|
|
3309
|
+
return { instance, snapshot: await this.snapshot(instance, memo), effects, html, tag };
|
|
581
3310
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
3311
|
+
/**
|
|
3312
|
+
* The route action of POST /oriel/live/update. Each component's update runs behind the auth middleware of the route
|
|
3313
|
+
* that rendered its page (Livewire's persistent middleware): a signed-out tab gets a 401, never the action. When a
|
|
3314
|
+
* middleware answers instead (a redirect), that is the response.
|
|
3315
|
+
*/
|
|
3316
|
+
async handle(request2) {
|
|
3317
|
+
if (!this.isLiveRequest(request2)) throw new InvalidLiveRequestError("Live update requests need the X-Oriel-Live header.");
|
|
3318
|
+
const container = this.container();
|
|
3319
|
+
const persistent = optional(container, ConfigRepository)?.get("live.persistent_middleware", PERSISTENT_MIDDLEWARE) ?? PERSISTENT_MIDDLEWARE;
|
|
3320
|
+
const components = [];
|
|
3321
|
+
for (const component of this.payload(request2).components) {
|
|
3322
|
+
const { memo } = this.verify(component.snapshot);
|
|
3323
|
+
const middleware = persistentMiddleware(container.make(Router), persistent, memo.method, memo.path);
|
|
3324
|
+
const result = await pageRequest.run(
|
|
3325
|
+
{ path: memo.path, method: memo.method },
|
|
3326
|
+
() => throughMiddleware(container, request2, middleware, () => this.updateComponent(component.snapshot, component.updates, component.calls))
|
|
3327
|
+
);
|
|
3328
|
+
if (result instanceof Response) return result;
|
|
3329
|
+
components.push({ snapshot: JSON.stringify(result.snapshot), effects: result.effects });
|
|
3330
|
+
}
|
|
3331
|
+
return { components };
|
|
3332
|
+
}
|
|
3333
|
+
/** A full-page component (Route.get('/counter', Counter)): mounted with the route parameters, inside the layout. */
|
|
3334
|
+
async page(component) {
|
|
3335
|
+
const container = this.container();
|
|
3336
|
+
const params = { ...optional(container, CurrentRoute)?.parameters ?? {} };
|
|
3337
|
+
const cls = component.constructor;
|
|
3338
|
+
const result = await this.mountComponent(cls, params, component);
|
|
3339
|
+
const redirect2 = result.effects.redirect;
|
|
3340
|
+
if (redirect2 !== void 0) {
|
|
3341
|
+
return optional(container, Redirector)?.to(redirect2) ?? new Response(null, { status: 302, headers: { Location: redirect2 } });
|
|
3342
|
+
}
|
|
3343
|
+
const layout = cls.layout ?? optional(container, ConfigRepository)?.get("live.layout") ?? "components.layouts.app";
|
|
3344
|
+
const html = result.html;
|
|
3345
|
+
const data = { children: { toHtml: () => html } };
|
|
3346
|
+
if (cls.title !== void 0) data.title = cls.title;
|
|
3347
|
+
return container.make(ViewFactory).make(layout, data);
|
|
3348
|
+
}
|
|
3349
|
+
/** Parses and checks a snapshot: a bad checksum throws before anything is hydrated. */
|
|
3350
|
+
verify(snapshotJson) {
|
|
3351
|
+
let snapshot;
|
|
3352
|
+
try {
|
|
3353
|
+
snapshot = typeof snapshotJson === "string" ? JSON.parse(snapshotJson) : void 0;
|
|
3354
|
+
} catch {
|
|
3355
|
+
throw new CorruptComponentPayloadError();
|
|
3356
|
+
}
|
|
3357
|
+
if (!isRecord(snapshot) || !isRecord(snapshot.data) || !isRecord(snapshot.memo)) throw new CorruptComponentPayloadError();
|
|
3358
|
+
const memo = snapshot.memo;
|
|
3359
|
+
const name = typeof memo.name === "string" ? memo.name : "unknown";
|
|
3360
|
+
const key = this.key();
|
|
3361
|
+
let valid;
|
|
3362
|
+
try {
|
|
3363
|
+
valid = verifyChecksum(key, { data: snapshot.data, memo: snapshot.memo }, snapshot.checksum);
|
|
3364
|
+
} catch {
|
|
3365
|
+
valid = false;
|
|
3366
|
+
}
|
|
3367
|
+
if (!valid) throw new CorruptComponentPayloadError(name);
|
|
3368
|
+
return snapshot;
|
|
588
3369
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
3370
|
+
key() {
|
|
3371
|
+
return this.container().make(Encrypter).getKey();
|
|
3372
|
+
}
|
|
3373
|
+
/** The request body, read from the raw JSON when the request has it, so TrimStrings can't alter typed values. */
|
|
3374
|
+
payload(request2) {
|
|
3375
|
+
let body;
|
|
3376
|
+
try {
|
|
3377
|
+
const content = request2.content;
|
|
3378
|
+
body = typeof content === "function" ? JSON.parse(content.call(request2)) : request2.all();
|
|
3379
|
+
} catch {
|
|
3380
|
+
throw new InvalidLiveRequestError("The Live update request body is not valid JSON.");
|
|
3381
|
+
}
|
|
3382
|
+
const components = isRecord(body) ? body.components : void 0;
|
|
3383
|
+
const valid = Array.isArray(components) && components.every(
|
|
3384
|
+
(component) => isRecord(component) && typeof component.snapshot === "string" && isRecord(component.updates) && Array.isArray(component.calls) && component.calls.every((call) => isRecord(call) && typeof call.method === "string" && Array.isArray(call.params))
|
|
3385
|
+
);
|
|
3386
|
+
if (!valid) throw new InvalidLiveRequestError("The Live update request body is malformed.");
|
|
3387
|
+
return body;
|
|
3388
|
+
}
|
|
3389
|
+
/** Makes every computed getter (static `computed` too) cache its value for the rest of the request. */
|
|
3390
|
+
prepareComputed(instance) {
|
|
3391
|
+
for (const name of computedOf(instance.constructor)) {
|
|
3392
|
+
for (let proto = Object.getPrototypeOf(instance); proto !== null; proto = Object.getPrototypeOf(proto)) {
|
|
3393
|
+
const getter = Object.getOwnPropertyDescriptor(proto, name)?.get;
|
|
3394
|
+
if (getter === void 0) continue;
|
|
3395
|
+
Object.defineProperty(instance, name, { get: cachedGetter(name, getter), enumerable: false, configurable: true });
|
|
3396
|
+
break;
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
async applyUpdate(instance, path, value) {
|
|
3401
|
+
if (!isUpdatable(instance, path)) throw new PropertyNotUpdatableError(path, stateOf(instance).name);
|
|
3402
|
+
const [root, ...rest] = path.split(".");
|
|
3403
|
+
const key = rest.length > 0 ? rest.join(".") : void 0;
|
|
3404
|
+
const studly = Str.studly(root);
|
|
3405
|
+
await hook(instance, "updating", path, value);
|
|
3406
|
+
await hook(instance, `updating${studly}`, value, key);
|
|
3407
|
+
const target2 = instance;
|
|
3408
|
+
if (rest.length === 0) target2[root] = value;
|
|
3409
|
+
else {
|
|
3410
|
+
const assertSlot = (node2, segment) => {
|
|
3411
|
+
if (Array.isArray(node2) && !isArraySlot(node2, segment)) throw new PropertyNotUpdatableError(path, stateOf(instance).name);
|
|
3412
|
+
};
|
|
3413
|
+
let node = target2[root];
|
|
3414
|
+
if (!isRecord(node) && !Array.isArray(node)) target2[root] = node = {};
|
|
3415
|
+
for (const segment of rest.slice(0, -1)) {
|
|
3416
|
+
assertSlot(node, segment);
|
|
3417
|
+
const next = node[segment];
|
|
3418
|
+
node = isRecord(next) || Array.isArray(next) ? next : node[segment] = {};
|
|
3419
|
+
}
|
|
3420
|
+
const last = rest.at(-1);
|
|
3421
|
+
assertSlot(node, last);
|
|
3422
|
+
Object.defineProperty(node, last, { value, enumerable: true, writable: true, configurable: true });
|
|
3423
|
+
}
|
|
3424
|
+
await hook(instance, "updated", path, value);
|
|
3425
|
+
await hook(instance, `updated${studly}`, value, key);
|
|
3426
|
+
}
|
|
3427
|
+
/** Runs one call from the browser: a magic action, an event, or a public method. Returns its return value. */
|
|
3428
|
+
async call(instance, method, params) {
|
|
3429
|
+
const name = stateOf(instance).name;
|
|
3430
|
+
switch (method) {
|
|
3431
|
+
case "$refresh":
|
|
3432
|
+
return void 0;
|
|
3433
|
+
case "$set":
|
|
3434
|
+
return this.applyUpdate(instance, String(params[0]), params[1]);
|
|
3435
|
+
case "$toggle": {
|
|
3436
|
+
const path = String(params[0]);
|
|
3437
|
+
const current = path.split(".").reduce((node, segment) => isRecord(node) || Array.isArray(node) ? node[segment] : void 0, instance);
|
|
3438
|
+
return this.applyUpdate(instance, path, !current);
|
|
3439
|
+
}
|
|
3440
|
+
case "__dispatch": {
|
|
3441
|
+
const [event, detail] = params;
|
|
3442
|
+
const listeners = listenersOf(instance.constructor);
|
|
3443
|
+
for (const listener of Object.hasOwn(listeners, String(event)) ? listeners[String(event)] : []) {
|
|
3444
|
+
if (!isCallable(instance, listener)) throw new MethodNotCallableError(listener, name);
|
|
3445
|
+
await instance[listener](isRecord(detail) ? detail : {});
|
|
3446
|
+
}
|
|
3447
|
+
return void 0;
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
if (!isCallable(instance, method)) throw new MethodNotCallableError(method, name);
|
|
3451
|
+
return instance[method](...params);
|
|
3452
|
+
}
|
|
3453
|
+
/** Renders the component (rendering/rendered hooks, nested children tracked) and checks for a single root. */
|
|
3454
|
+
async render(instance, memo, previous) {
|
|
3455
|
+
await hook(instance, "rendering");
|
|
3456
|
+
const context = { previous, children: {}, index: 0 };
|
|
3457
|
+
const html = await renderContext.run(context, async () => {
|
|
3458
|
+
const output = await instance.render();
|
|
3459
|
+
return typeof output === "string" ? output : this.container().make(ViewFactory).renderToString(output);
|
|
3460
|
+
});
|
|
3461
|
+
await hook(instance, "rendered", html);
|
|
3462
|
+
memo.children = context.children;
|
|
3463
|
+
return { html: html.trim(), tag: rootTag(html, memo.name) };
|
|
3464
|
+
}
|
|
3465
|
+
/** Dispatches, redirects: the effects every response carries. */
|
|
3466
|
+
effects(instance) {
|
|
3467
|
+
const state = stateOf(instance);
|
|
3468
|
+
const effects = {};
|
|
3469
|
+
if (state.dispatches.length > 0) {
|
|
3470
|
+
effects.dispatches = state.dispatches.map(({ name, params, self, to }) => ({
|
|
3471
|
+
name,
|
|
3472
|
+
params,
|
|
3473
|
+
...self ? { self } : {},
|
|
3474
|
+
...to === void 0 ? {} : { to: typeof to === "string" ? to : this.nameOf(to) }
|
|
3475
|
+
}));
|
|
3476
|
+
}
|
|
3477
|
+
if (state.redirect !== void 0) effects.redirect = state.redirect;
|
|
3478
|
+
if (state.redirectUsingNavigate) effects.redirectUsingNavigate = true;
|
|
3479
|
+
return effects;
|
|
3480
|
+
}
|
|
3481
|
+
/** dehydrate() hook, then the state as snapshot data, the error bag, and the checksum over both. */
|
|
3482
|
+
async snapshot(instance, memo) {
|
|
3483
|
+
await hook(instance, "dehydrate");
|
|
3484
|
+
const registry = this.registry();
|
|
3485
|
+
const data = {};
|
|
3486
|
+
for (const [property, value] of Object.entries(instance)) data[property] = dehydrate(value, registry, `${memo.name}.${property}`);
|
|
3487
|
+
const signed = { data, memo: { ...memo, errors: { ...stateOf(instance).errors } } };
|
|
3488
|
+
return { ...signed, checksum: checksum(this.key(), signed) };
|
|
593
3489
|
}
|
|
594
3490
|
};
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
3491
|
+
var COMPONENT_STUB = `import { Component } from '@orieljs/framework'
|
|
3492
|
+
|
|
3493
|
+
export default class {{ class }} extends Component {
|
|
3494
|
+
render() {
|
|
3495
|
+
return <div></div>
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
`;
|
|
3499
|
+
var TEST_STUB = `import { Live } from '@orieljs/framework'
|
|
3500
|
+
import { expect, test } from 'vitest'
|
|
3501
|
+
import {{ class }} from '{{ component }}'
|
|
3502
|
+
import { TestCase } from '{{ testCase }}'
|
|
3503
|
+
|
|
3504
|
+
TestCase.boot()
|
|
3505
|
+
|
|
3506
|
+
test('{{ name }} renders', async () => {
|
|
3507
|
+
const component = await Live.test({{ class }})
|
|
3508
|
+
expect(component.html()).toContain('<div')
|
|
3509
|
+
})
|
|
3510
|
+
`;
|
|
3511
|
+
var CLASS_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
3512
|
+
function importPath(from, to) {
|
|
3513
|
+
const path = relative22(dirname2(from), to).split(sep22).join("/").replace(/\.tsx?$/, "");
|
|
3514
|
+
return path.startsWith(".") ? path : `./${path}`;
|
|
3515
|
+
}
|
|
3516
|
+
function registerLiveCommands(kernel, app) {
|
|
3517
|
+
kernel.command("make:live {name : The component name, e.g. Posts/CreatePost} {--test : Also create a test} {--force : Overwrite existing files}", async function() {
|
|
3518
|
+
const segments = String(this.argument("name") ?? "").split(/[/\\.]+/).filter((segment) => segment !== "").map((segment) => Str.studly(segment));
|
|
3519
|
+
if (segments.length === 0 || !segments.every((segment) => CLASS_NAME.test(segment))) {
|
|
3520
|
+
this.error(`Component name [${String(this.argument("name"))}] is not valid: use names like Posts/CreatePost.`);
|
|
3521
|
+
return 1;
|
|
3522
|
+
}
|
|
3523
|
+
const className = segments.at(-1);
|
|
3524
|
+
const config = app.bound(ConfigRepository) ? app.make(ConfigRepository) : void 0;
|
|
3525
|
+
const directory = config?.get("live.class_path") ?? app.appPath("Live");
|
|
3526
|
+
const file = join23(directory, ...segments.slice(0, -1), `${className}.tsx`);
|
|
3527
|
+
const force = this.option("force") === true;
|
|
3528
|
+
if (existsSync22(file) && !force) {
|
|
3529
|
+
this.error(`Component [${relative22(app.basePath(), file)}] already exists.`);
|
|
3530
|
+
return 1;
|
|
3531
|
+
}
|
|
3532
|
+
await mkdir2(dirname2(file), { recursive: true });
|
|
3533
|
+
await writeFile2(file, COMPONENT_STUB.replace("{{ class }}", () => className));
|
|
3534
|
+
this.info(`Component [${relative22(app.basePath(), file)}] created successfully.`);
|
|
3535
|
+
if (this.option("test") === true) {
|
|
3536
|
+
const testFile = app.basePath(join23("tests", "Feature", "Live", ...segments.slice(0, -1), `${className}.test.ts`));
|
|
3537
|
+
if (existsSync22(testFile) && !force) {
|
|
3538
|
+
this.error(`Test [${relative22(app.basePath(), testFile)}] already exists.`);
|
|
3539
|
+
return 1;
|
|
3540
|
+
}
|
|
3541
|
+
const name = segments.map((segment) => Str.kebab(segment)).join(".");
|
|
3542
|
+
const test = TEST_STUB.replaceAll("{{ class }}", () => className).replace("{{ component }}", () => importPath(testFile, file)).replace("{{ testCase }}", () => importPath(testFile, app.basePath(join23("tests", "TestCase.ts")))).replace("{{ name }}", () => name);
|
|
3543
|
+
await mkdir2(dirname2(testFile), { recursive: true });
|
|
3544
|
+
await writeFile2(testFile, test);
|
|
3545
|
+
this.info(`Test [${relative22(app.basePath(), testFile)}] created successfully.`);
|
|
3546
|
+
}
|
|
3547
|
+
return 0;
|
|
3548
|
+
}).purpose("Create a new Live component");
|
|
600
3549
|
}
|
|
3550
|
+
var UPDATE_URI = "/oriel/live/update";
|
|
3551
|
+
var LiveServiceProvider = class extends ServiceProvider {
|
|
3552
|
+
register() {
|
|
3553
|
+
this.mergeConfigFrom({ class_path: this.app.appPath("Live"), layout: "components.layouts.app", persistent_middleware: [...PERSISTENT_MIDDLEWARE] }, "live");
|
|
3554
|
+
this.app.singleton(LiveComponentManager, () => new LiveComponentManager(this.app));
|
|
3555
|
+
this.app.singleton(LiveManager, (container) => container.make(LiveComponentManager));
|
|
3556
|
+
}
|
|
3557
|
+
async boot() {
|
|
3558
|
+
const manager = this.app.make(LiveComponentManager);
|
|
3559
|
+
if (this.app.bound(Router)) {
|
|
3560
|
+
this.app.make(Router).post(UPDATE_URI, () => manager.handle(inject(HttpRequest))).middleware("web").name("live.update");
|
|
3561
|
+
}
|
|
3562
|
+
if (this.app.bound(ConsoleKernel)) registerLiveCommands(this.app.make(ConsoleKernel), this.app);
|
|
3563
|
+
const directory = this.app.make(ConfigRepository).get("live.class_path", this.app.appPath("Live"));
|
|
3564
|
+
for (const [name, exported] of await this.app.run(() => discover(directory))) {
|
|
3565
|
+
if (isComponentClass(exported)) manager.register(exported, name);
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
};
|
|
601
3569
|
|
|
602
3570
|
// ../Testing/dist/index.js
|
|
603
|
-
import { AssertionError } from "assert";
|
|
604
|
-
import { isDeepStrictEqual } from "util";
|
|
605
|
-
import { existsSync as
|
|
606
|
-
import { join as
|
|
607
|
-
import { createHmac } from "crypto";
|
|
3571
|
+
import { AssertionError as AssertionError2 } from "assert";
|
|
3572
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
3573
|
+
import { existsSync as existsSync6 } from "fs";
|
|
3574
|
+
import { join as join6 } from "path";
|
|
3575
|
+
import { createHmac as createHmac2 } from "crypto";
|
|
608
3576
|
import { randomUUID } from "crypto";
|
|
609
3577
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
610
3578
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
611
|
-
import { randomBytes as
|
|
3579
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
612
3580
|
import { deflateSync } from "zlib";
|
|
613
|
-
function
|
|
614
|
-
const error = new
|
|
3581
|
+
function fail2(message, details = {}) {
|
|
3582
|
+
const error = new AssertionError2({ message, actual: details.actual, expected: details.expected, operator: "fail" });
|
|
615
3583
|
delete error.diff;
|
|
616
|
-
Error.captureStackTrace?.(error,
|
|
3584
|
+
Error.captureStackTrace?.(error, fail2);
|
|
617
3585
|
throw error;
|
|
618
3586
|
}
|
|
619
3587
|
function assert(condition, message, details) {
|
|
620
|
-
if (!condition)
|
|
3588
|
+
if (!condition) fail2(typeof message === "function" ? message() : message, details);
|
|
621
3589
|
}
|
|
622
3590
|
function format(value) {
|
|
623
3591
|
if (typeof value === "string") return JSON.stringify(value);
|
|
@@ -628,14 +3596,14 @@ function format(value) {
|
|
|
628
3596
|
return String(value);
|
|
629
3597
|
}
|
|
630
3598
|
}
|
|
631
|
-
function
|
|
3599
|
+
function escapeHtml2(value) {
|
|
632
3600
|
return value.replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]);
|
|
633
3601
|
}
|
|
634
3602
|
function stripTags(html) {
|
|
635
3603
|
return html.replace(/<[^>]*>/g, "");
|
|
636
3604
|
}
|
|
637
3605
|
function equals(a, b) {
|
|
638
|
-
return
|
|
3606
|
+
return isDeepStrictEqual2(canonical(a), canonical(b));
|
|
639
3607
|
}
|
|
640
3608
|
function canonical(value) {
|
|
641
3609
|
if (Array.isArray(value)) return value.map(canonical);
|
|
@@ -780,7 +3748,7 @@ async function similar(connection, table, data) {
|
|
|
780
3748
|
async function assertDatabaseHas(db, table, data, connection) {
|
|
781
3749
|
const t = target(db, table, connection);
|
|
782
3750
|
if (await constrain(t.connection.table(t.table), data).count() > 0) return;
|
|
783
|
-
|
|
3751
|
+
fail2(`Failed asserting that a row in the table [${t.table}] matches the attributes ${JSON.stringify(data, null, 2)}.
|
|
784
3752
|
|
|
785
3753
|
${await similar(t.connection, t.table, data)}`);
|
|
786
3754
|
}
|
|
@@ -1101,7 +4069,7 @@ var AssertableJson = class _AssertableJson {
|
|
|
1101
4069
|
const props = this.prop();
|
|
1102
4070
|
const keys = typeof props === "object" && props !== null ? Object.keys(props) : [];
|
|
1103
4071
|
if (keys.length === 0) {
|
|
1104
|
-
|
|
4072
|
+
fail2(this.path === void 0 ? "Cannot scope directly onto the first element of the root level because it is empty." : `Cannot scope directly onto the first element of property [${this.path}] because it is empty.`);
|
|
1105
4073
|
}
|
|
1106
4074
|
this.interactsWith(keys[0]);
|
|
1107
4075
|
return this.scope(keys[0], callback);
|
|
@@ -1110,7 +4078,7 @@ var AssertableJson = class _AssertableJson {
|
|
|
1110
4078
|
const props = this.prop();
|
|
1111
4079
|
const keys = typeof props === "object" && props !== null ? Object.keys(props) : [];
|
|
1112
4080
|
if (keys.length === 0) {
|
|
1113
|
-
|
|
4081
|
+
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.`);
|
|
1114
4082
|
}
|
|
1115
4083
|
for (const key of keys) {
|
|
1116
4084
|
this.interactsWith(key);
|
|
@@ -1141,29 +4109,29 @@ function sizeOf(value) {
|
|
|
1141
4109
|
function toPlain(value) {
|
|
1142
4110
|
return value === void 0 ? void 0 : JSON.parse(JSON.stringify(value));
|
|
1143
4111
|
}
|
|
1144
|
-
function inertiaPageOf(
|
|
4112
|
+
function inertiaPageOf(response2) {
|
|
1145
4113
|
let page;
|
|
1146
4114
|
try {
|
|
1147
|
-
if (
|
|
4115
|
+
if (response2.headers.has("X-Inertia")) page = JSON.parse(response2.content);
|
|
1148
4116
|
else {
|
|
1149
|
-
const match = /<script data-page="[^"]*" type="application\/json">([\s\S]*?)<\/script>/.exec(
|
|
4117
|
+
const match = /<script data-page="[^"]*" type="application\/json">([\s\S]*?)<\/script>/.exec(response2.content);
|
|
1150
4118
|
page = match === null ? void 0 : JSON.parse(match[1]);
|
|
1151
4119
|
}
|
|
1152
4120
|
} catch {
|
|
1153
4121
|
page = void 0;
|
|
1154
4122
|
}
|
|
1155
4123
|
const valid = typeof page === "object" && page !== null && ["component", "props", "url", "version"].every((key) => Object.hasOwn(page, key));
|
|
1156
|
-
if (!valid)
|
|
4124
|
+
if (!valid) fail2("Not a valid Inertia response.");
|
|
1157
4125
|
return page;
|
|
1158
4126
|
}
|
|
1159
4127
|
var AssertableInertia = class _AssertableInertia extends AssertableJson {
|
|
1160
4128
|
#page;
|
|
1161
4129
|
#response;
|
|
1162
|
-
static fromTestResponse(
|
|
1163
|
-
const page = inertiaPageOf(
|
|
4130
|
+
static fromTestResponse(response2) {
|
|
4131
|
+
const page = inertiaPageOf(response2);
|
|
1164
4132
|
const instance = new _AssertableInertia(page.props);
|
|
1165
4133
|
instance.#page = page;
|
|
1166
|
-
instance.#response =
|
|
4134
|
+
instance.#response = response2;
|
|
1167
4135
|
return instance;
|
|
1168
4136
|
}
|
|
1169
4137
|
/** Scopes are plain AssertableJson: the page-level methods only make sense at the root. */
|
|
@@ -1171,7 +4139,7 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
|
|
|
1171
4139
|
return new AssertableJson(props, path);
|
|
1172
4140
|
}
|
|
1173
4141
|
get page() {
|
|
1174
|
-
if (this.#page === void 0)
|
|
4142
|
+
if (this.#page === void 0) fail2("This assertion is only available on the page root.");
|
|
1175
4143
|
return this.#page;
|
|
1176
4144
|
}
|
|
1177
4145
|
/** The component name; the page file must exist unless `shouldExist` is false (inertia.testing.ensure_pages_exist). */
|
|
@@ -1183,8 +4151,8 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
|
|
|
1183
4151
|
if (shouldExist === true || shouldExist === void 0 && config?.get("inertia.testing.ensure_pages_exist", true) !== false) {
|
|
1184
4152
|
const paths = config?.get("inertia.pages.paths", []) ?? [];
|
|
1185
4153
|
const extensions = config?.get("inertia.pages.extensions", []) ?? [];
|
|
1186
|
-
const exists = paths.some((path) => extensions.some((extension) =>
|
|
1187
|
-
if (!exists)
|
|
4154
|
+
const exists = paths.some((path) => extensions.some((extension) => existsSync6(join6(path, `${value}.${extension}`))));
|
|
4155
|
+
if (!exists) fail2(`Inertia page component file [${value}] does not exist.`);
|
|
1188
4156
|
}
|
|
1189
4157
|
return this;
|
|
1190
4158
|
}
|
|
@@ -1221,7 +4189,7 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
|
|
|
1221
4189
|
*/
|
|
1222
4190
|
async reload(callback, only, except) {
|
|
1223
4191
|
const client = this.#response?.context.client;
|
|
1224
|
-
if (client === void 0)
|
|
4192
|
+
if (client === void 0) fail2("reload() needs a response from the test HTTP client.");
|
|
1225
4193
|
const headers = {
|
|
1226
4194
|
"X-Inertia": "true",
|
|
1227
4195
|
"X-Requested-With": "XMLHttpRequest",
|
|
@@ -1232,8 +4200,8 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
|
|
|
1232
4200
|
if (onlyList !== void 0 || exceptList !== void 0) headers["X-Inertia-Partial-Component"] = this.page.component;
|
|
1233
4201
|
if (onlyList !== void 0) headers["X-Inertia-Partial-Data"] = onlyList;
|
|
1234
4202
|
if (exceptList !== void 0) headers["X-Inertia-Partial-Except"] = exceptList;
|
|
1235
|
-
const
|
|
1236
|
-
const reloaded = _AssertableInertia.fromTestResponse(
|
|
4203
|
+
const response2 = await client.request("GET", this.page.url, { headers });
|
|
4204
|
+
const reloaded = _AssertableInertia.fromTestResponse(response2);
|
|
1237
4205
|
reloaded.component(this.page.component, false).url(this.page.url).version(this.page.version);
|
|
1238
4206
|
await callback?.(reloaded);
|
|
1239
4207
|
return this;
|
|
@@ -1280,7 +4248,7 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
|
|
|
1280
4248
|
}
|
|
1281
4249
|
};
|
|
1282
4250
|
function cookiePrefix(name, key) {
|
|
1283
|
-
return `${
|
|
4251
|
+
return `${createHmac2("sha1", key).update(`${name}v2`).digest("hex")}|`;
|
|
1284
4252
|
}
|
|
1285
4253
|
function encryptCookie(encrypter, name, value) {
|
|
1286
4254
|
return encrypter.encrypt(cookiePrefix(name, encrypter.getKey()) + value, false);
|
|
@@ -1328,8 +4296,8 @@ function isExpired(cookie, now = Date.now()) {
|
|
|
1328
4296
|
if (cookie.maxAge !== void 0 && !Number.isNaN(cookie.maxAge)) return cookie.maxAge <= 0;
|
|
1329
4297
|
return cookie.expires !== void 0 && cookie.expires.getTime() <= now;
|
|
1330
4298
|
}
|
|
1331
|
-
function responseCookies(
|
|
1332
|
-
return
|
|
4299
|
+
function responseCookies(response2) {
|
|
4300
|
+
return response2.headers.getSetCookie().map(parseSetCookie);
|
|
1333
4301
|
}
|
|
1334
4302
|
function cookieHeader(cookies) {
|
|
1335
4303
|
return [...cookies].map(([name, value]) => `${encodeURIComponent(name)}=${encodeURIComponent(value)}`).join("; ");
|
|
@@ -1346,8 +4314,8 @@ var TestResponse = class _TestResponse {
|
|
|
1346
4314
|
context;
|
|
1347
4315
|
decoded;
|
|
1348
4316
|
/** Reads the body once, so every assertion is synchronous. */
|
|
1349
|
-
static async from(
|
|
1350
|
-
return new _TestResponse(
|
|
4317
|
+
static async from(response2, context = {}) {
|
|
4318
|
+
return new _TestResponse(response2, await response2.text(), context);
|
|
1351
4319
|
}
|
|
1352
4320
|
get status() {
|
|
1353
4321
|
return this.baseResponse.status;
|
|
@@ -1368,7 +4336,7 @@ var TestResponse = class _TestResponse {
|
|
|
1368
4336
|
try {
|
|
1369
4337
|
this.decoded = { value: JSON.parse(this.content) };
|
|
1370
4338
|
} catch {
|
|
1371
|
-
|
|
4339
|
+
fail2(`Invalid JSON was returned from the route.
|
|
1372
4340
|
|
|
1373
4341
|
${this.excerpt()}`);
|
|
1374
4342
|
}
|
|
@@ -1381,7 +4349,7 @@ ${this.excerpt()}`);
|
|
|
1381
4349
|
}
|
|
1382
4350
|
// Status ------------------------------------------------------------------------------------------------
|
|
1383
4351
|
assertStatus(status) {
|
|
1384
|
-
if (this.status !== status)
|
|
4352
|
+
if (this.status !== status) fail2(this.statusMessage(`Expected response status code [${status}] but received ${this.status}.`), { actual: this.status, expected: status });
|
|
1385
4353
|
return this;
|
|
1386
4354
|
}
|
|
1387
4355
|
assertOk() {
|
|
@@ -1839,11 +4807,11 @@ ${JSON.stringify(errors, null, 2)}`, { actual: errors, expected: {} });
|
|
|
1839
4807
|
return Arr.get(this.viewDataOf(this.ensureView()), key);
|
|
1840
4808
|
}
|
|
1841
4809
|
// Internals ---------------------------------------------------------------------------------------------
|
|
1842
|
-
absolute(
|
|
4810
|
+
absolute(url2) {
|
|
1843
4811
|
try {
|
|
1844
|
-
return new URL(
|
|
4812
|
+
return new URL(url2, this.context.request?.url ?? "http://localhost").href;
|
|
1845
4813
|
} catch {
|
|
1846
|
-
return
|
|
4814
|
+
return url2;
|
|
1847
4815
|
}
|
|
1848
4816
|
}
|
|
1849
4817
|
excerpt(text = this.content) {
|
|
@@ -1898,13 +4866,13 @@ ${this.excerpt()}`;
|
|
|
1898
4866
|
return decryptCookie(app.make(Encrypter), cookie.name, cookie.value);
|
|
1899
4867
|
}
|
|
1900
4868
|
needles(value, escape) {
|
|
1901
|
-
return [value].flat().map((needle) => escape ?
|
|
4869
|
+
return [value].flat().map((needle) => escape ? escapeHtml2(needle) : needle);
|
|
1902
4870
|
}
|
|
1903
4871
|
inOrder(haystack, needles) {
|
|
1904
4872
|
let position = 0;
|
|
1905
4873
|
for (const needle of needles) {
|
|
1906
4874
|
const found = haystack.indexOf(needle, position);
|
|
1907
|
-
if (found === -1)
|
|
4875
|
+
if (found === -1) fail2(`Failed asserting that the response contains ${format(needles)} in that order (missing or out of order: [${needle}]).`);
|
|
1908
4876
|
position = found + needle.length;
|
|
1909
4877
|
}
|
|
1910
4878
|
return this;
|
|
@@ -1943,9 +4911,9 @@ function recordingViewFactory(factory, views) {
|
|
|
1943
4911
|
}
|
|
1944
4912
|
function recordingExceptionHandler(handler, record) {
|
|
1945
4913
|
return forward(handler, {
|
|
1946
|
-
async render(
|
|
4914
|
+
async render(request2, error) {
|
|
1947
4915
|
record(error);
|
|
1948
|
-
return handler.render(
|
|
4916
|
+
return handler.render(request2, error);
|
|
1949
4917
|
}
|
|
1950
4918
|
});
|
|
1951
4919
|
}
|
|
@@ -1983,13 +4951,13 @@ var HttpClient = class {
|
|
|
1983
4951
|
/** Requests whose actingAs users reached the guards (the AuthFactory they went through). */
|
|
1984
4952
|
actingAsApplied = /* @__PURE__ */ new WeakMap();
|
|
1985
4953
|
async request(method, uri, options = {}) {
|
|
1986
|
-
let
|
|
1987
|
-
if (!this.followRedirects) return
|
|
1988
|
-
for (let hops = 0; isRedirect(
|
|
1989
|
-
if (hops >= MAX_REDIRECTS)
|
|
1990
|
-
|
|
4954
|
+
let response2 = await this.send(this.buildRequest(method, uri, options));
|
|
4955
|
+
if (!this.followRedirects) return response2;
|
|
4956
|
+
for (let hops = 0; isRedirect(response2.status); hops++) {
|
|
4957
|
+
if (hops >= MAX_REDIRECTS) fail2(`Too many redirects (more than ${MAX_REDIRECTS}), last location [${response2.headers.get("Location")}].`);
|
|
4958
|
+
response2 = await this.send(this.buildRequest("GET", response2.headers.get("Location") ?? "/", {}));
|
|
1991
4959
|
}
|
|
1992
|
-
return
|
|
4960
|
+
return response2;
|
|
1993
4961
|
}
|
|
1994
4962
|
/** Runs `fn` in a scope prepared like a request's (carried session, actingAs users), e.g. for assertAuthenticated(). */
|
|
1995
4963
|
async inScope(fn) {
|
|
@@ -2000,23 +4968,48 @@ var HttpClient = class {
|
|
|
2000
4968
|
return fn(scope);
|
|
2001
4969
|
});
|
|
2002
4970
|
}
|
|
4971
|
+
/**
|
|
4972
|
+
* Runs `run` in a request scope like this client's requests have (a request for `method uri` bound, the carried
|
|
4973
|
+
* session, actingAs users), then carries the session and the users on, as after a request. Live.test() runs its
|
|
4974
|
+
* mount and updates through it.
|
|
4975
|
+
*/
|
|
4976
|
+
async withinRequest(run, method = "GET", uri = "/") {
|
|
4977
|
+
await this.app.boot();
|
|
4978
|
+
const request2 = this.buildRequest(method, uri, {});
|
|
4979
|
+
setClientAddress2(request2, "127.0.0.1");
|
|
4980
|
+
const scope = this.app.createScope();
|
|
4981
|
+
const context = { request: request2, app: this.app, client: this };
|
|
4982
|
+
const result = await scope.run(async () => {
|
|
4983
|
+
this.beforeRequest(scope, context);
|
|
4984
|
+
scope.instance(HttpRequest, new IncomingRequest(request2));
|
|
4985
|
+
if (scope.bound(AuthFactory)) {
|
|
4986
|
+
try {
|
|
4987
|
+
await scope.make(AuthFactory).guard().check();
|
|
4988
|
+
} catch {
|
|
4989
|
+
}
|
|
4990
|
+
}
|
|
4991
|
+
return run();
|
|
4992
|
+
});
|
|
4993
|
+
await scope.run(() => this.afterRequest(scope, context));
|
|
4994
|
+
return result;
|
|
4995
|
+
}
|
|
2003
4996
|
/** Runs the request through the app's real HttpKernel, in a request scope this client owns. */
|
|
2004
|
-
async send(
|
|
4997
|
+
async send(request2) {
|
|
2005
4998
|
const app = this.app;
|
|
2006
4999
|
await app.boot();
|
|
2007
|
-
|
|
5000
|
+
setClientAddress2(request2, "127.0.0.1");
|
|
2008
5001
|
const scope = app.createScope();
|
|
2009
|
-
const context = { request, app, client: this };
|
|
2010
|
-
const
|
|
5002
|
+
const context = { request: request2, app, client: this };
|
|
5003
|
+
const response2 = await scope.run(async () => {
|
|
2011
5004
|
await this.beforeRequest(scope, context);
|
|
2012
5005
|
this.previousUrl = void 0;
|
|
2013
|
-
return scope.make(HttpKernel).handle(
|
|
5006
|
+
return scope.make(HttpKernel).handle(request2);
|
|
2014
5007
|
});
|
|
2015
5008
|
await scope.run(() => this.afterRequest(scope, context));
|
|
2016
|
-
this.rememberCookies(
|
|
2017
|
-
await app.make(HttpKernel).terminate?.(
|
|
5009
|
+
this.rememberCookies(response2);
|
|
5010
|
+
await app.make(HttpKernel).terminate?.(request2, response2);
|
|
2018
5011
|
if (!this.handleExceptions && context.exception !== void 0) throw context.exception;
|
|
2019
|
-
return TestResponse.from(
|
|
5012
|
+
return TestResponse.from(response2, context);
|
|
2020
5013
|
}
|
|
2021
5014
|
/** Prepares the request scope: recorders, the carried session and actingAs users. */
|
|
2022
5015
|
beforeRequest(scope, context) {
|
|
@@ -2071,12 +5064,12 @@ var HttpClient = class {
|
|
|
2071
5064
|
}
|
|
2072
5065
|
buildRequest(method, uri, options) {
|
|
2073
5066
|
const verb = method.toUpperCase();
|
|
2074
|
-
const
|
|
5067
|
+
const url2 = new URL(uri, this.baseUrl());
|
|
2075
5068
|
const headers = new Headers({ ...this.headers, ...options.headers });
|
|
2076
5069
|
let body;
|
|
2077
5070
|
const data = options.data ?? {};
|
|
2078
5071
|
if (verb === "GET" || verb === "HEAD") {
|
|
2079
|
-
for (const [key, value] of flatten(data)) if (typeof value === "string")
|
|
5072
|
+
for (const [key, value] of flatten(data)) if (typeof value === "string") url2.searchParams.append(key, value);
|
|
2080
5073
|
} else if (options.json) {
|
|
2081
5074
|
body = JSON.stringify(data);
|
|
2082
5075
|
} else if (Object.keys(data).length > 0) {
|
|
@@ -2095,7 +5088,7 @@ var HttpClient = class {
|
|
|
2095
5088
|
}
|
|
2096
5089
|
const cookies = this.cookieHeader();
|
|
2097
5090
|
if (cookies !== "") headers.set("Cookie", cookies);
|
|
2098
|
-
return new Request(
|
|
5091
|
+
return new Request(url2, { method: verb, headers, body });
|
|
2099
5092
|
}
|
|
2100
5093
|
baseUrl() {
|
|
2101
5094
|
const configured = this.app.bound(ConfigRepository) ? this.app.make(ConfigRepository).get("app.url") : void 0;
|
|
@@ -2110,8 +5103,8 @@ var HttpClient = class {
|
|
|
2110
5103
|
return cookieHeader(values);
|
|
2111
5104
|
}
|
|
2112
5105
|
/** Like a browser: cookies the app sets are sent back on later requests in the same test; deleted ones are dropped. */
|
|
2113
|
-
rememberCookies(
|
|
2114
|
-
for (const cookie of responseCookies(
|
|
5106
|
+
rememberCookies(response2) {
|
|
5107
|
+
for (const cookie of responseCookies(response2)) {
|
|
2115
5108
|
if (isExpired(cookie)) this.cookies.delete(cookie.name);
|
|
2116
5109
|
else this.cookies.set(cookie.name, { value: cookie.value, encrypt: false });
|
|
2117
5110
|
}
|
|
@@ -2120,9 +5113,9 @@ var HttpClient = class {
|
|
|
2120
5113
|
function isRedirect(status) {
|
|
2121
5114
|
return status >= 300 && status < 400 && status !== 304;
|
|
2122
5115
|
}
|
|
2123
|
-
function
|
|
5116
|
+
function setClientAddress2(request2, address) {
|
|
2124
5117
|
const g = globalThis;
|
|
2125
|
-
(g[CLIENT_ADDRESS] ??= /* @__PURE__ */ new WeakMap()).set(
|
|
5118
|
+
(g[CLIENT_ADDRESS] ??= /* @__PURE__ */ new WeakMap()).set(request2, address);
|
|
2126
5119
|
}
|
|
2127
5120
|
function flatten(data, prefix = "") {
|
|
2128
5121
|
const out = [];
|
|
@@ -2218,11 +5211,43 @@ var InteractsWithApplication = class {
|
|
|
2218
5211
|
if (global === app) Container.setInstance(void 0);
|
|
2219
5212
|
}
|
|
2220
5213
|
}
|
|
5214
|
+
/**
|
|
5215
|
+
* Laravel's withoutVite(): in this test's requests vite([...]) and viteReactRefresh() render nothing, so feature
|
|
5216
|
+
* tests of pages pass before `vite build` has written public/build/manifest.json.
|
|
5217
|
+
*/
|
|
5218
|
+
withoutVite() {
|
|
5219
|
+
const app = this.app;
|
|
5220
|
+
app.scoped(Vite, () => new WithoutVite(app));
|
|
5221
|
+
return this;
|
|
5222
|
+
}
|
|
2221
5223
|
/** Runs in tearDown(), before the application is thrown away (in registration order). */
|
|
2222
5224
|
beforeApplicationDestroyed(callback) {
|
|
2223
5225
|
this.destroying.push(callback);
|
|
2224
5226
|
}
|
|
2225
5227
|
};
|
|
5228
|
+
var WithoutVite = class extends Vite {
|
|
5229
|
+
tags() {
|
|
5230
|
+
return new ViteHtml("");
|
|
5231
|
+
}
|
|
5232
|
+
reactRefresh() {
|
|
5233
|
+
return void 0;
|
|
5234
|
+
}
|
|
5235
|
+
asset() {
|
|
5236
|
+
return "";
|
|
5237
|
+
}
|
|
5238
|
+
content() {
|
|
5239
|
+
return "";
|
|
5240
|
+
}
|
|
5241
|
+
manifestHash() {
|
|
5242
|
+
return void 0;
|
|
5243
|
+
}
|
|
5244
|
+
preloadedAssets() {
|
|
5245
|
+
return {};
|
|
5246
|
+
}
|
|
5247
|
+
toHtml() {
|
|
5248
|
+
return "";
|
|
5249
|
+
}
|
|
5250
|
+
};
|
|
2226
5251
|
async function disconnectAll(db) {
|
|
2227
5252
|
const manager = db;
|
|
2228
5253
|
if (typeof manager.disconnectAll === "function") await manager.disconnectAll();
|
|
@@ -2237,6 +5262,11 @@ var MakesHttpRequests = class extends InteractsWithApplication {
|
|
|
2237
5262
|
createHttpClient() {
|
|
2238
5263
|
return new HttpClient(this.app);
|
|
2239
5264
|
}
|
|
5265
|
+
/** Live.test() runs in this test's request scope (Live reads the same token: no import between the packages). */
|
|
5266
|
+
async setUp() {
|
|
5267
|
+
await super.setUp();
|
|
5268
|
+
this.app.instance(token("Testing.RequestScope"), (run) => this.http.withinRequest(run));
|
|
5269
|
+
}
|
|
2240
5270
|
async tearDown() {
|
|
2241
5271
|
try {
|
|
2242
5272
|
await super.tearDown();
|
|
@@ -2252,8 +5282,8 @@ var MakesHttpRequests = class extends InteractsWithApplication {
|
|
|
2252
5282
|
this.http.headers[name] = value;
|
|
2253
5283
|
return this;
|
|
2254
5284
|
}
|
|
2255
|
-
withToken(
|
|
2256
|
-
return this.withHeader("Authorization", `${type} ${
|
|
5285
|
+
withToken(token2, type = "Bearer") {
|
|
5286
|
+
return this.withHeader("Authorization", `${type} ${token2}`);
|
|
2257
5287
|
}
|
|
2258
5288
|
withBasicAuth(username, password) {
|
|
2259
5289
|
return this.withToken(Buffer.from(`${username}:${password}`).toString("base64"), "Basic");
|
|
@@ -2284,9 +5314,9 @@ var MakesHttpRequests = class extends InteractsWithApplication {
|
|
|
2284
5314
|
return this;
|
|
2285
5315
|
}
|
|
2286
5316
|
/** Sets the Referer and the session's previous URL, so redirect()->back() goes there. */
|
|
2287
|
-
from(
|
|
2288
|
-
this.http.previousUrl =
|
|
2289
|
-
return this.withHeader("Referer",
|
|
5317
|
+
from(url2) {
|
|
5318
|
+
this.http.previousUrl = url2;
|
|
5319
|
+
return this.withHeader("Referer", url2);
|
|
2290
5320
|
}
|
|
2291
5321
|
/** Puts data in the session for the following requests (Laravel's withSession()). */
|
|
2292
5322
|
withSession(data) {
|
|
@@ -2436,13 +5466,13 @@ var InteractsWithConsole = class extends InteractsWithDatabase {
|
|
|
2436
5466
|
};
|
|
2437
5467
|
var TestCase = class extends InteractsWithConsole {
|
|
2438
5468
|
};
|
|
2439
|
-
function install(
|
|
2440
|
-
facade(
|
|
5469
|
+
function install(token2, fake) {
|
|
5470
|
+
facade(token2).swap(fake);
|
|
2441
5471
|
return fake;
|
|
2442
5472
|
}
|
|
2443
|
-
function bound(
|
|
5473
|
+
function bound(token2) {
|
|
2444
5474
|
const container = currentContainer();
|
|
2445
|
-
return container !== void 0 && container.bound(
|
|
5475
|
+
return container !== void 0 && container.bound(token2) ? container.make(token2) : void 0;
|
|
2446
5476
|
}
|
|
2447
5477
|
function nameOf(target2) {
|
|
2448
5478
|
if (typeof target2 === "string") return target2;
|
|
@@ -2754,7 +5784,7 @@ var BusFake = class _BusFake {
|
|
|
2754
5784
|
/** `[First, Second, new Third(1)]`: a dispatched First whose chain is exactly the rest (classes or equal instances). */
|
|
2755
5785
|
assertChained(expectedChain) {
|
|
2756
5786
|
const [first, ...rest] = expectedChain;
|
|
2757
|
-
if (first === void 0)
|
|
5787
|
+
if (first === void 0) fail2("assertChained() needs at least one job.");
|
|
2758
5788
|
const candidates = this.commands.filter((job) => sameJob(job, first));
|
|
2759
5789
|
assert(candidates.length > 0, `The expected [${nameOf(first)}] job was not dispatched.`);
|
|
2760
5790
|
const found = candidates.some((job) => {
|
|
@@ -2795,7 +5825,7 @@ var BusFake = class _BusFake {
|
|
|
2795
5825
|
return this.jobsToFake.length === 0 || matchesAny(job, this.jobsToFake);
|
|
2796
5826
|
}
|
|
2797
5827
|
real() {
|
|
2798
|
-
if (this.dispatcher === void 0)
|
|
5828
|
+
if (this.dispatcher === void 0) fail2("Bus.fake() was given jobs to fake, but there is no real BusDispatcher for the others.");
|
|
2799
5829
|
return this.dispatcher;
|
|
2800
5830
|
}
|
|
2801
5831
|
};
|
|
@@ -2894,7 +5924,7 @@ var QueueFake = class _QueueFake {
|
|
|
2894
5924
|
const jobs = this.pushed(job, callback);
|
|
2895
5925
|
assert(jobs.length > 0, `The expected [${nameOf(job)}] job was not pushed.`);
|
|
2896
5926
|
const found = jobs.some((pushed) => {
|
|
2897
|
-
const chain =
|
|
5927
|
+
const chain = chainOf2(pushed);
|
|
2898
5928
|
return chain.length === expectedChain.length && chain.every((link, index) => sameJob(link, expectedChain[index]));
|
|
2899
5929
|
});
|
|
2900
5930
|
assert(found, `The expected chain was not pushed with [${nameOf(job)}].`);
|
|
@@ -2902,7 +5932,7 @@ var QueueFake = class _QueueFake {
|
|
|
2902
5932
|
assertPushedWithoutChain(job, callback) {
|
|
2903
5933
|
const jobs = this.pushed(job, callback);
|
|
2904
5934
|
assert(jobs.length > 0, `The expected [${nameOf(job)}] job was not pushed.`);
|
|
2905
|
-
assert(jobs.some((pushed) =>
|
|
5935
|
+
assert(jobs.some((pushed) => chainOf2(pushed).length === 0), `The expected [${nameOf(job)}] job was pushed with a chain.`);
|
|
2906
5936
|
}
|
|
2907
5937
|
/** Pushed jobs matching the target (raw payloads match by their registry name or display name). */
|
|
2908
5938
|
pushed(job, callback) {
|
|
@@ -2948,11 +5978,11 @@ var QueueFake = class _QueueFake {
|
|
|
2948
5978
|
return this.jobsToFake.length === 0 || matchesAny(job, this.jobsToFake);
|
|
2949
5979
|
}
|
|
2950
5980
|
real() {
|
|
2951
|
-
if (this.manager === void 0)
|
|
5981
|
+
if (this.manager === void 0) fail2("Queue.fake() was given jobs to fake, but there is no real QueueManager for the others.");
|
|
2952
5982
|
return this.manager;
|
|
2953
5983
|
}
|
|
2954
5984
|
};
|
|
2955
|
-
function
|
|
5985
|
+
function chainOf2(job) {
|
|
2956
5986
|
const chain = job[CHAIN];
|
|
2957
5987
|
return chain?.jobs ?? [];
|
|
2958
5988
|
}
|
|
@@ -3138,8 +6168,8 @@ var AnonymousNotifiable = class {
|
|
|
3138
6168
|
}
|
|
3139
6169
|
dispatcher;
|
|
3140
6170
|
routes = /* @__PURE__ */ new Map();
|
|
3141
|
-
route(channel,
|
|
3142
|
-
this.routes.set(channel,
|
|
6171
|
+
route(channel, route2) {
|
|
6172
|
+
this.routes.set(channel, route2);
|
|
3143
6173
|
return this;
|
|
3144
6174
|
}
|
|
3145
6175
|
routeNotificationFor(channel) {
|
|
@@ -3183,8 +6213,8 @@ var NotificationFake = class _NotificationFake {
|
|
|
3183
6213
|
async sendNow(notifiables, notification, channels) {
|
|
3184
6214
|
this.record(notifiables, notification, channels);
|
|
3185
6215
|
}
|
|
3186
|
-
route(channel,
|
|
3187
|
-
return new AnonymousNotifiable(this).route(channel,
|
|
6216
|
+
route(channel, route2) {
|
|
6217
|
+
return new AnonymousNotifiable(this).route(channel, route2);
|
|
3188
6218
|
}
|
|
3189
6219
|
/** The Notifications provider calls this in boot(); a fake has no channels to register. */
|
|
3190
6220
|
extend(_channel, _factory) {
|
|
@@ -3288,7 +6318,7 @@ function guessMimeType(path) {
|
|
|
3288
6318
|
}
|
|
3289
6319
|
function hashName(file) {
|
|
3290
6320
|
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
3291
|
-
const name = [...
|
|
6321
|
+
const name = [...randomBytes3(40)].map((byte) => alphabet[byte % alphabet.length]).join("");
|
|
3292
6322
|
const extension = file.extension();
|
|
3293
6323
|
return extension === "" ? name : `${name}.${extension}`;
|
|
3294
6324
|
}
|
|
@@ -3474,7 +6504,7 @@ var StorageFake = class _StorageFake {
|
|
|
3474
6504
|
const disk = name ?? this.defaultDisk;
|
|
3475
6505
|
const faked = this.disks.get(disk);
|
|
3476
6506
|
if (faked !== void 0) return faked;
|
|
3477
|
-
if (this.manager === void 0)
|
|
6507
|
+
if (this.manager === void 0) fail2(`Disk [${disk}] is not faked and there is no real FilesystemManager.`);
|
|
3478
6508
|
return this.manager.disk(disk);
|
|
3479
6509
|
}
|
|
3480
6510
|
extend(driver, factory) {
|
|
@@ -3605,6 +6635,92 @@ export {
|
|
|
3605
6635
|
viteReactRefresh,
|
|
3606
6636
|
Application2 as Application,
|
|
3607
6637
|
ClosureKernel,
|
|
6638
|
+
HttpException,
|
|
6639
|
+
HttpResponseException,
|
|
6640
|
+
HttpServerError,
|
|
6641
|
+
exceedsJsonDepth,
|
|
6642
|
+
requestScope,
|
|
6643
|
+
currentRequest,
|
|
6644
|
+
optionalSession,
|
|
6645
|
+
optionalCookieJar,
|
|
6646
|
+
HttpRedirectResponse,
|
|
6647
|
+
HttpRedirector,
|
|
6648
|
+
setClientAddress,
|
|
6649
|
+
clientAddress,
|
|
6650
|
+
extensionFromMime,
|
|
6651
|
+
FormUploadedFile,
|
|
6652
|
+
parseBracketKey,
|
|
6653
|
+
normalizeLists,
|
|
6654
|
+
fromEntries,
|
|
6655
|
+
sanitize,
|
|
6656
|
+
mergeRecursive,
|
|
6657
|
+
isBlank,
|
|
6658
|
+
transformLeaves,
|
|
6659
|
+
ipMatches,
|
|
6660
|
+
IncomingRequest,
|
|
6661
|
+
request,
|
|
6662
|
+
response,
|
|
6663
|
+
redirect,
|
|
6664
|
+
back,
|
|
6665
|
+
url,
|
|
6666
|
+
route,
|
|
6667
|
+
abort,
|
|
6668
|
+
abortIf,
|
|
6669
|
+
abortUnless,
|
|
6670
|
+
contentDisposition,
|
|
6671
|
+
HttpResponseFactory,
|
|
6672
|
+
ConvertEmptyStringsToNull,
|
|
6673
|
+
TrimStrings,
|
|
6674
|
+
Kernel,
|
|
6675
|
+
MiddlewareConfiguration,
|
|
6676
|
+
applyMiddlewareCallbacks,
|
|
6677
|
+
statusText,
|
|
6678
|
+
escapeHtml,
|
|
6679
|
+
minimalPage,
|
|
6680
|
+
stackFrames,
|
|
6681
|
+
debugPage,
|
|
6682
|
+
statusFor,
|
|
6683
|
+
summarizeValidation,
|
|
6684
|
+
ErrorHandler,
|
|
6685
|
+
ExceptionsConfiguration,
|
|
6686
|
+
applyExceptionCallbacks,
|
|
6687
|
+
servePublicFile,
|
|
6688
|
+
bunRuntime,
|
|
6689
|
+
serveBun,
|
|
6690
|
+
toWebRequest,
|
|
6691
|
+
writeResponse,
|
|
6692
|
+
serveNode,
|
|
6693
|
+
detectRuntime,
|
|
6694
|
+
serve,
|
|
6695
|
+
routeListRows,
|
|
6696
|
+
filterRouteList,
|
|
6697
|
+
formatRouteList,
|
|
6698
|
+
API_ROUTES_STUB,
|
|
6699
|
+
addApiRouting,
|
|
6700
|
+
registerHttpCommands,
|
|
6701
|
+
HttpServiceProvider,
|
|
6702
|
+
On,
|
|
6703
|
+
Locked,
|
|
6704
|
+
Computed,
|
|
6705
|
+
ErrorBag,
|
|
6706
|
+
PendingEvent,
|
|
6707
|
+
Component,
|
|
6708
|
+
LiveError,
|
|
6709
|
+
CorruptComponentPayloadError,
|
|
6710
|
+
MethodNotCallableError,
|
|
6711
|
+
PropertyNotUpdatableError,
|
|
6712
|
+
LiveComponentNotFoundError,
|
|
6713
|
+
InvalidLiveRequestError,
|
|
6714
|
+
TestRequestScope,
|
|
6715
|
+
Testable,
|
|
6716
|
+
live,
|
|
6717
|
+
Live,
|
|
6718
|
+
componentName,
|
|
6719
|
+
PERSISTENT_MIDDLEWARE,
|
|
6720
|
+
persistentMiddleware,
|
|
6721
|
+
LiveComponentManager,
|
|
6722
|
+
UPDATE_URI,
|
|
6723
|
+
LiveServiceProvider,
|
|
3608
6724
|
PendingCommand,
|
|
3609
6725
|
RefreshDatabaseState,
|
|
3610
6726
|
runCommand,
|
|
@@ -3650,4 +6766,4 @@ export {
|
|
|
3650
6766
|
StorageFake,
|
|
3651
6767
|
FakeFile
|
|
3652
6768
|
};
|
|
3653
|
-
//# sourceMappingURL=chunk-
|
|
6769
|
+
//# sourceMappingURL=chunk-SBZQ6L3Z.js.map
|