@orieljs/framework 0.1.0-alpha.3 → 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-WEGTH46M.js → chunk-3DETYZMZ.js} +3 -3
- package/dist/{chunk-IRHK7GH7.js → chunk-DCI2T7WJ.js} +24 -3
- package/dist/chunk-DCI2T7WJ.js.map +1 -0
- package/dist/{chunk-I2OMKIQZ.js → chunk-SBZQ6L3Z.js} +2213 -137
- 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 +155 -132
- package/dist/contracts.js +3 -1
- package/dist/index.d.ts +543 -285
- package/dist/index.js +805 -2053
- package/dist/index.js.map +1 -1
- package/dist/jsx-runtime.js +3 -3
- package/dist/{testing--F6FODc_.d.ts → testing-D3dCOkTm.d.ts} +208 -86
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +3 -3
- package/package.json +30 -30
- package/dist/chunk-3ZRPBRAE.js.map +0 -1
- package/dist/chunk-HU43NADT.js +0 -745
- package/dist/chunk-HU43NADT.js.map +0 -1
- package/dist/chunk-I2OMKIQZ.js.map +0 -1
- package/dist/chunk-IRHK7GH7.js.map +0 -1
- package/dist/chunk-S7YC43IH.js +0 -93
- package/dist/chunk-S7YC43IH.js.map +0 -1
- package/dist/chunk-TIE5OAMF.js +0 -474
- package/dist/chunk-TIE5OAMF.js.map +0 -1
- package/dist/chunk-UPZAML2Q.js +0 -3653
- package/dist/chunk-UPZAML2Q.js.map +0 -1
- package/dist/jsx-runtime-CZ6JqBQo.d.ts +0 -345
- /package/dist/{chunk-WEGTH46M.js.map → chunk-3DETYZMZ.js.map} +0 -0
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
currentContainer,
|
|
10
10
|
facade,
|
|
11
11
|
inject
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-DCI2T7WJ.js";
|
|
13
13
|
import {
|
|
14
14
|
Application,
|
|
15
15
|
AuthFactory,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
ClassRegistry,
|
|
18
18
|
ConfigRepository,
|
|
19
19
|
ConsoleKernel,
|
|
20
|
+
CookieJar,
|
|
20
21
|
CurrentRoute,
|
|
21
22
|
DatabaseManager,
|
|
22
23
|
Encrypter,
|
|
@@ -27,18 +28,22 @@ import {
|
|
|
27
28
|
HttpKernel,
|
|
28
29
|
HttpRequest,
|
|
29
30
|
LiveManager,
|
|
31
|
+
LogManager,
|
|
30
32
|
MailManager,
|
|
31
33
|
MiddlewareCallbacks,
|
|
32
34
|
NotificationDispatcher,
|
|
35
|
+
PipelineFactory,
|
|
33
36
|
QueueManager,
|
|
34
37
|
Redirector,
|
|
38
|
+
ResponseFactory,
|
|
35
39
|
Router,
|
|
36
40
|
RoutingPaths,
|
|
37
41
|
Session,
|
|
38
42
|
UrlGenerator,
|
|
39
43
|
ValidationFactory,
|
|
40
|
-
ViewFactory
|
|
41
|
-
|
|
44
|
+
ViewFactory,
|
|
45
|
+
token
|
|
46
|
+
} from "./chunk-WSGWYXAO.js";
|
|
42
47
|
|
|
43
48
|
// ../Config/dist/index.js
|
|
44
49
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -88,12 +93,20 @@ function env(key, defaultValue) {
|
|
|
88
93
|
const quoted = /^(["'])(.*)\1$/s.exec(raw);
|
|
89
94
|
return quoted ? quoted[2] : raw;
|
|
90
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
|
+
}
|
|
91
101
|
function loadEnvironment(basePath) {
|
|
92
102
|
const appEnv = process.env.APP_ENV;
|
|
93
103
|
const specific = appEnv ? join(basePath, `.env.${appEnv}`) : void 0;
|
|
94
104
|
const file = specific && existsSync(specific) ? specific : join(basePath, ".env");
|
|
95
105
|
if (!existsSync(file)) return void 0;
|
|
96
|
-
|
|
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);
|
|
97
110
|
if (process.env[key] === void 0) process.env[key] = value;
|
|
98
111
|
}
|
|
99
112
|
return file;
|
|
@@ -140,10 +153,11 @@ var ApplicationBuilder = class {
|
|
|
140
153
|
return this;
|
|
141
154
|
}
|
|
142
155
|
withRouting(options) {
|
|
143
|
-
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);
|
|
144
158
|
const paths = { apiPrefix: options.apiPrefix ?? "api" };
|
|
145
|
-
if (options.web !== void 0) paths.web =
|
|
146
|
-
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);
|
|
147
161
|
if (options.console !== void 0) paths.console = abs(options.console);
|
|
148
162
|
if (options.health !== void 0) paths.health = options.health;
|
|
149
163
|
this.routing = paths;
|
|
@@ -323,9 +337,9 @@ var Vite = class {
|
|
|
323
337
|
const stylesheets = uniqueTags.filter((tag) => tag.startsWith("<link"));
|
|
324
338
|
const scripts = uniqueTags.filter((tag) => !tag.startsWith("<link"));
|
|
325
339
|
const seen = /* @__PURE__ */ new Set();
|
|
326
|
-
const uniquePreloads = preloads.filter(([,
|
|
327
|
-
const sorted = [...uniquePreloads.filter(([,
|
|
328
|
-
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));
|
|
329
343
|
return new ViteHtml(preloadTags.join("") + stylesheets.join("") + scripts.join(""));
|
|
330
344
|
}
|
|
331
345
|
/** Laravel's @viteReactRefresh: the React Refresh preamble while the dev server runs; nothing otherwise. */
|
|
@@ -410,41 +424,41 @@ var Vite = class {
|
|
|
410
424
|
}
|
|
411
425
|
return imports;
|
|
412
426
|
}
|
|
413
|
-
#tagForChunk(src,
|
|
414
|
-
if (CSS_PATH.test(
|
|
415
|
-
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) });
|
|
416
430
|
return `<link ${attributes2.join(" ")} />`;
|
|
417
431
|
}
|
|
418
|
-
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) });
|
|
419
433
|
return `<script ${attributes.join(" ")}></script>`;
|
|
420
434
|
}
|
|
421
|
-
#preloadTag(src,
|
|
422
|
-
const css = CSS_PATH.test(
|
|
435
|
+
#preloadTag(src, url2, chunk2, manifest) {
|
|
436
|
+
const css = CSS_PATH.test(url2);
|
|
423
437
|
let attributes = {
|
|
424
438
|
rel: css ? "preload" : "modulepreload",
|
|
425
439
|
as: css ? "style" : "script",
|
|
426
|
-
href:
|
|
440
|
+
href: url2,
|
|
427
441
|
nonce: this.#nonce ?? false,
|
|
428
|
-
crossorigin: (css ? this.#styleAttributes(src,
|
|
442
|
+
crossorigin: (css ? this.#styleAttributes(src, url2, chunk2, manifest) : this.#scriptAttributes(src, url2, chunk2, manifest)).crossorigin ?? false
|
|
429
443
|
};
|
|
430
444
|
if (this.#integrityKey !== false) attributes.integrity = chunk2?.[this.#integrityKey] ?? false;
|
|
431
445
|
for (const resolver of this.#preloadResolvers) {
|
|
432
|
-
const resolved = resolver(src,
|
|
446
|
+
const resolved = resolver(src, url2, chunk2, manifest);
|
|
433
447
|
if (resolved === false) return "";
|
|
434
448
|
attributes = { ...attributes, ...resolved };
|
|
435
449
|
}
|
|
436
450
|
const { href: _href, ...rest } = attributes;
|
|
437
|
-
this.#preloaded[
|
|
451
|
+
this.#preloaded[url2] = this.#parseAttributes(rest);
|
|
438
452
|
return `<link ${this.#parseAttributes(attributes).join(" ")} />`;
|
|
439
453
|
}
|
|
440
|
-
#scriptAttributes(src,
|
|
454
|
+
#scriptAttributes(src, url2, chunk2, manifest) {
|
|
441
455
|
let attributes = this.#integrityKey !== false ? { integrity: chunk2?.[this.#integrityKey] ?? false } : {};
|
|
442
|
-
for (const resolver of this.#scriptResolvers) attributes = { ...attributes, ...resolver(src,
|
|
456
|
+
for (const resolver of this.#scriptResolvers) attributes = { ...attributes, ...resolver(src, url2, chunk2, manifest) };
|
|
443
457
|
return attributes;
|
|
444
458
|
}
|
|
445
|
-
#styleAttributes(src,
|
|
459
|
+
#styleAttributes(src, url2, chunk2, manifest) {
|
|
446
460
|
let attributes = this.#integrityKey !== false ? { integrity: chunk2?.[this.#integrityKey] ?? false } : {};
|
|
447
|
-
for (const resolver of this.#styleResolvers) attributes = { ...attributes, ...resolver(src,
|
|
461
|
+
for (const resolver of this.#styleResolvers) attributes = { ...attributes, ...resolver(src, url2, chunk2, manifest) };
|
|
448
462
|
return attributes;
|
|
449
463
|
}
|
|
450
464
|
/** false/null/undefined are dropped, true is a bare attribute, anything else name="value". */
|
|
@@ -476,7 +490,7 @@ var Application2 = class _Application extends Container {
|
|
|
476
490
|
this.scoped(Vite, () => new Vite(this));
|
|
477
491
|
}
|
|
478
492
|
base;
|
|
479
|
-
version = "0.1.0-alpha.
|
|
493
|
+
version = "0.1.0-alpha.4";
|
|
480
494
|
providers = [];
|
|
481
495
|
bootingCallbacks = [];
|
|
482
496
|
bootedCallbacks = [];
|
|
@@ -566,10 +580,10 @@ var Application2 = class _Application extends Container {
|
|
|
566
580
|
terminating(callback) {
|
|
567
581
|
this.terminatingCallbacks.push(callback);
|
|
568
582
|
}
|
|
569
|
-
async handle(
|
|
583
|
+
async handle(request2) {
|
|
570
584
|
await this.boot();
|
|
571
585
|
const scope = this.createScope();
|
|
572
|
-
return scope.run(() => scope.make(HttpKernel).handle(
|
|
586
|
+
return scope.run(() => scope.make(HttpKernel).handle(request2));
|
|
573
587
|
}
|
|
574
588
|
async terminate() {
|
|
575
589
|
for (const callback of this.terminatingCallbacks) await callback();
|
|
@@ -594,10 +608,10 @@ var ClosureKernel = class {
|
|
|
594
608
|
this.routes.set(`${method.toUpperCase()} ${path}`, handler);
|
|
595
609
|
return this;
|
|
596
610
|
}
|
|
597
|
-
async handle(
|
|
598
|
-
const handler = this.routes.get(`${
|
|
611
|
+
async handle(request2) {
|
|
612
|
+
const handler = this.routes.get(`${request2.method} ${new URL(request2.url).pathname}`);
|
|
599
613
|
if (!handler) return new Response("Not Found", { status: 404, headers: { "content-type": "text/plain; charset=utf-8" } });
|
|
600
|
-
return toResponse(await handler(
|
|
614
|
+
return toResponse(await handler(request2));
|
|
601
615
|
}
|
|
602
616
|
};
|
|
603
617
|
function toResponse(output) {
|
|
@@ -607,18 +621,1852 @@ function toResponse(output) {
|
|
|
607
621
|
return Response.json(output);
|
|
608
622
|
}
|
|
609
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
|
+
|
|
610
2458
|
// ../Live/dist/index.js
|
|
611
2459
|
import { AssertionError } from "assert";
|
|
612
2460
|
import { isDeepStrictEqual } from "util";
|
|
613
2461
|
import { AsyncLocalStorage } from "async_hooks";
|
|
614
2462
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
615
2463
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
616
|
-
import { existsSync as
|
|
617
|
-
import { join as join5, relative, sep } from "path";
|
|
2464
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
|
|
2465
|
+
import { join as join5, relative as relative2, sep as sep2 } from "path";
|
|
618
2466
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
619
2467
|
import { existsSync as existsSync22 } from "fs";
|
|
620
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
621
|
-
import { dirname, join as join23, relative as
|
|
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";
|
|
622
2470
|
Symbol.metadata ??= /* @__PURE__ */ Symbol.for("Symbol.metadata");
|
|
623
2471
|
var LISTENERS = /* @__PURE__ */ Symbol.for("oriel:live.listeners");
|
|
624
2472
|
var LOCKED = /* @__PURE__ */ Symbol.for("oriel:live.locked");
|
|
@@ -771,9 +2619,9 @@ var Component = class {
|
|
|
771
2619
|
return new PendingEvent(event);
|
|
772
2620
|
}
|
|
773
2621
|
/** Sends the browser to `url` after this request; `navigate` uses o:navigate instead of a full page load. */
|
|
774
|
-
redirect(
|
|
2622
|
+
redirect(url2, options = {}) {
|
|
775
2623
|
const state = stateOf(this);
|
|
776
|
-
state.redirect =
|
|
2624
|
+
state.redirect = url2;
|
|
777
2625
|
if (options.navigate === true) state.redirectUsingNavigate = true;
|
|
778
2626
|
else delete state.redirectUsingNavigate;
|
|
779
2627
|
}
|
|
@@ -884,14 +2732,14 @@ function rootTag(html, component) {
|
|
|
884
2732
|
let rootEnd = -1;
|
|
885
2733
|
TOKEN.lastIndex = 0;
|
|
886
2734
|
for (let match = TOKEN.exec(source); match !== null; match = TOKEN.exec(source)) {
|
|
887
|
-
const [
|
|
2735
|
+
const [token2, closing, opening, selfClosing] = match;
|
|
888
2736
|
if (root === void 0 && match.index !== 0) fail22();
|
|
889
2737
|
if (depth === 0 && root !== void 0) fail22();
|
|
890
2738
|
if (opening !== void 0) {
|
|
891
2739
|
const tag = opening.toLowerCase();
|
|
892
2740
|
root ??= tag;
|
|
893
2741
|
if (selfClosing === "/" || VOID.has(tag)) {
|
|
894
|
-
if (depth === 0) rootEnd = match.index +
|
|
2742
|
+
if (depth === 0) rootEnd = match.index + token2.length;
|
|
895
2743
|
continue;
|
|
896
2744
|
}
|
|
897
2745
|
if (RAW_TEXT.has(tag)) {
|
|
@@ -903,7 +2751,7 @@ function rootTag(html, component) {
|
|
|
903
2751
|
depth++;
|
|
904
2752
|
} else if (closing !== void 0) {
|
|
905
2753
|
if (--depth < 0) fail22();
|
|
906
|
-
if (depth === 0) rootEnd = match.index +
|
|
2754
|
+
if (depth === 0) rootEnd = match.index + token2.length;
|
|
907
2755
|
}
|
|
908
2756
|
}
|
|
909
2757
|
if (root === void 0 || depth !== 0 || source.slice(rootEnd).trim() !== "") fail22();
|
|
@@ -920,23 +2768,31 @@ var fail = (message, actual, expected) => {
|
|
|
920
2768
|
throw new AssertionError({ message, actual, expected, operator: "live" });
|
|
921
2769
|
};
|
|
922
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
|
+
}
|
|
923
2776
|
var Testable = class _Testable {
|
|
924
|
-
constructor(manager, result) {
|
|
2777
|
+
constructor(manager, scope, result) {
|
|
925
2778
|
this.manager = manager;
|
|
2779
|
+
this.scope = scope;
|
|
926
2780
|
this.#snapshot = JSON.stringify(result.snapshot);
|
|
927
2781
|
this.#html = result.html ?? "";
|
|
928
2782
|
this.#effects = result.effects;
|
|
929
2783
|
this.#instance = result.instance;
|
|
930
2784
|
}
|
|
931
2785
|
manager;
|
|
2786
|
+
scope;
|
|
932
2787
|
#snapshot;
|
|
933
2788
|
#html;
|
|
934
2789
|
#effects;
|
|
935
2790
|
#instance;
|
|
936
2791
|
/** Mounts `component` with `params`, as `<Live component={…} {...params} />` would. */
|
|
937
2792
|
static async create(component, params = {}) {
|
|
2793
|
+
const scope = requestScope2();
|
|
938
2794
|
const manager = inject(LiveManager);
|
|
939
|
-
return new _Testable(manager, await manager.mountComponent(component, params));
|
|
2795
|
+
return new _Testable(manager, scope, await scope(() => manager.mountComponent(component, params)));
|
|
940
2796
|
}
|
|
941
2797
|
/** The component instance after the last request. */
|
|
942
2798
|
instance() {
|
|
@@ -971,7 +2827,7 @@ var Testable = class _Testable {
|
|
|
971
2827
|
return this.#update({}, [{ method: "$refresh", params: [] }]);
|
|
972
2828
|
}
|
|
973
2829
|
async #update(updates, calls) {
|
|
974
|
-
const result = await this.manager.updateComponent(this.#snapshot, updates, calls);
|
|
2830
|
+
const result = await this.scope(() => this.manager.updateComponent(this.#snapshot, updates, calls));
|
|
975
2831
|
this.#snapshot = JSON.stringify(result.snapshot);
|
|
976
2832
|
if (result.html !== void 0) this.#html = result.html;
|
|
977
2833
|
this.#effects = result.effects;
|
|
@@ -1029,10 +2885,10 @@ var Testable = class _Testable {
|
|
|
1029
2885
|
if (found.length > 0) fail(`Component has errors: "${found.join('", "')}"`, errors);
|
|
1030
2886
|
return this;
|
|
1031
2887
|
}
|
|
1032
|
-
assertRedirect(
|
|
1033
|
-
const
|
|
1034
|
-
if (
|
|
1035
|
-
if (
|
|
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);
|
|
1036
2892
|
return this;
|
|
1037
2893
|
}
|
|
1038
2894
|
assertNoRedirect() {
|
|
@@ -1088,8 +2944,8 @@ function defaultName(cls) {
|
|
|
1088
2944
|
return typeof name === "string" && name !== "" ? Str.kebab(name) : void 0;
|
|
1089
2945
|
}
|
|
1090
2946
|
function componentFiles(directory) {
|
|
1091
|
-
if (!
|
|
1092
|
-
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(
|
|
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();
|
|
1093
2949
|
}
|
|
1094
2950
|
async function discover(directory) {
|
|
1095
2951
|
const found = [];
|
|
@@ -1098,16 +2954,87 @@ async function discover(directory) {
|
|
|
1098
2954
|
/* @vite-ignore */
|
|
1099
2955
|
pathToFileURL2(join5(directory, file)).href
|
|
1100
2956
|
);
|
|
1101
|
-
found.push([componentName(
|
|
2957
|
+
found.push([componentName(relative2(directory, join5(directory, file))), module.default]);
|
|
1102
2958
|
}
|
|
1103
2959
|
return found;
|
|
1104
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
|
+
}
|
|
1105
3032
|
function isModel(value) {
|
|
1106
3033
|
if (typeof value !== "object" || value === null) return false;
|
|
1107
3034
|
const candidate = value;
|
|
1108
3035
|
return typeof candidate.getKey === "function" && typeof candidate.getKeyName === "function" && typeof candidate.getTable === "function" && typeof candidate.exists === "boolean";
|
|
1109
3036
|
}
|
|
1110
|
-
var
|
|
3037
|
+
var isPlainObject2 = (value) => {
|
|
1111
3038
|
const proto = Object.getPrototypeOf(value);
|
|
1112
3039
|
return proto === Object.prototype || proto === null;
|
|
1113
3040
|
};
|
|
@@ -1154,7 +3081,7 @@ function dehydrate(value, registry, path, seen = /* @__PURE__ */ new Set()) {
|
|
|
1154
3081
|
}
|
|
1155
3082
|
return [items.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), { s: "clctn" }];
|
|
1156
3083
|
}
|
|
1157
|
-
if (
|
|
3084
|
+
if (isPlainObject2(object)) {
|
|
1158
3085
|
const out = {};
|
|
1159
3086
|
for (const [key, item] of Object.entries(object)) out[key] = dehydrate(item, registry, `${path}.${key}`, seen);
|
|
1160
3087
|
return [out, { s: "obj" }];
|
|
@@ -1241,10 +3168,11 @@ function isUpdatable(instance, path) {
|
|
|
1241
3168
|
return !isModel(node) && !(node instanceof Collection);
|
|
1242
3169
|
}
|
|
1243
3170
|
var renderContext = new AsyncLocalStorage();
|
|
1244
|
-
|
|
1245
|
-
|
|
3171
|
+
var pageRequest = new AsyncLocalStorage();
|
|
3172
|
+
function optional(container, token2) {
|
|
3173
|
+
if (!container.bound(token2)) return void 0;
|
|
1246
3174
|
try {
|
|
1247
|
-
return container.make(
|
|
3175
|
+
return container.make(token2);
|
|
1248
3176
|
} catch {
|
|
1249
3177
|
return void 0;
|
|
1250
3178
|
}
|
|
@@ -1291,8 +3219,8 @@ var LiveComponentManager = class {
|
|
|
1291
3219
|
if (!isComponentClass(cls)) throw new LiveComponentNotFoundError(name);
|
|
1292
3220
|
return cls;
|
|
1293
3221
|
}
|
|
1294
|
-
isLiveRequest(
|
|
1295
|
-
return
|
|
3222
|
+
isLiveRequest(request2) {
|
|
3223
|
+
return request2.header("X-Oriel-Live") === "true";
|
|
1296
3224
|
}
|
|
1297
3225
|
/** The first render: HTML with o:id, o:snapshot and o:effects on the root element. */
|
|
1298
3226
|
async mount(component, params = {}, key) {
|
|
@@ -1322,8 +3250,9 @@ var LiveComponentManager = class {
|
|
|
1322
3250
|
}
|
|
1323
3251
|
this.prepareComputed(instance);
|
|
1324
3252
|
await hook(instance, "mount", params);
|
|
1325
|
-
const
|
|
1326
|
-
const
|
|
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: {} };
|
|
1327
3256
|
const { html, tag } = await this.render(instance, memo, {});
|
|
1328
3257
|
const effects = this.effects(instance);
|
|
1329
3258
|
const listeners = Object.keys(listenersOf(cls));
|
|
@@ -1333,10 +3262,11 @@ var LiveComponentManager = class {
|
|
|
1333
3262
|
return { instance, snapshot, effects, html: insertAttributesIntoRoot(html, attributes), tag };
|
|
1334
3263
|
}
|
|
1335
3264
|
/** Applies each component's updates and calls, re-renders it and returns its new snapshot and effects. */
|
|
1336
|
-
async update(
|
|
3265
|
+
async update(request2) {
|
|
1337
3266
|
const components = [];
|
|
1338
|
-
for (const component of
|
|
1339
|
-
const
|
|
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));
|
|
1340
3270
|
components.push({ snapshot: JSON.stringify(result.snapshot), effects: result.effects });
|
|
1341
3271
|
}
|
|
1342
3272
|
return { components };
|
|
@@ -1378,10 +3308,27 @@ var LiveComponentManager = class {
|
|
|
1378
3308
|
if (calls.length > 0) effects.returns = returns;
|
|
1379
3309
|
return { instance, snapshot: await this.snapshot(instance, memo), effects, html, tag };
|
|
1380
3310
|
}
|
|
1381
|
-
/**
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
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 };
|
|
1385
3332
|
}
|
|
1386
3333
|
/** A full-page component (Route.get('/counter', Counter)): mounted with the route parameters, inside the layout. */
|
|
1387
3334
|
async page(component) {
|
|
@@ -1389,9 +3336,9 @@ var LiveComponentManager = class {
|
|
|
1389
3336
|
const params = { ...optional(container, CurrentRoute)?.parameters ?? {} };
|
|
1390
3337
|
const cls = component.constructor;
|
|
1391
3338
|
const result = await this.mountComponent(cls, params, component);
|
|
1392
|
-
const
|
|
1393
|
-
if (
|
|
1394
|
-
return optional(container, Redirector)?.to(
|
|
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 } });
|
|
1395
3342
|
}
|
|
1396
3343
|
const layout = cls.layout ?? optional(container, ConfigRepository)?.get("live.layout") ?? "components.layouts.app";
|
|
1397
3344
|
const html = result.html;
|
|
@@ -1424,11 +3371,11 @@ var LiveComponentManager = class {
|
|
|
1424
3371
|
return this.container().make(Encrypter).getKey();
|
|
1425
3372
|
}
|
|
1426
3373
|
/** The request body, read from the raw JSON when the request has it, so TrimStrings can't alter typed values. */
|
|
1427
|
-
payload(
|
|
3374
|
+
payload(request2) {
|
|
1428
3375
|
let body;
|
|
1429
3376
|
try {
|
|
1430
|
-
const content =
|
|
1431
|
-
body = typeof content === "function" ? JSON.parse(content.call(
|
|
3377
|
+
const content = request2.content;
|
|
3378
|
+
body = typeof content === "function" ? JSON.parse(content.call(request2)) : request2.all();
|
|
1432
3379
|
} catch {
|
|
1433
3380
|
throw new InvalidLiveRequestError("The Live update request body is not valid JSON.");
|
|
1434
3381
|
}
|
|
@@ -1563,7 +3510,7 @@ test('{{ name }} renders', async () => {
|
|
|
1563
3510
|
`;
|
|
1564
3511
|
var CLASS_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1565
3512
|
function importPath(from, to) {
|
|
1566
|
-
const path =
|
|
3513
|
+
const path = relative22(dirname2(from), to).split(sep22).join("/").replace(/\.tsx?$/, "");
|
|
1567
3514
|
return path.startsWith(".") ? path : `./${path}`;
|
|
1568
3515
|
}
|
|
1569
3516
|
function registerLiveCommands(kernel, app) {
|
|
@@ -1579,23 +3526,23 @@ function registerLiveCommands(kernel, app) {
|
|
|
1579
3526
|
const file = join23(directory, ...segments.slice(0, -1), `${className}.tsx`);
|
|
1580
3527
|
const force = this.option("force") === true;
|
|
1581
3528
|
if (existsSync22(file) && !force) {
|
|
1582
|
-
this.error(`Component [${
|
|
3529
|
+
this.error(`Component [${relative22(app.basePath(), file)}] already exists.`);
|
|
1583
3530
|
return 1;
|
|
1584
3531
|
}
|
|
1585
|
-
await
|
|
1586
|
-
await
|
|
1587
|
-
this.info(`Component [${
|
|
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.`);
|
|
1588
3535
|
if (this.option("test") === true) {
|
|
1589
3536
|
const testFile = app.basePath(join23("tests", "Feature", "Live", ...segments.slice(0, -1), `${className}.test.ts`));
|
|
1590
3537
|
if (existsSync22(testFile) && !force) {
|
|
1591
|
-
this.error(`Test [${
|
|
3538
|
+
this.error(`Test [${relative22(app.basePath(), testFile)}] already exists.`);
|
|
1592
3539
|
return 1;
|
|
1593
3540
|
}
|
|
1594
3541
|
const name = segments.map((segment) => Str.kebab(segment)).join(".");
|
|
1595
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);
|
|
1596
|
-
await
|
|
1597
|
-
await
|
|
1598
|
-
this.info(`Test [${
|
|
3543
|
+
await mkdir2(dirname2(testFile), { recursive: true });
|
|
3544
|
+
await writeFile2(testFile, test);
|
|
3545
|
+
this.info(`Test [${relative22(app.basePath(), testFile)}] created successfully.`);
|
|
1599
3546
|
}
|
|
1600
3547
|
return 0;
|
|
1601
3548
|
}).purpose("Create a new Live component");
|
|
@@ -1603,7 +3550,7 @@ function registerLiveCommands(kernel, app) {
|
|
|
1603
3550
|
var UPDATE_URI = "/oriel/live/update";
|
|
1604
3551
|
var LiveServiceProvider = class extends ServiceProvider {
|
|
1605
3552
|
register() {
|
|
1606
|
-
this.mergeConfigFrom({ class_path: this.app.appPath("Live"), layout: "components.layouts.app" }, "live");
|
|
3553
|
+
this.mergeConfigFrom({ class_path: this.app.appPath("Live"), layout: "components.layouts.app", persistent_middleware: [...PERSISTENT_MIDDLEWARE] }, "live");
|
|
1607
3554
|
this.app.singleton(LiveComponentManager, () => new LiveComponentManager(this.app));
|
|
1608
3555
|
this.app.singleton(LiveManager, (container) => container.make(LiveComponentManager));
|
|
1609
3556
|
}
|
|
@@ -1623,7 +3570,7 @@ var LiveServiceProvider = class extends ServiceProvider {
|
|
|
1623
3570
|
// ../Testing/dist/index.js
|
|
1624
3571
|
import { AssertionError as AssertionError2 } from "assert";
|
|
1625
3572
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
1626
|
-
import { existsSync as
|
|
3573
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1627
3574
|
import { join as join6 } from "path";
|
|
1628
3575
|
import { createHmac as createHmac2 } from "crypto";
|
|
1629
3576
|
import { randomUUID } from "crypto";
|
|
@@ -1649,7 +3596,7 @@ function format(value) {
|
|
|
1649
3596
|
return String(value);
|
|
1650
3597
|
}
|
|
1651
3598
|
}
|
|
1652
|
-
function
|
|
3599
|
+
function escapeHtml2(value) {
|
|
1653
3600
|
return value.replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]);
|
|
1654
3601
|
}
|
|
1655
3602
|
function stripTags(html) {
|
|
@@ -2162,12 +4109,12 @@ function sizeOf(value) {
|
|
|
2162
4109
|
function toPlain(value) {
|
|
2163
4110
|
return value === void 0 ? void 0 : JSON.parse(JSON.stringify(value));
|
|
2164
4111
|
}
|
|
2165
|
-
function inertiaPageOf(
|
|
4112
|
+
function inertiaPageOf(response2) {
|
|
2166
4113
|
let page;
|
|
2167
4114
|
try {
|
|
2168
|
-
if (
|
|
4115
|
+
if (response2.headers.has("X-Inertia")) page = JSON.parse(response2.content);
|
|
2169
4116
|
else {
|
|
2170
|
-
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);
|
|
2171
4118
|
page = match === null ? void 0 : JSON.parse(match[1]);
|
|
2172
4119
|
}
|
|
2173
4120
|
} catch {
|
|
@@ -2180,11 +4127,11 @@ function inertiaPageOf(response) {
|
|
|
2180
4127
|
var AssertableInertia = class _AssertableInertia extends AssertableJson {
|
|
2181
4128
|
#page;
|
|
2182
4129
|
#response;
|
|
2183
|
-
static fromTestResponse(
|
|
2184
|
-
const page = inertiaPageOf(
|
|
4130
|
+
static fromTestResponse(response2) {
|
|
4131
|
+
const page = inertiaPageOf(response2);
|
|
2185
4132
|
const instance = new _AssertableInertia(page.props);
|
|
2186
4133
|
instance.#page = page;
|
|
2187
|
-
instance.#response =
|
|
4134
|
+
instance.#response = response2;
|
|
2188
4135
|
return instance;
|
|
2189
4136
|
}
|
|
2190
4137
|
/** Scopes are plain AssertableJson: the page-level methods only make sense at the root. */
|
|
@@ -2204,7 +4151,7 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
|
|
|
2204
4151
|
if (shouldExist === true || shouldExist === void 0 && config?.get("inertia.testing.ensure_pages_exist", true) !== false) {
|
|
2205
4152
|
const paths = config?.get("inertia.pages.paths", []) ?? [];
|
|
2206
4153
|
const extensions = config?.get("inertia.pages.extensions", []) ?? [];
|
|
2207
|
-
const exists = paths.some((path) => extensions.some((extension) =>
|
|
4154
|
+
const exists = paths.some((path) => extensions.some((extension) => existsSync6(join6(path, `${value}.${extension}`))));
|
|
2208
4155
|
if (!exists) fail2(`Inertia page component file [${value}] does not exist.`);
|
|
2209
4156
|
}
|
|
2210
4157
|
return this;
|
|
@@ -2253,8 +4200,8 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
|
|
|
2253
4200
|
if (onlyList !== void 0 || exceptList !== void 0) headers["X-Inertia-Partial-Component"] = this.page.component;
|
|
2254
4201
|
if (onlyList !== void 0) headers["X-Inertia-Partial-Data"] = onlyList;
|
|
2255
4202
|
if (exceptList !== void 0) headers["X-Inertia-Partial-Except"] = exceptList;
|
|
2256
|
-
const
|
|
2257
|
-
const reloaded = _AssertableInertia.fromTestResponse(
|
|
4203
|
+
const response2 = await client.request("GET", this.page.url, { headers });
|
|
4204
|
+
const reloaded = _AssertableInertia.fromTestResponse(response2);
|
|
2258
4205
|
reloaded.component(this.page.component, false).url(this.page.url).version(this.page.version);
|
|
2259
4206
|
await callback?.(reloaded);
|
|
2260
4207
|
return this;
|
|
@@ -2349,8 +4296,8 @@ function isExpired(cookie, now = Date.now()) {
|
|
|
2349
4296
|
if (cookie.maxAge !== void 0 && !Number.isNaN(cookie.maxAge)) return cookie.maxAge <= 0;
|
|
2350
4297
|
return cookie.expires !== void 0 && cookie.expires.getTime() <= now;
|
|
2351
4298
|
}
|
|
2352
|
-
function responseCookies(
|
|
2353
|
-
return
|
|
4299
|
+
function responseCookies(response2) {
|
|
4300
|
+
return response2.headers.getSetCookie().map(parseSetCookie);
|
|
2354
4301
|
}
|
|
2355
4302
|
function cookieHeader(cookies) {
|
|
2356
4303
|
return [...cookies].map(([name, value]) => `${encodeURIComponent(name)}=${encodeURIComponent(value)}`).join("; ");
|
|
@@ -2367,8 +4314,8 @@ var TestResponse = class _TestResponse {
|
|
|
2367
4314
|
context;
|
|
2368
4315
|
decoded;
|
|
2369
4316
|
/** Reads the body once, so every assertion is synchronous. */
|
|
2370
|
-
static async from(
|
|
2371
|
-
return new _TestResponse(
|
|
4317
|
+
static async from(response2, context = {}) {
|
|
4318
|
+
return new _TestResponse(response2, await response2.text(), context);
|
|
2372
4319
|
}
|
|
2373
4320
|
get status() {
|
|
2374
4321
|
return this.baseResponse.status;
|
|
@@ -2860,11 +4807,11 @@ ${JSON.stringify(errors, null, 2)}`, { actual: errors, expected: {} });
|
|
|
2860
4807
|
return Arr.get(this.viewDataOf(this.ensureView()), key);
|
|
2861
4808
|
}
|
|
2862
4809
|
// Internals ---------------------------------------------------------------------------------------------
|
|
2863
|
-
absolute(
|
|
4810
|
+
absolute(url2) {
|
|
2864
4811
|
try {
|
|
2865
|
-
return new URL(
|
|
4812
|
+
return new URL(url2, this.context.request?.url ?? "http://localhost").href;
|
|
2866
4813
|
} catch {
|
|
2867
|
-
return
|
|
4814
|
+
return url2;
|
|
2868
4815
|
}
|
|
2869
4816
|
}
|
|
2870
4817
|
excerpt(text = this.content) {
|
|
@@ -2919,7 +4866,7 @@ ${this.excerpt()}`;
|
|
|
2919
4866
|
return decryptCookie(app.make(Encrypter), cookie.name, cookie.value);
|
|
2920
4867
|
}
|
|
2921
4868
|
needles(value, escape) {
|
|
2922
|
-
return [value].flat().map((needle) => escape ?
|
|
4869
|
+
return [value].flat().map((needle) => escape ? escapeHtml2(needle) : needle);
|
|
2923
4870
|
}
|
|
2924
4871
|
inOrder(haystack, needles) {
|
|
2925
4872
|
let position = 0;
|
|
@@ -2964,9 +4911,9 @@ function recordingViewFactory(factory, views) {
|
|
|
2964
4911
|
}
|
|
2965
4912
|
function recordingExceptionHandler(handler, record) {
|
|
2966
4913
|
return forward(handler, {
|
|
2967
|
-
async render(
|
|
4914
|
+
async render(request2, error) {
|
|
2968
4915
|
record(error);
|
|
2969
|
-
return handler.render(
|
|
4916
|
+
return handler.render(request2, error);
|
|
2970
4917
|
}
|
|
2971
4918
|
});
|
|
2972
4919
|
}
|
|
@@ -3004,13 +4951,13 @@ var HttpClient = class {
|
|
|
3004
4951
|
/** Requests whose actingAs users reached the guards (the AuthFactory they went through). */
|
|
3005
4952
|
actingAsApplied = /* @__PURE__ */ new WeakMap();
|
|
3006
4953
|
async request(method, uri, options = {}) {
|
|
3007
|
-
let
|
|
3008
|
-
if (!this.followRedirects) return
|
|
3009
|
-
for (let hops = 0; isRedirect(
|
|
3010
|
-
if (hops >= MAX_REDIRECTS) fail2(`Too many redirects (more than ${MAX_REDIRECTS}), last location [${
|
|
3011
|
-
|
|
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") ?? "/", {}));
|
|
3012
4959
|
}
|
|
3013
|
-
return
|
|
4960
|
+
return response2;
|
|
3014
4961
|
}
|
|
3015
4962
|
/** Runs `fn` in a scope prepared like a request's (carried session, actingAs users), e.g. for assertAuthenticated(). */
|
|
3016
4963
|
async inScope(fn) {
|
|
@@ -3021,23 +4968,48 @@ var HttpClient = class {
|
|
|
3021
4968
|
return fn(scope);
|
|
3022
4969
|
});
|
|
3023
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
|
+
}
|
|
3024
4996
|
/** Runs the request through the app's real HttpKernel, in a request scope this client owns. */
|
|
3025
|
-
async send(
|
|
4997
|
+
async send(request2) {
|
|
3026
4998
|
const app = this.app;
|
|
3027
4999
|
await app.boot();
|
|
3028
|
-
|
|
5000
|
+
setClientAddress2(request2, "127.0.0.1");
|
|
3029
5001
|
const scope = app.createScope();
|
|
3030
|
-
const context = { request, app, client: this };
|
|
3031
|
-
const
|
|
5002
|
+
const context = { request: request2, app, client: this };
|
|
5003
|
+
const response2 = await scope.run(async () => {
|
|
3032
5004
|
await this.beforeRequest(scope, context);
|
|
3033
5005
|
this.previousUrl = void 0;
|
|
3034
|
-
return scope.make(HttpKernel).handle(
|
|
5006
|
+
return scope.make(HttpKernel).handle(request2);
|
|
3035
5007
|
});
|
|
3036
5008
|
await scope.run(() => this.afterRequest(scope, context));
|
|
3037
|
-
this.rememberCookies(
|
|
3038
|
-
await app.make(HttpKernel).terminate?.(
|
|
5009
|
+
this.rememberCookies(response2);
|
|
5010
|
+
await app.make(HttpKernel).terminate?.(request2, response2);
|
|
3039
5011
|
if (!this.handleExceptions && context.exception !== void 0) throw context.exception;
|
|
3040
|
-
return TestResponse.from(
|
|
5012
|
+
return TestResponse.from(response2, context);
|
|
3041
5013
|
}
|
|
3042
5014
|
/** Prepares the request scope: recorders, the carried session and actingAs users. */
|
|
3043
5015
|
beforeRequest(scope, context) {
|
|
@@ -3092,12 +5064,12 @@ var HttpClient = class {
|
|
|
3092
5064
|
}
|
|
3093
5065
|
buildRequest(method, uri, options) {
|
|
3094
5066
|
const verb = method.toUpperCase();
|
|
3095
|
-
const
|
|
5067
|
+
const url2 = new URL(uri, this.baseUrl());
|
|
3096
5068
|
const headers = new Headers({ ...this.headers, ...options.headers });
|
|
3097
5069
|
let body;
|
|
3098
5070
|
const data = options.data ?? {};
|
|
3099
5071
|
if (verb === "GET" || verb === "HEAD") {
|
|
3100
|
-
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);
|
|
3101
5073
|
} else if (options.json) {
|
|
3102
5074
|
body = JSON.stringify(data);
|
|
3103
5075
|
} else if (Object.keys(data).length > 0) {
|
|
@@ -3116,7 +5088,7 @@ var HttpClient = class {
|
|
|
3116
5088
|
}
|
|
3117
5089
|
const cookies = this.cookieHeader();
|
|
3118
5090
|
if (cookies !== "") headers.set("Cookie", cookies);
|
|
3119
|
-
return new Request(
|
|
5091
|
+
return new Request(url2, { method: verb, headers, body });
|
|
3120
5092
|
}
|
|
3121
5093
|
baseUrl() {
|
|
3122
5094
|
const configured = this.app.bound(ConfigRepository) ? this.app.make(ConfigRepository).get("app.url") : void 0;
|
|
@@ -3131,8 +5103,8 @@ var HttpClient = class {
|
|
|
3131
5103
|
return cookieHeader(values);
|
|
3132
5104
|
}
|
|
3133
5105
|
/** Like a browser: cookies the app sets are sent back on later requests in the same test; deleted ones are dropped. */
|
|
3134
|
-
rememberCookies(
|
|
3135
|
-
for (const cookie of responseCookies(
|
|
5106
|
+
rememberCookies(response2) {
|
|
5107
|
+
for (const cookie of responseCookies(response2)) {
|
|
3136
5108
|
if (isExpired(cookie)) this.cookies.delete(cookie.name);
|
|
3137
5109
|
else this.cookies.set(cookie.name, { value: cookie.value, encrypt: false });
|
|
3138
5110
|
}
|
|
@@ -3141,9 +5113,9 @@ var HttpClient = class {
|
|
|
3141
5113
|
function isRedirect(status) {
|
|
3142
5114
|
return status >= 300 && status < 400 && status !== 304;
|
|
3143
5115
|
}
|
|
3144
|
-
function
|
|
5116
|
+
function setClientAddress2(request2, address) {
|
|
3145
5117
|
const g = globalThis;
|
|
3146
|
-
(g[CLIENT_ADDRESS] ??= /* @__PURE__ */ new WeakMap()).set(
|
|
5118
|
+
(g[CLIENT_ADDRESS] ??= /* @__PURE__ */ new WeakMap()).set(request2, address);
|
|
3147
5119
|
}
|
|
3148
5120
|
function flatten(data, prefix = "") {
|
|
3149
5121
|
const out = [];
|
|
@@ -3239,11 +5211,43 @@ var InteractsWithApplication = class {
|
|
|
3239
5211
|
if (global === app) Container.setInstance(void 0);
|
|
3240
5212
|
}
|
|
3241
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
|
+
}
|
|
3242
5223
|
/** Runs in tearDown(), before the application is thrown away (in registration order). */
|
|
3243
5224
|
beforeApplicationDestroyed(callback) {
|
|
3244
5225
|
this.destroying.push(callback);
|
|
3245
5226
|
}
|
|
3246
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
|
+
};
|
|
3247
5251
|
async function disconnectAll(db) {
|
|
3248
5252
|
const manager = db;
|
|
3249
5253
|
if (typeof manager.disconnectAll === "function") await manager.disconnectAll();
|
|
@@ -3258,6 +5262,11 @@ var MakesHttpRequests = class extends InteractsWithApplication {
|
|
|
3258
5262
|
createHttpClient() {
|
|
3259
5263
|
return new HttpClient(this.app);
|
|
3260
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
|
+
}
|
|
3261
5270
|
async tearDown() {
|
|
3262
5271
|
try {
|
|
3263
5272
|
await super.tearDown();
|
|
@@ -3273,8 +5282,8 @@ var MakesHttpRequests = class extends InteractsWithApplication {
|
|
|
3273
5282
|
this.http.headers[name] = value;
|
|
3274
5283
|
return this;
|
|
3275
5284
|
}
|
|
3276
|
-
withToken(
|
|
3277
|
-
return this.withHeader("Authorization", `${type} ${
|
|
5285
|
+
withToken(token2, type = "Bearer") {
|
|
5286
|
+
return this.withHeader("Authorization", `${type} ${token2}`);
|
|
3278
5287
|
}
|
|
3279
5288
|
withBasicAuth(username, password) {
|
|
3280
5289
|
return this.withToken(Buffer.from(`${username}:${password}`).toString("base64"), "Basic");
|
|
@@ -3305,9 +5314,9 @@ var MakesHttpRequests = class extends InteractsWithApplication {
|
|
|
3305
5314
|
return this;
|
|
3306
5315
|
}
|
|
3307
5316
|
/** Sets the Referer and the session's previous URL, so redirect()->back() goes there. */
|
|
3308
|
-
from(
|
|
3309
|
-
this.http.previousUrl =
|
|
3310
|
-
return this.withHeader("Referer",
|
|
5317
|
+
from(url2) {
|
|
5318
|
+
this.http.previousUrl = url2;
|
|
5319
|
+
return this.withHeader("Referer", url2);
|
|
3311
5320
|
}
|
|
3312
5321
|
/** Puts data in the session for the following requests (Laravel's withSession()). */
|
|
3313
5322
|
withSession(data) {
|
|
@@ -3457,13 +5466,13 @@ var InteractsWithConsole = class extends InteractsWithDatabase {
|
|
|
3457
5466
|
};
|
|
3458
5467
|
var TestCase = class extends InteractsWithConsole {
|
|
3459
5468
|
};
|
|
3460
|
-
function install(
|
|
3461
|
-
facade(
|
|
5469
|
+
function install(token2, fake) {
|
|
5470
|
+
facade(token2).swap(fake);
|
|
3462
5471
|
return fake;
|
|
3463
5472
|
}
|
|
3464
|
-
function bound(
|
|
5473
|
+
function bound(token2) {
|
|
3465
5474
|
const container = currentContainer();
|
|
3466
|
-
return container !== void 0 && container.bound(
|
|
5475
|
+
return container !== void 0 && container.bound(token2) ? container.make(token2) : void 0;
|
|
3467
5476
|
}
|
|
3468
5477
|
function nameOf(target2) {
|
|
3469
5478
|
if (typeof target2 === "string") return target2;
|
|
@@ -4159,8 +6168,8 @@ var AnonymousNotifiable = class {
|
|
|
4159
6168
|
}
|
|
4160
6169
|
dispatcher;
|
|
4161
6170
|
routes = /* @__PURE__ */ new Map();
|
|
4162
|
-
route(channel,
|
|
4163
|
-
this.routes.set(channel,
|
|
6171
|
+
route(channel, route2) {
|
|
6172
|
+
this.routes.set(channel, route2);
|
|
4164
6173
|
return this;
|
|
4165
6174
|
}
|
|
4166
6175
|
routeNotificationFor(channel) {
|
|
@@ -4204,8 +6213,8 @@ var NotificationFake = class _NotificationFake {
|
|
|
4204
6213
|
async sendNow(notifiables, notification, channels) {
|
|
4205
6214
|
this.record(notifiables, notification, channels);
|
|
4206
6215
|
}
|
|
4207
|
-
route(channel,
|
|
4208
|
-
return new AnonymousNotifiable(this).route(channel,
|
|
6216
|
+
route(channel, route2) {
|
|
6217
|
+
return new AnonymousNotifiable(this).route(channel, route2);
|
|
4209
6218
|
}
|
|
4210
6219
|
/** The Notifications provider calls this in boot(); a fake has no channels to register. */
|
|
4211
6220
|
extend(_channel, _factory) {
|
|
@@ -4626,6 +6635,70 @@ export {
|
|
|
4626
6635
|
viteReactRefresh,
|
|
4627
6636
|
Application2 as Application,
|
|
4628
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,
|
|
4629
6702
|
On,
|
|
4630
6703
|
Locked,
|
|
4631
6704
|
Computed,
|
|
@@ -4638,10 +6711,13 @@ export {
|
|
|
4638
6711
|
PropertyNotUpdatableError,
|
|
4639
6712
|
LiveComponentNotFoundError,
|
|
4640
6713
|
InvalidLiveRequestError,
|
|
6714
|
+
TestRequestScope,
|
|
4641
6715
|
Testable,
|
|
4642
6716
|
live,
|
|
4643
6717
|
Live,
|
|
4644
6718
|
componentName,
|
|
6719
|
+
PERSISTENT_MIDDLEWARE,
|
|
6720
|
+
persistentMiddleware,
|
|
4645
6721
|
LiveComponentManager,
|
|
4646
6722
|
UPDATE_URI,
|
|
4647
6723
|
LiveServiceProvider,
|
|
@@ -4690,4 +6766,4 @@ export {
|
|
|
4690
6766
|
StorageFake,
|
|
4691
6767
|
FakeFile
|
|
4692
6768
|
};
|
|
4693
|
-
//# sourceMappingURL=chunk-
|
|
6769
|
+
//# sourceMappingURL=chunk-SBZQ6L3Z.js.map
|