@orieljs/framework 0.1.0-alpha.3 → 0.1.0-alpha.5
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-A5XRLEWM.js} +3 -3
- package/dist/{chunk-3ZRPBRAE.js → chunk-BSQKOLSK.js} +5 -1
- package/dist/chunk-BSQKOLSK.js.map +1 -0
- package/dist/{chunk-IRHK7GH7.js → chunk-PM5YTG57.js} +138 -3
- package/dist/chunk-PM5YTG57.js.map +1 -0
- package/dist/{chunk-I2OMKIQZ.js → chunk-WNJP46B7.js} +2770 -204
- package/dist/chunk-WNJP46B7.js.map +1 -0
- package/dist/contracts.d.ts +197 -132
- package/dist/contracts.js +5 -1
- package/dist/index.d.ts +920 -323
- package/dist/index.js +1340 -2122
- package/dist/index.js.map +1 -1
- package/dist/jsx-runtime.js +3 -3
- package/dist/{testing--F6FODc_.d.ts → testing-BErengYY.d.ts} +290 -91
- 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-A5XRLEWM.js.map} +0 -0
|
@@ -8,8 +8,9 @@ import {
|
|
|
8
8
|
Str,
|
|
9
9
|
currentContainer,
|
|
10
10
|
facade,
|
|
11
|
+
fileType_exports,
|
|
11
12
|
inject
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-PM5YTG57.js";
|
|
13
14
|
import {
|
|
14
15
|
Application,
|
|
15
16
|
AuthFactory,
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
ClassRegistry,
|
|
18
19
|
ConfigRepository,
|
|
19
20
|
ConsoleKernel,
|
|
21
|
+
CookieJar,
|
|
20
22
|
CurrentRoute,
|
|
21
23
|
DatabaseManager,
|
|
22
24
|
Encrypter,
|
|
@@ -27,18 +29,23 @@ import {
|
|
|
27
29
|
HttpKernel,
|
|
28
30
|
HttpRequest,
|
|
29
31
|
LiveManager,
|
|
32
|
+
LogManager,
|
|
30
33
|
MailManager,
|
|
31
34
|
MiddlewareCallbacks,
|
|
32
35
|
NotificationDispatcher,
|
|
36
|
+
PaginationState,
|
|
37
|
+
PipelineFactory,
|
|
33
38
|
QueueManager,
|
|
34
39
|
Redirector,
|
|
40
|
+
ResponseFactory,
|
|
35
41
|
Router,
|
|
36
42
|
RoutingPaths,
|
|
37
43
|
Session,
|
|
38
44
|
UrlGenerator,
|
|
39
45
|
ValidationFactory,
|
|
40
|
-
ViewFactory
|
|
41
|
-
|
|
46
|
+
ViewFactory,
|
|
47
|
+
token
|
|
48
|
+
} from "./chunk-BSQKOLSK.js";
|
|
42
49
|
|
|
43
50
|
// ../Config/dist/index.js
|
|
44
51
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -88,12 +95,20 @@ function env(key, defaultValue) {
|
|
|
88
95
|
const quoted = /^(["'])(.*)\1$/s.exec(raw);
|
|
89
96
|
return quoted ? quoted[2] : raw;
|
|
90
97
|
}
|
|
98
|
+
function singleQuoted(source) {
|
|
99
|
+
const keys = /* @__PURE__ */ new Set();
|
|
100
|
+
for (const match of source.matchAll(/^[ \t]*(?:export[ \t]+)?([\w.-]+)[ \t]*=[ \t]*'/gm)) keys.add(match[1]);
|
|
101
|
+
return keys;
|
|
102
|
+
}
|
|
91
103
|
function loadEnvironment(basePath) {
|
|
92
104
|
const appEnv = process.env.APP_ENV;
|
|
93
105
|
const specific = appEnv ? join(basePath, `.env.${appEnv}`) : void 0;
|
|
94
106
|
const file = specific && existsSync(specific) ? specific : join(basePath, ".env");
|
|
95
107
|
if (!existsSync(file)) return void 0;
|
|
96
|
-
|
|
108
|
+
const source = readFileSync(file, "utf8");
|
|
109
|
+
const literal = singleQuoted(source);
|
|
110
|
+
for (const [key, raw] of Object.entries(parse(source))) {
|
|
111
|
+
const value = literal.has(key) ? raw : raw.replace(/\$\{([A-Za-z_][A-Za-z0-9_.]*)\}/g, (match, name) => process.env[name] ?? match);
|
|
97
112
|
if (process.env[key] === void 0) process.env[key] = value;
|
|
98
113
|
}
|
|
99
114
|
return file;
|
|
@@ -140,10 +155,11 @@ var ApplicationBuilder = class {
|
|
|
140
155
|
return this;
|
|
141
156
|
}
|
|
142
157
|
withRouting(options) {
|
|
143
|
-
const abs = (p) =>
|
|
158
|
+
const abs = (p) => isAbsolute(p) ? p : join4(this.app.basePath(), p);
|
|
159
|
+
const each = (p) => Array.isArray(p) ? p.map(abs) : abs(p);
|
|
144
160
|
const paths = { apiPrefix: options.apiPrefix ?? "api" };
|
|
145
|
-
if (options.web !== void 0) paths.web =
|
|
146
|
-
if (options.api !== void 0) paths.api =
|
|
161
|
+
if (options.web !== void 0) paths.web = each(options.web);
|
|
162
|
+
if (options.api !== void 0) paths.api = each(options.api);
|
|
147
163
|
if (options.console !== void 0) paths.console = abs(options.console);
|
|
148
164
|
if (options.health !== void 0) paths.health = options.health;
|
|
149
165
|
this.routing = paths;
|
|
@@ -323,9 +339,9 @@ var Vite = class {
|
|
|
323
339
|
const stylesheets = uniqueTags.filter((tag) => tag.startsWith("<link"));
|
|
324
340
|
const scripts = uniqueTags.filter((tag) => !tag.startsWith("<link"));
|
|
325
341
|
const seen = /* @__PURE__ */ new Set();
|
|
326
|
-
const uniquePreloads = preloads.filter(([,
|
|
327
|
-
const sorted = [...uniquePreloads.filter(([,
|
|
328
|
-
const preloadTags = sorted.map(([src,
|
|
342
|
+
const uniquePreloads = preloads.filter(([, url2]) => seen.has(url2) ? false : (seen.add(url2), true));
|
|
343
|
+
const sorted = [...uniquePreloads.filter(([, url2]) => CSS_PATH.test(url2)), ...uniquePreloads.filter(([, url2]) => !CSS_PATH.test(url2))];
|
|
344
|
+
const preloadTags = sorted.map(([src, url2, chunk2]) => this.#preloadTag(src, url2, chunk2, manifest));
|
|
329
345
|
return new ViteHtml(preloadTags.join("") + stylesheets.join("") + scripts.join(""));
|
|
330
346
|
}
|
|
331
347
|
/** Laravel's @viteReactRefresh: the React Refresh preamble while the dev server runs; nothing otherwise. */
|
|
@@ -410,41 +426,41 @@ var Vite = class {
|
|
|
410
426
|
}
|
|
411
427
|
return imports;
|
|
412
428
|
}
|
|
413
|
-
#tagForChunk(src,
|
|
414
|
-
if (CSS_PATH.test(
|
|
415
|
-
const attributes2 = this.#parseAttributes({ rel: "stylesheet", href:
|
|
429
|
+
#tagForChunk(src, url2, chunk2, manifest) {
|
|
430
|
+
if (CSS_PATH.test(url2)) {
|
|
431
|
+
const attributes2 = this.#parseAttributes({ rel: "stylesheet", href: url2, nonce: this.#nonce ?? false, ...this.#styleAttributes(src, url2, chunk2, manifest) });
|
|
416
432
|
return `<link ${attributes2.join(" ")} />`;
|
|
417
433
|
}
|
|
418
|
-
const attributes = this.#parseAttributes({ type: "module", src:
|
|
434
|
+
const attributes = this.#parseAttributes({ type: "module", src: url2, nonce: this.#nonce ?? false, ...this.#scriptAttributes(src, url2, chunk2, manifest) });
|
|
419
435
|
return `<script ${attributes.join(" ")}></script>`;
|
|
420
436
|
}
|
|
421
|
-
#preloadTag(src,
|
|
422
|
-
const css = CSS_PATH.test(
|
|
437
|
+
#preloadTag(src, url2, chunk2, manifest) {
|
|
438
|
+
const css = CSS_PATH.test(url2);
|
|
423
439
|
let attributes = {
|
|
424
440
|
rel: css ? "preload" : "modulepreload",
|
|
425
441
|
as: css ? "style" : "script",
|
|
426
|
-
href:
|
|
442
|
+
href: url2,
|
|
427
443
|
nonce: this.#nonce ?? false,
|
|
428
|
-
crossorigin: (css ? this.#styleAttributes(src,
|
|
444
|
+
crossorigin: (css ? this.#styleAttributes(src, url2, chunk2, manifest) : this.#scriptAttributes(src, url2, chunk2, manifest)).crossorigin ?? false
|
|
429
445
|
};
|
|
430
446
|
if (this.#integrityKey !== false) attributes.integrity = chunk2?.[this.#integrityKey] ?? false;
|
|
431
447
|
for (const resolver of this.#preloadResolvers) {
|
|
432
|
-
const resolved = resolver(src,
|
|
448
|
+
const resolved = resolver(src, url2, chunk2, manifest);
|
|
433
449
|
if (resolved === false) return "";
|
|
434
450
|
attributes = { ...attributes, ...resolved };
|
|
435
451
|
}
|
|
436
452
|
const { href: _href, ...rest } = attributes;
|
|
437
|
-
this.#preloaded[
|
|
453
|
+
this.#preloaded[url2] = this.#parseAttributes(rest);
|
|
438
454
|
return `<link ${this.#parseAttributes(attributes).join(" ")} />`;
|
|
439
455
|
}
|
|
440
|
-
#scriptAttributes(src,
|
|
456
|
+
#scriptAttributes(src, url2, chunk2, manifest) {
|
|
441
457
|
let attributes = this.#integrityKey !== false ? { integrity: chunk2?.[this.#integrityKey] ?? false } : {};
|
|
442
|
-
for (const resolver of this.#scriptResolvers) attributes = { ...attributes, ...resolver(src,
|
|
458
|
+
for (const resolver of this.#scriptResolvers) attributes = { ...attributes, ...resolver(src, url2, chunk2, manifest) };
|
|
443
459
|
return attributes;
|
|
444
460
|
}
|
|
445
|
-
#styleAttributes(src,
|
|
461
|
+
#styleAttributes(src, url2, chunk2, manifest) {
|
|
446
462
|
let attributes = this.#integrityKey !== false ? { integrity: chunk2?.[this.#integrityKey] ?? false } : {};
|
|
447
|
-
for (const resolver of this.#styleResolvers) attributes = { ...attributes, ...resolver(src,
|
|
463
|
+
for (const resolver of this.#styleResolvers) attributes = { ...attributes, ...resolver(src, url2, chunk2, manifest) };
|
|
448
464
|
return attributes;
|
|
449
465
|
}
|
|
450
466
|
/** false/null/undefined are dropped, true is a bare attribute, anything else name="value". */
|
|
@@ -476,7 +492,7 @@ var Application2 = class _Application extends Container {
|
|
|
476
492
|
this.scoped(Vite, () => new Vite(this));
|
|
477
493
|
}
|
|
478
494
|
base;
|
|
479
|
-
version = "0.1.0-alpha.
|
|
495
|
+
version = "0.1.0-alpha.5";
|
|
480
496
|
providers = [];
|
|
481
497
|
bootingCallbacks = [];
|
|
482
498
|
bootedCallbacks = [];
|
|
@@ -566,59 +582,1934 @@ var Application2 = class _Application extends Container {
|
|
|
566
582
|
terminating(callback) {
|
|
567
583
|
this.terminatingCallbacks.push(callback);
|
|
568
584
|
}
|
|
569
|
-
async handle(
|
|
585
|
+
async handle(request2) {
|
|
570
586
|
await this.boot();
|
|
571
587
|
const scope = this.createScope();
|
|
572
|
-
return scope.run(() => scope.make(HttpKernel).handle(
|
|
588
|
+
return scope.run(() => scope.make(HttpKernel).handle(request2));
|
|
573
589
|
}
|
|
574
590
|
async terminate() {
|
|
575
591
|
for (const callback of this.terminatingCallbacks) await callback();
|
|
576
592
|
}
|
|
577
|
-
async runBoot() {
|
|
578
|
-
for (const callback of this.bootingCallbacks) await callback(this);
|
|
579
|
-
for (let i = 0; i < this.providers.length; i++) await this.bootProvider(this.providers[i]);
|
|
580
|
-
this.hasBooted = true;
|
|
581
|
-
for (const callback of this.bootedCallbacks) await callback(this);
|
|
593
|
+
async runBoot() {
|
|
594
|
+
for (const callback of this.bootingCallbacks) await callback(this);
|
|
595
|
+
for (let i = 0; i < this.providers.length; i++) await this.bootProvider(this.providers[i]);
|
|
596
|
+
this.hasBooted = true;
|
|
597
|
+
for (const callback of this.bootedCallbacks) await callback(this);
|
|
598
|
+
}
|
|
599
|
+
async bootProvider(provider) {
|
|
600
|
+
try {
|
|
601
|
+
await provider.boot?.();
|
|
602
|
+
} catch (error) {
|
|
603
|
+
throw new ProviderBootError(provider.constructor.name, error);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
var ClosureKernel = class {
|
|
608
|
+
routes = /* @__PURE__ */ new Map();
|
|
609
|
+
on(method, path, handler) {
|
|
610
|
+
this.routes.set(`${method.toUpperCase()} ${path}`, handler);
|
|
611
|
+
return this;
|
|
612
|
+
}
|
|
613
|
+
async handle(request2) {
|
|
614
|
+
const handler = this.routes.get(`${request2.method} ${new URL(request2.url).pathname}`);
|
|
615
|
+
if (!handler) return new Response("Not Found", { status: 404, headers: { "content-type": "text/plain; charset=utf-8" } });
|
|
616
|
+
return toResponse(await handler(request2));
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
function toResponse(output) {
|
|
620
|
+
if (output instanceof Response) return output;
|
|
621
|
+
if (output === void 0 || output === null) return new Response(null, { status: 204 });
|
|
622
|
+
if (typeof output === "string") return new Response(output, { headers: { "content-type": "text/html; charset=utf-8" } });
|
|
623
|
+
return Response.json(output);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// ../Http/dist/index.js
|
|
627
|
+
import { createReadStream } from "fs";
|
|
628
|
+
import { stat } from "fs/promises";
|
|
629
|
+
import { basename } from "path";
|
|
630
|
+
import { Readable } from "stream";
|
|
631
|
+
import { readFile } from "fs/promises";
|
|
632
|
+
import { fileURLToPath } from "url";
|
|
633
|
+
import { createReadStream as createReadStream2 } from "fs";
|
|
634
|
+
import { stat as stat2 } from "fs/promises";
|
|
635
|
+
import { Readable as Readable2 } from "stream";
|
|
636
|
+
import { relative, resolve, sep } from "path";
|
|
637
|
+
import { createServer } from "http";
|
|
638
|
+
import { Readable as Readable3 } from "stream";
|
|
639
|
+
import { pipeline } from "stream/promises";
|
|
640
|
+
import { existsSync as existsSync4 } from "fs";
|
|
641
|
+
import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
642
|
+
import { dirname } from "path";
|
|
643
|
+
var HttpException = class extends Error {
|
|
644
|
+
constructor(status, message = "", headers = {}) {
|
|
645
|
+
super(message);
|
|
646
|
+
this.status = status;
|
|
647
|
+
this.headers = headers;
|
|
648
|
+
}
|
|
649
|
+
status;
|
|
650
|
+
headers;
|
|
651
|
+
kind = "Http";
|
|
652
|
+
name = "HttpException";
|
|
653
|
+
};
|
|
654
|
+
var HttpResponseException = class extends Error {
|
|
655
|
+
constructor(response2) {
|
|
656
|
+
super(`HTTP response exception (${response2.status}).`);
|
|
657
|
+
this.response = response2;
|
|
658
|
+
this.status = response2.status;
|
|
659
|
+
}
|
|
660
|
+
response;
|
|
661
|
+
kind = "Http";
|
|
662
|
+
name = "HttpResponseException";
|
|
663
|
+
status;
|
|
664
|
+
};
|
|
665
|
+
var HttpServerError = class extends Error {
|
|
666
|
+
kind = "Server";
|
|
667
|
+
status = 500;
|
|
668
|
+
name = "HttpServerError";
|
|
669
|
+
};
|
|
670
|
+
function exceedsJsonDepth(text, maxDepth) {
|
|
671
|
+
let depth = 0;
|
|
672
|
+
let inString = false;
|
|
673
|
+
for (let i = 0; i < text.length; i++) {
|
|
674
|
+
const code = text.charCodeAt(i);
|
|
675
|
+
if (inString) {
|
|
676
|
+
if (code === 92) i++;
|
|
677
|
+
else if (code === 34) inString = false;
|
|
678
|
+
} else if (code === 34) inString = true;
|
|
679
|
+
else if (code === 123 || code === 91) {
|
|
680
|
+
if (++depth > maxDepth) return true;
|
|
681
|
+
} else if (code === 125 || code === 93) depth--;
|
|
682
|
+
}
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
async function readBody(raw, limit) {
|
|
686
|
+
if (raw.body === null) return new Uint8Array(0);
|
|
687
|
+
const declared = Number(raw.headers.get("content-length") ?? NaN);
|
|
688
|
+
if (limit !== null && Number.isFinite(declared) && declared > limit) {
|
|
689
|
+
await raw.body.cancel();
|
|
690
|
+
throw new HttpException(413, "Content Too Large");
|
|
691
|
+
}
|
|
692
|
+
const reader = raw.body.getReader();
|
|
693
|
+
const chunks = [];
|
|
694
|
+
let size = 0;
|
|
695
|
+
for (; ; ) {
|
|
696
|
+
const { done, value } = await reader.read();
|
|
697
|
+
if (done) break;
|
|
698
|
+
size += value.byteLength;
|
|
699
|
+
if (limit !== null && size > limit) {
|
|
700
|
+
await reader.cancel();
|
|
701
|
+
throw new HttpException(413, "Content Too Large");
|
|
702
|
+
}
|
|
703
|
+
chunks.push(value);
|
|
704
|
+
}
|
|
705
|
+
const out = new Uint8Array(size);
|
|
706
|
+
let offset = 0;
|
|
707
|
+
for (const chunk2 of chunks) {
|
|
708
|
+
out.set(chunk2, offset);
|
|
709
|
+
offset += chunk2.byteLength;
|
|
710
|
+
}
|
|
711
|
+
return out;
|
|
712
|
+
}
|
|
713
|
+
function requestScope() {
|
|
714
|
+
const container = currentContainer();
|
|
715
|
+
return container !== void 0 && container.bound(HttpRequest) ? container : void 0;
|
|
716
|
+
}
|
|
717
|
+
function currentRequest() {
|
|
718
|
+
return requestScope()?.make(HttpRequest);
|
|
719
|
+
}
|
|
720
|
+
function optionalSession() {
|
|
721
|
+
const scope = requestScope();
|
|
722
|
+
return scope?.bound(Session) ? scope.make(Session) : void 0;
|
|
723
|
+
}
|
|
724
|
+
function optionalCookieJar() {
|
|
725
|
+
const scope = requestScope();
|
|
726
|
+
return scope?.bound(CookieJar) ? scope.make(CookieJar) : void 0;
|
|
727
|
+
}
|
|
728
|
+
function serializeCookie(cookie) {
|
|
729
|
+
const parts = [`${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value)}`, `Path=${cookie.path}`];
|
|
730
|
+
if (cookie.domain !== void 0) parts.push(`Domain=${cookie.domain}`);
|
|
731
|
+
if (cookie.secure) parts.push("Secure");
|
|
732
|
+
if (cookie.httpOnly) parts.push("HttpOnly");
|
|
733
|
+
parts.push(`SameSite=${cookie.sameSite.charAt(0).toUpperCase()}${cookie.sameSite.slice(1)}`);
|
|
734
|
+
if (cookie.minutes < 0) parts.push("Max-Age=0", "Expires=Thu, 01 Jan 1970 00:00:00 GMT");
|
|
735
|
+
else if (cookie.minutes > 0) parts.push(`Max-Age=${Math.round(cookie.minutes * 60)}`);
|
|
736
|
+
return parts.join("; ");
|
|
737
|
+
}
|
|
738
|
+
var HttpRedirectResponse = class {
|
|
739
|
+
constructor(targetUrl, status = 302, headers) {
|
|
740
|
+
this.targetUrl = targetUrl;
|
|
741
|
+
this.status = status;
|
|
742
|
+
this.#headers = new Headers(headers);
|
|
743
|
+
}
|
|
744
|
+
targetUrl;
|
|
745
|
+
status;
|
|
746
|
+
#headers;
|
|
747
|
+
with(key, value) {
|
|
748
|
+
const session = optionalSession();
|
|
749
|
+
if (session === void 0) return this;
|
|
750
|
+
if (typeof key === "string") session.flash(key, value);
|
|
751
|
+
else for (const [name, item] of Object.entries(key)) session.flash(name, item);
|
|
752
|
+
return this;
|
|
753
|
+
}
|
|
754
|
+
/** Flashes `errors` as { [bag]: { field: string[] } }, keeping the other bags already flashed. */
|
|
755
|
+
withErrors(errors, bag = "default") {
|
|
756
|
+
const session = optionalSession();
|
|
757
|
+
if (session === void 0) return this;
|
|
758
|
+
const source = typeof errors.toObject === "function" ? errors.toObject() : errors;
|
|
759
|
+
const normalized = Object.fromEntries(
|
|
760
|
+
Object.entries(source).map(([field, messages]) => [field, Array.isArray(messages) ? [...messages] : [messages]])
|
|
761
|
+
);
|
|
762
|
+
const existing = session.get("errors", {});
|
|
763
|
+
session.flash("errors", { ...existing, [bag]: normalized });
|
|
764
|
+
return this;
|
|
765
|
+
}
|
|
766
|
+
/** Flashes `input`, or the current request's input (files are never part of input()). */
|
|
767
|
+
withInput(input) {
|
|
768
|
+
const session = optionalSession();
|
|
769
|
+
if (session === void 0) return this;
|
|
770
|
+
session.flashInput(input ?? currentRequest()?.input() ?? {});
|
|
771
|
+
return this;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Always a Set-Cookie header on this response (Laravel's RedirectResponse::withCookie). A bound CookieJar only
|
|
775
|
+
* supplies the defaults (path, domain, secure, same-site); queueing would lose the cookie outside the web group.
|
|
776
|
+
*/
|
|
777
|
+
withCookie(name, value, minutes = 0) {
|
|
778
|
+
const jar = optionalCookieJar();
|
|
779
|
+
const cookie = jar !== void 0 ? jar.make(name, value, minutes) : { name, value, minutes, path: "/", secure: false, httpOnly: true, sameSite: "lax" };
|
|
780
|
+
this.#headers.append("Set-Cookie", serializeCookie(cookie));
|
|
781
|
+
return this;
|
|
782
|
+
}
|
|
783
|
+
header(name, value) {
|
|
784
|
+
this.#headers.set(name, value);
|
|
785
|
+
return this;
|
|
786
|
+
}
|
|
787
|
+
toResponse() {
|
|
788
|
+
const headers = new Headers(this.#headers);
|
|
789
|
+
headers.set("Location", this.targetUrl);
|
|
790
|
+
return new Response(null, { status: this.status, headers });
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
function pathOf(url2) {
|
|
794
|
+
let parsed;
|
|
795
|
+
try {
|
|
796
|
+
parsed = new URL(url2, "http://localhost");
|
|
797
|
+
} catch {
|
|
798
|
+
return "/";
|
|
799
|
+
}
|
|
800
|
+
return `${parsed.pathname.replace(/^\/+/, "/")}${parsed.search}${parsed.hash}`;
|
|
801
|
+
}
|
|
802
|
+
var HttpRedirector = class {
|
|
803
|
+
to(path, status = 302, headers) {
|
|
804
|
+
return new HttpRedirectResponse(inject(UrlGenerator).to(path), status, headers);
|
|
805
|
+
}
|
|
806
|
+
route(name, parameters, status = 302) {
|
|
807
|
+
return new HttpRedirectResponse(inject(UrlGenerator).route(name, parameters), status);
|
|
808
|
+
}
|
|
809
|
+
back(fallback, status = 302) {
|
|
810
|
+
return new HttpRedirectResponse(inject(UrlGenerator).previous(fallback), status);
|
|
811
|
+
}
|
|
812
|
+
/** Pulls url.intended from the session (put there by guest()), else goes to `fallback`. */
|
|
813
|
+
intended(fallback = "/", status = 302) {
|
|
814
|
+
const path = optionalSession()?.pull("url.intended") ?? fallback;
|
|
815
|
+
return this.to(path, status);
|
|
816
|
+
}
|
|
817
|
+
away(url2, status = 302) {
|
|
818
|
+
return new HttpRedirectResponse(url2, status);
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Remembers where a guest was going (a GET page, else the previous URL), then redirects to `path`. Only the path
|
|
822
|
+
* is stored, so intended() goes back on whichever host serves the app, and never to another site's Referer.
|
|
823
|
+
*/
|
|
824
|
+
guest(path, status = 302) {
|
|
825
|
+
const session = optionalSession();
|
|
826
|
+
const request2 = currentRequest();
|
|
827
|
+
if (session !== void 0) {
|
|
828
|
+
const intended = request2 !== void 0 && request2.method() === "GET" && !request2.expectsJson() ? request2.fullUrl() : inject(UrlGenerator).previous();
|
|
829
|
+
session.put("url.intended", pathOf(intended));
|
|
830
|
+
}
|
|
831
|
+
return this.to(path, status);
|
|
832
|
+
}
|
|
833
|
+
setIntendedUrl(url2) {
|
|
834
|
+
optionalSession()?.put("url.intended", url2);
|
|
835
|
+
}
|
|
836
|
+
refresh(status = 302) {
|
|
837
|
+
return this.to(currentRequest()?.fullUrl() ?? inject(UrlGenerator).current(), status);
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
var KEY = /* @__PURE__ */ Symbol.for("oriel:http.client-address");
|
|
841
|
+
var store = globalThis[KEY] ??= /* @__PURE__ */ new WeakMap();
|
|
842
|
+
function setClientAddress(request2, address) {
|
|
843
|
+
if (address !== void 0 && address !== "") store.set(request2, address);
|
|
844
|
+
}
|
|
845
|
+
function clientAddress(request2) {
|
|
846
|
+
return store.get(request2);
|
|
847
|
+
}
|
|
848
|
+
var TYPES = {
|
|
849
|
+
avif: "image/avif",
|
|
850
|
+
css: "text/css",
|
|
851
|
+
csv: "text/csv",
|
|
852
|
+
doc: "application/msword",
|
|
853
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
854
|
+
gif: "image/gif",
|
|
855
|
+
gz: "application/gzip",
|
|
856
|
+
html: "text/html",
|
|
857
|
+
ico: "image/vnd.microsoft.icon",
|
|
858
|
+
jpg: "image/jpeg",
|
|
859
|
+
js: "text/javascript",
|
|
860
|
+
map: "application/json",
|
|
861
|
+
json: "application/json",
|
|
862
|
+
md: "text/markdown",
|
|
863
|
+
mp3: "audio/mpeg",
|
|
864
|
+
mp4: "video/mp4",
|
|
865
|
+
otf: "font/otf",
|
|
866
|
+
pdf: "application/pdf",
|
|
867
|
+
png: "image/png",
|
|
868
|
+
svg: "image/svg+xml",
|
|
869
|
+
ttf: "font/ttf",
|
|
870
|
+
txt: "text/plain",
|
|
871
|
+
wasm: "application/wasm",
|
|
872
|
+
webm: "video/webm",
|
|
873
|
+
webp: "image/webp",
|
|
874
|
+
woff: "font/woff",
|
|
875
|
+
woff2: "font/woff2",
|
|
876
|
+
xls: "application/vnd.ms-excel",
|
|
877
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
878
|
+
xml: "application/xml",
|
|
879
|
+
zip: "application/zip"
|
|
880
|
+
};
|
|
881
|
+
var ALIASES = { jpeg: "jpg", htm: "html", mjs: "js", text: "txt" };
|
|
882
|
+
var EXTENSIONS = Object.fromEntries(Object.entries(TYPES).map(([extension, type]) => [type, extension]));
|
|
883
|
+
function mimeFromExtension(pathOrExtension) {
|
|
884
|
+
const extension = pathOrExtension.slice(pathOrExtension.lastIndexOf(".") + 1).toLowerCase();
|
|
885
|
+
return TYPES[ALIASES[extension] ?? extension] ?? "application/octet-stream";
|
|
886
|
+
}
|
|
887
|
+
function extensionFromMime(type) {
|
|
888
|
+
return EXTENSIONS[type.split(";")[0].trim().toLowerCase()];
|
|
889
|
+
}
|
|
890
|
+
var NEVER_CLAIMED = /* @__PURE__ */ new Set([
|
|
891
|
+
...fileType_exports.BINARY_AND_MARKUP_TYPES.flatMap(({ extensions }) => extensions),
|
|
892
|
+
"jpe",
|
|
893
|
+
"svgz",
|
|
894
|
+
"xhtml",
|
|
895
|
+
"xht",
|
|
896
|
+
"shtml",
|
|
897
|
+
"xsl",
|
|
898
|
+
"xslt",
|
|
899
|
+
"mht",
|
|
900
|
+
"mhtml",
|
|
901
|
+
"js",
|
|
902
|
+
"mjs",
|
|
903
|
+
"rss",
|
|
904
|
+
"atom",
|
|
905
|
+
"rdf",
|
|
906
|
+
"mathml",
|
|
907
|
+
"mml",
|
|
908
|
+
"xul"
|
|
909
|
+
]);
|
|
910
|
+
var PLAIN_EXTENSION = /^[a-z0-9]+$/;
|
|
911
|
+
var FormUploadedFile = class _FormUploadedFile {
|
|
912
|
+
constructor(file, detected) {
|
|
913
|
+
this.file = file;
|
|
914
|
+
this.detected = detected;
|
|
915
|
+
this.clientName = file.name ?? "";
|
|
916
|
+
this.mimeType = file.type === "" ? "application/octet-stream" : file.type;
|
|
917
|
+
this.size = file.size;
|
|
918
|
+
}
|
|
919
|
+
file;
|
|
920
|
+
detected;
|
|
921
|
+
clientName;
|
|
922
|
+
mimeType;
|
|
923
|
+
size;
|
|
924
|
+
/**
|
|
925
|
+
* Reads the leading bytes first, so extension(), hashName() and store() name the file by its content. The request
|
|
926
|
+
* builds its uploads this way.
|
|
927
|
+
*/
|
|
928
|
+
static async fromFile(file) {
|
|
929
|
+
return new _FormUploadedFile(file, fileType_exports.detectFileType(new Uint8Array(await file.slice(0, fileType_exports.SNIFF_BYTES).arrayBuffer())));
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Laravel's extension(): the type the bytes show (guessExtension()), when they were read and match a known format.
|
|
933
|
+
* Text and bytes no format matches keep the client's claim (its MIME type, else its file name) when it is a plain
|
|
934
|
+
* extension, unless it names a format the bytes would have shown or one a browser renders or runs: otherwise 'txt'
|
|
935
|
+
* for text, '' for the rest. Unread (built directly), the client's claim when it is a plain extension, else ''.
|
|
936
|
+
*/
|
|
937
|
+
extension() {
|
|
938
|
+
const claimed = extensionFromMime(this.mimeType) ?? this.clientExtension();
|
|
939
|
+
const detected = this.detected;
|
|
940
|
+
if (detected === void 0) return PLAIN_EXTENSION.test(claimed) ? claimed : "";
|
|
941
|
+
if (detected !== fileType_exports.TEXT && detected.extensions.length > 0) return detected.extensions[0];
|
|
942
|
+
if (PLAIN_EXTENSION.test(claimed) && !NEVER_CLAIMED.has(claimed)) return claimed;
|
|
943
|
+
return detected === fileType_exports.TEXT ? "txt" : "";
|
|
944
|
+
}
|
|
945
|
+
clientExtension() {
|
|
946
|
+
const dot = this.clientName.lastIndexOf(".");
|
|
947
|
+
return dot === -1 ? "" : this.clientName.slice(dot + 1).toLowerCase();
|
|
948
|
+
}
|
|
949
|
+
/** A random 40-character name with the guessed extension. */
|
|
950
|
+
hashName() {
|
|
951
|
+
const extension = this.extension();
|
|
952
|
+
return extension === "" ? Str.random(40) : `${Str.random(40)}.${extension}`;
|
|
953
|
+
}
|
|
954
|
+
arrayBuffer() {
|
|
955
|
+
return this.file.arrayBuffer();
|
|
956
|
+
}
|
|
957
|
+
text() {
|
|
958
|
+
return this.file.text();
|
|
959
|
+
}
|
|
960
|
+
stream() {
|
|
961
|
+
return this.file.stream();
|
|
962
|
+
}
|
|
963
|
+
store(directory, disk) {
|
|
964
|
+
return inject(FilesystemManager).disk(disk).putFile(directory, this);
|
|
965
|
+
}
|
|
966
|
+
storeAs(directory, name, disk) {
|
|
967
|
+
return inject(FilesystemManager).disk(disk).putFileAs(directory, this, name);
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
var FORBIDDEN = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
971
|
+
var INTEGER_KEY = /^(?:0|[1-9]\d*)$/;
|
|
972
|
+
var isPlainObject = (value) => {
|
|
973
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
974
|
+
const prototype = Object.getPrototypeOf(value);
|
|
975
|
+
return prototype === Object.prototype || prototype === null;
|
|
976
|
+
};
|
|
977
|
+
function parseBracketKey(name) {
|
|
978
|
+
const open = name.indexOf("[");
|
|
979
|
+
if (open <= 0 || !name.endsWith("]")) return [name];
|
|
980
|
+
const segments = [name.slice(0, open)];
|
|
981
|
+
const rest = name.slice(open);
|
|
982
|
+
const matches2 = rest.match(/\[[^[\]]*\]/g);
|
|
983
|
+
if (matches2 === null || matches2.join("") !== rest) return [name];
|
|
984
|
+
for (const match of matches2) segments.push(match.slice(1, -1));
|
|
985
|
+
return segments;
|
|
986
|
+
}
|
|
987
|
+
function normalizeLists(value) {
|
|
988
|
+
if (Array.isArray(value)) return value.map(normalizeLists);
|
|
989
|
+
if (!isPlainObject(value)) return value;
|
|
990
|
+
const keys = Object.keys(value);
|
|
991
|
+
const out = {};
|
|
992
|
+
for (const key of keys) out[key] = normalizeLists(value[key]);
|
|
993
|
+
if (keys.length > 0 && keys.every((key, index) => key === String(index))) return keys.map((key) => out[key]);
|
|
994
|
+
return out;
|
|
995
|
+
}
|
|
996
|
+
function fromEntries(entries) {
|
|
997
|
+
const root = {};
|
|
998
|
+
const nextIndex = /* @__PURE__ */ new WeakMap();
|
|
999
|
+
for (const [name, value] of entries) {
|
|
1000
|
+
const segments = parseBracketKey(name);
|
|
1001
|
+
if (segments.some((segment) => FORBIDDEN.has(segment))) continue;
|
|
1002
|
+
let node = root;
|
|
1003
|
+
for (let i = 0; i < segments.length; i++) {
|
|
1004
|
+
let segment = segments[i];
|
|
1005
|
+
const next = nextIndex.get(node) ?? 0;
|
|
1006
|
+
if (segment === "") {
|
|
1007
|
+
segment = String(next);
|
|
1008
|
+
nextIndex.set(node, next + 1);
|
|
1009
|
+
} else if (INTEGER_KEY.test(segment) && Number(segment) >= next) {
|
|
1010
|
+
nextIndex.set(node, Number(segment) + 1);
|
|
1011
|
+
}
|
|
1012
|
+
if (i === segments.length - 1) {
|
|
1013
|
+
node[segment] = value;
|
|
1014
|
+
break;
|
|
1015
|
+
}
|
|
1016
|
+
if (!isPlainObject(node[segment])) node[segment] = {};
|
|
1017
|
+
node = node[segment];
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return normalizeLists(root);
|
|
1021
|
+
}
|
|
1022
|
+
function sanitize(value) {
|
|
1023
|
+
if (Array.isArray(value)) return value.map(sanitize);
|
|
1024
|
+
if (!isPlainObject(value)) return value;
|
|
1025
|
+
const out = {};
|
|
1026
|
+
for (const [key, item] of Object.entries(value)) if (!FORBIDDEN.has(key)) out[key] = sanitize(item);
|
|
1027
|
+
return out;
|
|
1028
|
+
}
|
|
1029
|
+
function mergeRecursive(base, override) {
|
|
1030
|
+
const out = { ...base };
|
|
1031
|
+
for (const [key, value] of Object.entries(override)) {
|
|
1032
|
+
if (FORBIDDEN.has(key)) continue;
|
|
1033
|
+
const current = out[key];
|
|
1034
|
+
out[key] = (isPlainObject(current) || Array.isArray(current)) && (isPlainObject(value) || Array.isArray(value)) ? normalizeLists(mergeRecursive({ ...current }, { ...value })) : value;
|
|
1035
|
+
}
|
|
1036
|
+
return out;
|
|
1037
|
+
}
|
|
1038
|
+
function isBlank(value) {
|
|
1039
|
+
if (value === null || value === void 0) return true;
|
|
1040
|
+
if (typeof value === "string") return value.trim() === "";
|
|
1041
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
1042
|
+
if (isPlainObject(value)) return Object.keys(value).length === 0;
|
|
1043
|
+
return false;
|
|
1044
|
+
}
|
|
1045
|
+
function transformLeaves(tree, transform, prefix = "") {
|
|
1046
|
+
const out = {};
|
|
1047
|
+
for (const [key, value] of Object.entries(tree)) out[key] = transformValue(`${prefix}${key}`, value, transform);
|
|
1048
|
+
return out;
|
|
1049
|
+
}
|
|
1050
|
+
function transformValue(path, value, transform) {
|
|
1051
|
+
if (Array.isArray(value)) return value.map((item, index) => transformValue(`${path}.${index}`, item, transform));
|
|
1052
|
+
if (isPlainObject(value)) return transformLeaves(value, transform, `${path}.`);
|
|
1053
|
+
return transform(path, value);
|
|
1054
|
+
}
|
|
1055
|
+
var TRUE_VALUES = /* @__PURE__ */ new Set(["1", "true", "on", "yes"]);
|
|
1056
|
+
var normalizeIp = (ip) => ip.trim().replace(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i, "$1");
|
|
1057
|
+
function ipv4ToNumber(ip) {
|
|
1058
|
+
const parts = ip.split(".");
|
|
1059
|
+
if (parts.length !== 4 || parts.some((part) => !/^\d{1,3}$/.test(part) || Number(part) > 255)) return void 0;
|
|
1060
|
+
return parts.reduce((total, part) => total * 256 + Number(part), 0);
|
|
1061
|
+
}
|
|
1062
|
+
function ipMatches(ip, patterns) {
|
|
1063
|
+
const address = normalizeIp(ip);
|
|
1064
|
+
return patterns.some((pattern) => {
|
|
1065
|
+
if (pattern === "*") return true;
|
|
1066
|
+
if (!pattern.includes("/")) return normalizeIp(pattern) === address;
|
|
1067
|
+
const [range, bits] = pattern.split("/");
|
|
1068
|
+
const base = ipv4ToNumber(range);
|
|
1069
|
+
const value = ipv4ToNumber(address);
|
|
1070
|
+
const size = Number(bits);
|
|
1071
|
+
if (base === void 0 || value === void 0 || !(size >= 0 && size <= 32)) return false;
|
|
1072
|
+
const mask = size === 0 ? 0 : ~0 << 32 - size >>> 0;
|
|
1073
|
+
return (base & mask) >>> 0 === (value & mask) >>> 0;
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
var IncomingRequest = class {
|
|
1077
|
+
constructor(raw, options = {}) {
|
|
1078
|
+
this.raw = raw;
|
|
1079
|
+
this.#url = new URL(raw.url);
|
|
1080
|
+
this.#trustedProxies = options.trustedProxies ?? [];
|
|
1081
|
+
this.#maxBodySize = options.maxBodySize === void 0 ? 8 * 1024 * 1024 : options.maxBodySize;
|
|
1082
|
+
this.#maxJsonDepth = options.maxJsonDepth === void 0 ? 512 : options.maxJsonDepth;
|
|
1083
|
+
this.#query = fromEntries(this.#url.searchParams.entries());
|
|
1084
|
+
this.#method = raw.method.toUpperCase();
|
|
1085
|
+
}
|
|
1086
|
+
raw;
|
|
1087
|
+
#url;
|
|
1088
|
+
#trustedProxies;
|
|
1089
|
+
#maxBodySize;
|
|
1090
|
+
#maxJsonDepth;
|
|
1091
|
+
#query;
|
|
1092
|
+
#body = {};
|
|
1093
|
+
#files = {};
|
|
1094
|
+
#content = "";
|
|
1095
|
+
#method;
|
|
1096
|
+
#cookies;
|
|
1097
|
+
#loading;
|
|
1098
|
+
#userResolver;
|
|
1099
|
+
/** Reads and parses the body (JSON, urlencoded or multipart) once, then applies method spoofing. */
|
|
1100
|
+
load() {
|
|
1101
|
+
return this.#loading ??= this.#load();
|
|
1102
|
+
}
|
|
1103
|
+
async #load() {
|
|
1104
|
+
const method = this.raw.method.toUpperCase();
|
|
1105
|
+
if (method !== "GET" && method !== "HEAD" && this.raw.body !== null) {
|
|
1106
|
+
const type = (this.raw.headers.get("content-type") ?? "").toLowerCase();
|
|
1107
|
+
const isJson = type.includes("/json") || type.includes("+json");
|
|
1108
|
+
const isUrlencoded = type.startsWith("application/x-www-form-urlencoded");
|
|
1109
|
+
const isMultipart = type.startsWith("multipart/form-data");
|
|
1110
|
+
if (isJson || isUrlencoded || isMultipart) {
|
|
1111
|
+
const bytes = await readBody(this.raw, this.#maxBodySize);
|
|
1112
|
+
if (isJson && this.#maxJsonDepth !== null && exceedsJsonDepth(new TextDecoder().decode(bytes), this.#maxJsonDepth)) {
|
|
1113
|
+
throw new HttpException(400, "The request body is nested too deeply.");
|
|
1114
|
+
}
|
|
1115
|
+
try {
|
|
1116
|
+
if (isJson) {
|
|
1117
|
+
this.#content = new TextDecoder().decode(bytes);
|
|
1118
|
+
this.#body = this.#parseJson(this.#content);
|
|
1119
|
+
} else if (isUrlencoded) {
|
|
1120
|
+
this.#content = new TextDecoder().decode(bytes);
|
|
1121
|
+
this.#body = fromEntries(new URLSearchParams(this.#content).entries());
|
|
1122
|
+
} else {
|
|
1123
|
+
await this.#parseForm(await new Response(bytes, { headers: { "content-type": this.raw.headers.get("content-type") ?? "" } }).formData());
|
|
1124
|
+
}
|
|
1125
|
+
} catch {
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
if (method === "POST") {
|
|
1130
|
+
const override = this.raw.headers.get("x-http-method-override") ?? this.#string(this.#body._method) ?? this.#string(this.#query._method);
|
|
1131
|
+
const spoofed = override?.toUpperCase();
|
|
1132
|
+
if (spoofed !== void 0 && /^[A-Z]+$/.test(spoofed) && !["GET", "HEAD", "CONNECT", "TRACE"].includes(spoofed)) {
|
|
1133
|
+
this.#method = spoofed;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
return this;
|
|
1137
|
+
}
|
|
1138
|
+
#parseJson(text) {
|
|
1139
|
+
let parsed;
|
|
1140
|
+
try {
|
|
1141
|
+
parsed = JSON.parse(text);
|
|
1142
|
+
} catch {
|
|
1143
|
+
return {};
|
|
1144
|
+
}
|
|
1145
|
+
if (Array.isArray(parsed)) return sanitize({ ...parsed });
|
|
1146
|
+
return isPlainObject(parsed) ? sanitize(parsed) : {};
|
|
1147
|
+
}
|
|
1148
|
+
async #parseForm(form) {
|
|
1149
|
+
const fields = [];
|
|
1150
|
+
const files = [];
|
|
1151
|
+
for (const [name, value] of form.entries()) {
|
|
1152
|
+
if (typeof value === "string") fields.push([name, value]);
|
|
1153
|
+
else if (!((value.name ?? "") === "" && value.size === 0)) files.push([name, await FormUploadedFile.fromFile(value)]);
|
|
1154
|
+
}
|
|
1155
|
+
this.#body = fromEntries(fields);
|
|
1156
|
+
this.#files = fromEntries(files);
|
|
1157
|
+
}
|
|
1158
|
+
#string(value) {
|
|
1159
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
1160
|
+
}
|
|
1161
|
+
method() {
|
|
1162
|
+
return this.#method;
|
|
1163
|
+
}
|
|
1164
|
+
isMethod(method) {
|
|
1165
|
+
return this.#method === method.toUpperCase();
|
|
1166
|
+
}
|
|
1167
|
+
/** The scheme and host, honouring X-Forwarded-Proto/Host from trusted proxies. */
|
|
1168
|
+
root() {
|
|
1169
|
+
const trusted = this.#fromTrustedProxy();
|
|
1170
|
+
const proto = trusted ? this.raw.headers.get("x-forwarded-proto")?.split(",")[0]?.trim() : void 0;
|
|
1171
|
+
const host = trusted ? this.raw.headers.get("x-forwarded-host")?.split(",")[0]?.trim() : void 0;
|
|
1172
|
+
const scheme = proto === "https" || proto === "http" ? proto : this.#url.protocol.slice(0, -1);
|
|
1173
|
+
return `${scheme}://${host !== void 0 && host !== "" ? host : this.#url.host}`;
|
|
1174
|
+
}
|
|
1175
|
+
/** The URL without the query string or a trailing slash. */
|
|
1176
|
+
url() {
|
|
1177
|
+
return `${this.root()}${this.#url.pathname}`.replace(/\/+$/, "");
|
|
1178
|
+
}
|
|
1179
|
+
fullUrl() {
|
|
1180
|
+
return this.#url.search === "" ? this.url() : `${this.url()}${this.#url.search}`;
|
|
1181
|
+
}
|
|
1182
|
+
/** 'posts/1', or '/' for the root. */
|
|
1183
|
+
path() {
|
|
1184
|
+
const trimmed = this.#url.pathname.replace(/^\/+|\/+$/g, "");
|
|
1185
|
+
return trimmed === "" ? "/" : trimmed;
|
|
1186
|
+
}
|
|
1187
|
+
decodedPath() {
|
|
1188
|
+
try {
|
|
1189
|
+
return decodeURIComponent(this.path());
|
|
1190
|
+
} catch {
|
|
1191
|
+
return this.path();
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
secure() {
|
|
1195
|
+
return this.root().startsWith("https:");
|
|
1196
|
+
}
|
|
1197
|
+
/** request.is('admin/*'): wildcard match against the decoded path. */
|
|
1198
|
+
is(...patterns) {
|
|
1199
|
+
const path = this.decodedPath();
|
|
1200
|
+
return patterns.some((pattern) => {
|
|
1201
|
+
const trimmed = pattern.replace(/^\/+|\/+$/g, "");
|
|
1202
|
+
return Str.is(trimmed === "" ? "/" : trimmed, path);
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
routeIs(...names) {
|
|
1206
|
+
const name = this.#currentRoute()?.route.getName();
|
|
1207
|
+
return name !== void 0 && names.some((pattern) => Str.is(pattern, name));
|
|
1208
|
+
}
|
|
1209
|
+
header(name, defaultValue) {
|
|
1210
|
+
return this.raw.headers.get(name) ?? defaultValue;
|
|
1211
|
+
}
|
|
1212
|
+
bearerToken() {
|
|
1213
|
+
const header = this.header("authorization", "");
|
|
1214
|
+
const position = header.toLowerCase().lastIndexOf("bearer ");
|
|
1215
|
+
if (position === -1) return void 0;
|
|
1216
|
+
const token2 = header.slice(position + 7).split(",")[0].trim();
|
|
1217
|
+
return token2 === "" ? void 0 : token2;
|
|
1218
|
+
}
|
|
1219
|
+
userAgent() {
|
|
1220
|
+
return this.header("user-agent");
|
|
1221
|
+
}
|
|
1222
|
+
/** The socket address, or the right-most untrusted X-Forwarded-For entry when the socket is a trusted proxy. */
|
|
1223
|
+
ip() {
|
|
1224
|
+
const socket = clientAddress(this.raw);
|
|
1225
|
+
if (socket === void 0) return void 0;
|
|
1226
|
+
const address = normalizeIp(socket);
|
|
1227
|
+
if (!this.#fromTrustedProxy()) return address;
|
|
1228
|
+
const chain = (this.raw.headers.get("x-forwarded-for") ?? "").split(",").map((entry) => normalizeIp(entry)).filter((entry) => entry !== "");
|
|
1229
|
+
for (let i = chain.length - 1; i >= 0; i--) if (!ipMatches(chain[i], this.#trustedProxies)) return chain[i];
|
|
1230
|
+
return chain[0] ?? address;
|
|
1231
|
+
}
|
|
1232
|
+
#fromTrustedProxy() {
|
|
1233
|
+
const socket = clientAddress(this.raw);
|
|
1234
|
+
return socket !== void 0 && this.#trustedProxies.length > 0 && ipMatches(socket, this.#trustedProxies);
|
|
1235
|
+
}
|
|
1236
|
+
ajax() {
|
|
1237
|
+
return this.header("x-requested-with") === "XMLHttpRequest";
|
|
1238
|
+
}
|
|
1239
|
+
pjax() {
|
|
1240
|
+
return this.header("x-pjax") === "true";
|
|
1241
|
+
}
|
|
1242
|
+
/** The first Accept type is JSON. */
|
|
1243
|
+
wantsJson() {
|
|
1244
|
+
const first = this.header("accept", "").split(",")[0].trim();
|
|
1245
|
+
return first.includes("/json") || first.includes("+json");
|
|
1246
|
+
}
|
|
1247
|
+
/** Laravel: (ajax and not pjax and accepts anything) or wantsJson. */
|
|
1248
|
+
expectsJson() {
|
|
1249
|
+
const first = this.header("accept", "").split(",")[0].split(";")[0].trim();
|
|
1250
|
+
const acceptsAny = first === "" || first === "*/*" || first === "*";
|
|
1251
|
+
return this.ajax() && !this.pjax() && acceptsAny || this.wantsJson();
|
|
1252
|
+
}
|
|
1253
|
+
/** The raw body text for JSON and urlencoded requests ('' otherwise; the raw stream stays unread for other types). */
|
|
1254
|
+
content() {
|
|
1255
|
+
return this.#content;
|
|
1256
|
+
}
|
|
1257
|
+
query(key, defaultValue) {
|
|
1258
|
+
return Arr.get(this.#query, key, defaultValue);
|
|
1259
|
+
}
|
|
1260
|
+
/** Body wins over the query string (PHP's $body + $query). Files are not input. */
|
|
1261
|
+
input(key, defaultValue) {
|
|
1262
|
+
return Arr.get({ ...this.#query, ...this.#body }, key, defaultValue);
|
|
1263
|
+
}
|
|
1264
|
+
/** Input plus files (array_replace_recursive). */
|
|
1265
|
+
all() {
|
|
1266
|
+
return mergeRecursive({ ...this.#query, ...this.#body }, this.#files);
|
|
1267
|
+
}
|
|
1268
|
+
only(...keys) {
|
|
1269
|
+
const all = this.all();
|
|
1270
|
+
const out = {};
|
|
1271
|
+
for (const key of keys.flat()) if (Arr.has(all, key)) Arr.set(out, key, Arr.get(all, key));
|
|
1272
|
+
return out;
|
|
1273
|
+
}
|
|
1274
|
+
except(...keys) {
|
|
1275
|
+
const all = structuredCloneInput(this.all());
|
|
1276
|
+
for (const key of keys.flat()) Arr.forget(all, key);
|
|
1277
|
+
return all;
|
|
1278
|
+
}
|
|
1279
|
+
has(...keys) {
|
|
1280
|
+
const all = this.all();
|
|
1281
|
+
return keys.flat().every((key) => Arr.has(all, key));
|
|
1282
|
+
}
|
|
1283
|
+
hasAny(...keys) {
|
|
1284
|
+
const all = this.all();
|
|
1285
|
+
return keys.flat().some((key) => Arr.has(all, key));
|
|
1286
|
+
}
|
|
1287
|
+
missing(...keys) {
|
|
1288
|
+
return !this.has(...keys);
|
|
1289
|
+
}
|
|
1290
|
+
filled(key) {
|
|
1291
|
+
return !isBlank(Arr.get(this.all(), key));
|
|
1292
|
+
}
|
|
1293
|
+
string(key, defaultValue = "") {
|
|
1294
|
+
const value = this.input(key);
|
|
1295
|
+
return value === void 0 || value === null ? defaultValue : String(value);
|
|
1296
|
+
}
|
|
1297
|
+
/** PHP's FILTER_VALIDATE_BOOLEAN: '1', 'true', 'on', 'yes' (any case) and true are true. */
|
|
1298
|
+
boolean(key, defaultValue = false) {
|
|
1299
|
+
const value = this.input(key);
|
|
1300
|
+
if (value === void 0) return defaultValue;
|
|
1301
|
+
if (typeof value === "boolean") return value;
|
|
1302
|
+
if (typeof value === "number") return value === 1;
|
|
1303
|
+
return typeof value === "string" && TRUE_VALUES.has(value.trim().toLowerCase());
|
|
1304
|
+
}
|
|
1305
|
+
/** PHP's intval: leading digits, else 0. */
|
|
1306
|
+
integer(key, defaultValue = 0) {
|
|
1307
|
+
const value = this.input(key);
|
|
1308
|
+
if (value === void 0) return defaultValue;
|
|
1309
|
+
if (typeof value === "number") return Number.isFinite(value) ? Math.trunc(value) : 0;
|
|
1310
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
|
1311
|
+
const parsed = Number.parseInt(String(value).trim(), 10);
|
|
1312
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
1313
|
+
}
|
|
1314
|
+
/** Adds or replaces top-level body values. */
|
|
1315
|
+
merge(values) {
|
|
1316
|
+
this.#body = { ...this.#body, ...sanitize(values) };
|
|
1317
|
+
return this;
|
|
1318
|
+
}
|
|
1319
|
+
/** Rewrites every scalar in the query and the body (TrimStrings and ConvertEmptyStringsToNull use this). */
|
|
1320
|
+
transformInput(transform) {
|
|
1321
|
+
this.#query = transformLeaves(this.#query, transform);
|
|
1322
|
+
this.#body = transformLeaves(this.#body, transform);
|
|
1323
|
+
return this;
|
|
1324
|
+
}
|
|
1325
|
+
file(key) {
|
|
1326
|
+
const value = Arr.get(this.#files, key);
|
|
1327
|
+
return value instanceof FormUploadedFile ? value : void 0;
|
|
1328
|
+
}
|
|
1329
|
+
hasFile(key) {
|
|
1330
|
+
const value = Arr.get(this.#files, key);
|
|
1331
|
+
return value instanceof FormUploadedFile || Array.isArray(value) && value.some((item) => item instanceof FormUploadedFile);
|
|
1332
|
+
}
|
|
1333
|
+
allFiles() {
|
|
1334
|
+
return { ...this.#files };
|
|
1335
|
+
}
|
|
1336
|
+
cookie(name) {
|
|
1337
|
+
return this.cookies()[name];
|
|
1338
|
+
}
|
|
1339
|
+
cookies() {
|
|
1340
|
+
if (this.#cookies === void 0) {
|
|
1341
|
+
const cookies = {};
|
|
1342
|
+
for (const part of (this.raw.headers.get("cookie") ?? "").split(";")) {
|
|
1343
|
+
const index = part.indexOf("=");
|
|
1344
|
+
if (index === -1) continue;
|
|
1345
|
+
const name = part.slice(0, index).trim();
|
|
1346
|
+
if (name === "" || Object.hasOwn(cookies, name)) continue;
|
|
1347
|
+
let value = part.slice(index + 1).trim();
|
|
1348
|
+
if (value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
|
|
1349
|
+
try {
|
|
1350
|
+
cookies[name] = decodeURIComponent(value);
|
|
1351
|
+
} catch {
|
|
1352
|
+
cookies[name] = value;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
this.#cookies = cookies;
|
|
1356
|
+
}
|
|
1357
|
+
return { ...this.#cookies };
|
|
1358
|
+
}
|
|
1359
|
+
/** Replaces the parsed cookies (EncryptCookies calls this after decrypting). */
|
|
1360
|
+
setCookies(cookies) {
|
|
1361
|
+
this.#cookies = { ...cookies };
|
|
1362
|
+
}
|
|
1363
|
+
/** One route parameter (after model binding), or all of them. */
|
|
1364
|
+
route(key) {
|
|
1365
|
+
const parameters = this.#currentRoute()?.parameters ?? {};
|
|
1366
|
+
return key === void 0 ? { ...parameters } : parameters[key];
|
|
1367
|
+
}
|
|
1368
|
+
session() {
|
|
1369
|
+
return inject(Session);
|
|
1370
|
+
}
|
|
1371
|
+
user(guard) {
|
|
1372
|
+
return this.#userResolver?.(guard);
|
|
1373
|
+
}
|
|
1374
|
+
setUserResolver(resolver) {
|
|
1375
|
+
this.#userResolver = resolver;
|
|
1376
|
+
}
|
|
1377
|
+
/** Validates all() through the ValidationFactory; throws its Validation error on failure. */
|
|
1378
|
+
validate(rules, messages, attributes) {
|
|
1379
|
+
return inject(ValidationFactory).make(this.all(), rules, messages, attributes).validated();
|
|
1380
|
+
}
|
|
1381
|
+
#currentRoute() {
|
|
1382
|
+
const container = currentContainer();
|
|
1383
|
+
return container?.bound(CurrentRoute) ? container.make(CurrentRoute) : void 0;
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
function structuredCloneInput(tree) {
|
|
1387
|
+
const out = {};
|
|
1388
|
+
for (const [key, value] of Object.entries(tree)) {
|
|
1389
|
+
out[key] = isPlainObject(value) ? structuredCloneInput(value) : Array.isArray(value) ? [...value] : value;
|
|
1390
|
+
}
|
|
1391
|
+
return out;
|
|
1392
|
+
}
|
|
1393
|
+
function request(key, defaultValue) {
|
|
1394
|
+
const current = inject(HttpRequest);
|
|
1395
|
+
return key === void 0 ? current : current.input(key, defaultValue);
|
|
1396
|
+
}
|
|
1397
|
+
function response(content, status, headers) {
|
|
1398
|
+
const factory = inject(ResponseFactory);
|
|
1399
|
+
return content === void 0 ? factory : factory.make(content, status, headers);
|
|
1400
|
+
}
|
|
1401
|
+
function redirect(to, status) {
|
|
1402
|
+
const redirector = inject(Redirector);
|
|
1403
|
+
return to === void 0 ? redirector : redirector.to(to, status);
|
|
1404
|
+
}
|
|
1405
|
+
function back(fallback, status) {
|
|
1406
|
+
return inject(Redirector).back(fallback, status);
|
|
1407
|
+
}
|
|
1408
|
+
function url(path, query, absolute) {
|
|
1409
|
+
const generator = inject(UrlGenerator);
|
|
1410
|
+
return path === void 0 ? generator : generator.to(path, query, absolute);
|
|
1411
|
+
}
|
|
1412
|
+
function route(name, parameters, absolute) {
|
|
1413
|
+
return inject(UrlGenerator).route(name, parameters, absolute);
|
|
1414
|
+
}
|
|
1415
|
+
function abort(status, message = "", headers = {}) {
|
|
1416
|
+
if (status instanceof Response) throw new HttpResponseException(status);
|
|
1417
|
+
throw new HttpException(status, message, headers);
|
|
1418
|
+
}
|
|
1419
|
+
function abortIf(condition, status, message = "", headers = {}) {
|
|
1420
|
+
if (condition) abort(status, message, headers);
|
|
1421
|
+
}
|
|
1422
|
+
function abortUnless(condition, status, message = "", headers = {}) {
|
|
1423
|
+
if (!condition) abort(status, message, headers);
|
|
1424
|
+
}
|
|
1425
|
+
function contentDisposition(type, name) {
|
|
1426
|
+
const fallback = name.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "\\$&");
|
|
1427
|
+
const header = `${type}; filename="${fallback}"`;
|
|
1428
|
+
return /^[\x20-\x7e]*$/.test(name) ? header : `${header}; filename*=UTF-8''${encodeURIComponent(name)}`;
|
|
1429
|
+
}
|
|
1430
|
+
var HttpResponseFactory = class {
|
|
1431
|
+
/** Strings default to text/html; charset=utf-8, as in Laravel. */
|
|
1432
|
+
make(content = null, status = 200, headers) {
|
|
1433
|
+
const merged = new Headers(headers);
|
|
1434
|
+
if (typeof content === "string" && !merged.has("content-type")) merged.set("content-type", "text/html; charset=utf-8");
|
|
1435
|
+
return new Response(content, { status, headers: merged });
|
|
1436
|
+
}
|
|
1437
|
+
json(data, status = 200, headers) {
|
|
1438
|
+
const merged = new Headers(headers);
|
|
1439
|
+
if (!merged.has("content-type")) merged.set("content-type", "application/json");
|
|
1440
|
+
return new Response(JSON.stringify(data === void 0 ? null : data), { status, headers: merged });
|
|
1441
|
+
}
|
|
1442
|
+
noContent(status = 204) {
|
|
1443
|
+
return new Response(null, { status });
|
|
1444
|
+
}
|
|
1445
|
+
/** Renders a view (through the ViewFactory) into an HTML response. */
|
|
1446
|
+
async view(name, data = {}, status = 200, headers) {
|
|
1447
|
+
const html = await inject(ViewFactory).make(name, data).render();
|
|
1448
|
+
return this.make(html, status, headers);
|
|
1449
|
+
}
|
|
1450
|
+
/** Streams a file as an attachment. A missing file is a 404. */
|
|
1451
|
+
download(path, name = basename(path), headers) {
|
|
1452
|
+
return this.#file(path, "attachment", name, headers);
|
|
1453
|
+
}
|
|
1454
|
+
/** Streams a file for the browser to display (Laravel's response()->file()). */
|
|
1455
|
+
file(path, headers) {
|
|
1456
|
+
return this.#file(path, "inline", basename(path), headers);
|
|
1457
|
+
}
|
|
1458
|
+
stream(body, status = 200, headers) {
|
|
1459
|
+
return new Response(body, { status, headers });
|
|
1460
|
+
}
|
|
1461
|
+
async #file(path, disposition, name, headers) {
|
|
1462
|
+
const info = await stat(path).catch(() => void 0);
|
|
1463
|
+
if (info === void 0 || !info.isFile()) throw new HttpException(404);
|
|
1464
|
+
const merged = new Headers(headers);
|
|
1465
|
+
if (!merged.has("content-type")) merged.set("content-type", mimeFromExtension(name));
|
|
1466
|
+
merged.set("content-length", String(info.size));
|
|
1467
|
+
merged.set("content-disposition", contentDisposition(disposition, name));
|
|
1468
|
+
const body = Readable.toWeb(createReadStream(path));
|
|
1469
|
+
return new Response(body, { status: 200, headers: merged });
|
|
1470
|
+
}
|
|
1471
|
+
};
|
|
1472
|
+
var ConvertEmptyStringsToNull = class {
|
|
1473
|
+
async handle(request2, next) {
|
|
1474
|
+
if (request2 instanceof IncomingRequest) request2.transformInput((_key, value) => value === "" ? null : value);
|
|
1475
|
+
return next(request2);
|
|
1476
|
+
}
|
|
1477
|
+
};
|
|
1478
|
+
var TRIM = /^[\s\uFEFF\u200B\u200E]+|[\s\uFEFF\u200B\u200E]+$/g;
|
|
1479
|
+
var TrimStrings = class {
|
|
1480
|
+
/** Dotted input keys left untouched. Override in a subclass to change. */
|
|
1481
|
+
except = ["current_password", "password", "password_confirmation"];
|
|
1482
|
+
async handle(request2, next) {
|
|
1483
|
+
if (request2 instanceof IncomingRequest) {
|
|
1484
|
+
request2.transformInput(
|
|
1485
|
+
(key, value) => typeof value === "string" && !this.except.includes(key) ? value.replace(TRIM, "") : value
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1488
|
+
return next(request2);
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
var plainServerError = () => new Response("Server Error", { status: 500, headers: { "content-type": "text/plain; charset=utf-8" } });
|
|
1492
|
+
var Kernel = class {
|
|
1493
|
+
constructor(app) {
|
|
1494
|
+
this.app = app;
|
|
1495
|
+
}
|
|
1496
|
+
app;
|
|
1497
|
+
#middleware = [TrimStrings, ConvertEmptyStringsToNull];
|
|
1498
|
+
#requests = /* @__PURE__ */ new WeakMap();
|
|
1499
|
+
getGlobalMiddleware() {
|
|
1500
|
+
return [...this.#middleware];
|
|
1501
|
+
}
|
|
1502
|
+
setGlobalMiddleware(middleware) {
|
|
1503
|
+
this.#middleware = [...middleware];
|
|
1504
|
+
return this;
|
|
1505
|
+
}
|
|
1506
|
+
appendMiddleware(...middleware) {
|
|
1507
|
+
for (const cls of middleware) if (!this.#middleware.includes(cls)) this.#middleware.push(cls);
|
|
1508
|
+
return this;
|
|
1509
|
+
}
|
|
1510
|
+
prependMiddleware(...middleware) {
|
|
1511
|
+
for (const cls of [...middleware].reverse()) if (!this.#middleware.includes(cls)) this.#middleware.unshift(cls);
|
|
1512
|
+
return this;
|
|
1513
|
+
}
|
|
1514
|
+
hasMiddleware(middleware) {
|
|
1515
|
+
return this.#middleware.includes(middleware);
|
|
1516
|
+
}
|
|
1517
|
+
/** Runs in the current request scope (Application.handle() opens one); called directly, it opens its own. */
|
|
1518
|
+
handle(raw) {
|
|
1519
|
+
const current = currentContainer();
|
|
1520
|
+
const scope = current !== void 0 && current !== this.app ? current : this.app.createScope();
|
|
1521
|
+
return scope.run(() => this.#handle(raw, scope));
|
|
1522
|
+
}
|
|
1523
|
+
async #handle(raw, scope) {
|
|
1524
|
+
let request2;
|
|
1525
|
+
let response2;
|
|
1526
|
+
try {
|
|
1527
|
+
request2 = new IncomingRequest(raw, {
|
|
1528
|
+
trustedProxies: this.#trustedProxies(),
|
|
1529
|
+
maxBodySize: this.#maxBodySize(),
|
|
1530
|
+
maxJsonDepth: this.#maxJsonDepth()
|
|
1531
|
+
});
|
|
1532
|
+
scope.instance(HttpRequest, request2);
|
|
1533
|
+
const state = { scope, request: request2, middleware: [] };
|
|
1534
|
+
this.#requests.set(raw, state);
|
|
1535
|
+
await request2.load();
|
|
1536
|
+
response2 = await this.#sendThroughGlobal(state);
|
|
1537
|
+
} catch (error) {
|
|
1538
|
+
request2 ??= this.#bareRequest(scope, raw);
|
|
1539
|
+
if (request2 !== void 0) {
|
|
1540
|
+
response2 = await this.#renderLast(scope, request2, error);
|
|
1541
|
+
} else {
|
|
1542
|
+
await this.#report(scope, error);
|
|
1543
|
+
response2 = plainServerError();
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
if (raw.method.toUpperCase() === "HEAD" && response2.body !== null) {
|
|
1547
|
+
await response2.body.cancel().catch(() => void 0);
|
|
1548
|
+
response2 = new Response(null, { status: response2.status, statusText: response2.statusText, headers: response2.headers });
|
|
1549
|
+
}
|
|
1550
|
+
return response2;
|
|
1551
|
+
}
|
|
1552
|
+
/** The request without options, for rendering an error raised while the real one was being built. */
|
|
1553
|
+
#bareRequest(scope, raw) {
|
|
1554
|
+
try {
|
|
1555
|
+
const request2 = new IncomingRequest(raw);
|
|
1556
|
+
scope.instance(HttpRequest, request2);
|
|
1557
|
+
return request2;
|
|
1558
|
+
} catch {
|
|
1559
|
+
return void 0;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
/** 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. */
|
|
1563
|
+
async terminate(raw, response2) {
|
|
1564
|
+
const state = this.#requests.get(raw);
|
|
1565
|
+
if (state !== void 0) {
|
|
1566
|
+
this.#requests.delete(raw);
|
|
1567
|
+
await state.scope.run(async () => {
|
|
1568
|
+
const route2 = state.scope.bound(CurrentRoute) ? state.scope.make(CurrentRoute).middleware : [];
|
|
1569
|
+
for (const middleware of [...state.middleware, ...route2]) {
|
|
1570
|
+
if (typeof middleware.terminate !== "function") continue;
|
|
1571
|
+
try {
|
|
1572
|
+
await middleware.terminate(state.request, response2);
|
|
1573
|
+
} catch (error) {
|
|
1574
|
+
await this.#report(state.scope, error);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
await this.app.terminate();
|
|
1578
|
+
});
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
await this.app.terminate();
|
|
1582
|
+
}
|
|
1583
|
+
#sendThroughGlobal(state) {
|
|
1584
|
+
const { scope, request: request2 } = state;
|
|
1585
|
+
const pipes = this.#middleware.map((cls) => {
|
|
1586
|
+
const instance = scope.make(cls);
|
|
1587
|
+
state.middleware.push(instance);
|
|
1588
|
+
return (passable, next) => this.#guard(scope, passable, () => instance.handle(passable, next));
|
|
1589
|
+
});
|
|
1590
|
+
return scope.make(PipelineFactory).make().send(request2).through(pipes).then((passable) => this.#guard(scope, passable, () => this.#dispatch(scope, passable)));
|
|
1591
|
+
}
|
|
1592
|
+
#dispatch(scope, request2) {
|
|
1593
|
+
return scope.make(Router).dispatch(request2);
|
|
1594
|
+
}
|
|
1595
|
+
/** Converts a Responsable result, and renders a thrown error at this stage (Laravel's Routing\Pipeline). */
|
|
1596
|
+
async #guard(scope, request2, run) {
|
|
1597
|
+
try {
|
|
1598
|
+
const result = await run();
|
|
1599
|
+
if (result instanceof Response) return result;
|
|
1600
|
+
const responsable = result;
|
|
1601
|
+
if (typeof responsable?.toResponse === "function") return await responsable.toResponse(request2);
|
|
1602
|
+
throw new HttpServerError("Middleware must return a Response.");
|
|
1603
|
+
} catch (error) {
|
|
1604
|
+
if (!scope.bound(ExceptionHandler)) throw error;
|
|
1605
|
+
const handler = scope.make(ExceptionHandler);
|
|
1606
|
+
await handler.report(error);
|
|
1607
|
+
return handler.render(request2, error);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
/** Last resort: the handler failed or isn't bound. Never throws. */
|
|
1611
|
+
async #renderLast(scope, request2, error) {
|
|
1612
|
+
if (scope.bound(ExceptionHandler)) {
|
|
1613
|
+
try {
|
|
1614
|
+
const handler = scope.make(ExceptionHandler);
|
|
1615
|
+
await handler.report(error);
|
|
1616
|
+
return await handler.render(request2, error);
|
|
1617
|
+
} catch (renderError) {
|
|
1618
|
+
console.error(renderError);
|
|
1619
|
+
}
|
|
1620
|
+
} else {
|
|
1621
|
+
console.error(error);
|
|
1622
|
+
}
|
|
1623
|
+
return plainServerError();
|
|
1624
|
+
}
|
|
1625
|
+
async #report(scope, error) {
|
|
1626
|
+
try {
|
|
1627
|
+
if (scope.bound(ExceptionHandler)) await scope.make(ExceptionHandler).report(error);
|
|
1628
|
+
else console.error(error);
|
|
1629
|
+
} catch (reportError) {
|
|
1630
|
+
console.error(reportError);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
/** config('http.max_body_size'): bytes, or null for no limit. Anything else is a configuration error. */
|
|
1634
|
+
#maxBodySize() {
|
|
1635
|
+
if (!this.app.bound(ConfigRepository)) return 8 * 1024 * 1024;
|
|
1636
|
+
const value = this.app.make(ConfigRepository).get("http.max_body_size", 8 * 1024 * 1024);
|
|
1637
|
+
if (value === null) return null;
|
|
1638
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
|
|
1639
|
+
throw new HttpServerError(
|
|
1640
|
+
`Invalid http.max_body_size ${JSON.stringify(value) ?? String(value)}: use a number of bytes (0 or more), or null for no limit.`
|
|
1641
|
+
);
|
|
1642
|
+
}
|
|
1643
|
+
/** config('http.max_json_depth'): how deep a JSON body may nest, or null for no limit. */
|
|
1644
|
+
#maxJsonDepth() {
|
|
1645
|
+
if (!this.app.bound(ConfigRepository)) return 512;
|
|
1646
|
+
const value = this.app.make(ConfigRepository).get("http.max_json_depth", 512);
|
|
1647
|
+
if (value === null) return null;
|
|
1648
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 1) return value;
|
|
1649
|
+
throw new HttpServerError(
|
|
1650
|
+
`Invalid http.max_json_depth ${JSON.stringify(value) ?? String(value)}: use a whole number (1 or more), or null for no limit.`
|
|
1651
|
+
);
|
|
1652
|
+
}
|
|
1653
|
+
/** config('http.trusted_proxies'): an array, or a comma-separated string such as '*' or '10.0.0.0/8,::1'. */
|
|
1654
|
+
#trustedProxies() {
|
|
1655
|
+
if (!this.app.bound(ConfigRepository)) return [];
|
|
1656
|
+
const value = this.app.make(ConfigRepository).get("http.trusted_proxies", []);
|
|
1657
|
+
if (typeof value === "string") return value.split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
|
|
1658
|
+
return Array.isArray(value) ? value.map(String) : [];
|
|
1659
|
+
}
|
|
1660
|
+
};
|
|
1661
|
+
var MiddlewareConfiguration = class {
|
|
1662
|
+
constructor(kernel, router, config) {
|
|
1663
|
+
this.kernel = kernel;
|
|
1664
|
+
this.router = router;
|
|
1665
|
+
this.config = config;
|
|
1666
|
+
}
|
|
1667
|
+
kernel;
|
|
1668
|
+
router;
|
|
1669
|
+
config;
|
|
1670
|
+
append(...middleware) {
|
|
1671
|
+
this.kernel.appendMiddleware(...middleware);
|
|
1672
|
+
return this;
|
|
1673
|
+
}
|
|
1674
|
+
prepend(...middleware) {
|
|
1675
|
+
this.kernel.prependMiddleware(...middleware);
|
|
1676
|
+
return this;
|
|
1677
|
+
}
|
|
1678
|
+
web(options) {
|
|
1679
|
+
return this.#modifyGroup("web", options);
|
|
1680
|
+
}
|
|
1681
|
+
api(options) {
|
|
1682
|
+
return this.#modifyGroup("api", options);
|
|
1683
|
+
}
|
|
1684
|
+
alias(aliases) {
|
|
1685
|
+
for (const [name, middleware] of Object.entries(aliases)) this.router.aliasMiddleware(name, middleware);
|
|
1686
|
+
return this;
|
|
1687
|
+
}
|
|
1688
|
+
group(name, middleware) {
|
|
1689
|
+
this.router.middlewareGroup(name, middleware);
|
|
1690
|
+
return this;
|
|
1691
|
+
}
|
|
1692
|
+
/** Stored in config('http.csrf_except'), which the Session component's VerifyCsrfToken reads. */
|
|
1693
|
+
validateCsrfTokens(options) {
|
|
1694
|
+
const current = this.config?.get("http.csrf_except", []) ?? [];
|
|
1695
|
+
this.config?.set("http.csrf_except", [...current, ...options.except]);
|
|
1696
|
+
return this;
|
|
1697
|
+
}
|
|
1698
|
+
/** Stored in config('http.redirect_guests_to'), which the Auth middleware and the exception handler read. */
|
|
1699
|
+
redirectGuestsTo(url2) {
|
|
1700
|
+
this.config?.set("http.redirect_guests_to", url2);
|
|
1701
|
+
return this;
|
|
1702
|
+
}
|
|
1703
|
+
/** Laravel 11's redirectUsersTo(): stored in config('http.redirect_users_to') for Auth's RedirectIfAuthenticated. */
|
|
1704
|
+
redirectUsersTo(url2) {
|
|
1705
|
+
this.config?.set("http.redirect_users_to", url2);
|
|
1706
|
+
return this;
|
|
1707
|
+
}
|
|
1708
|
+
/** Laravel's order: prepends, then the group minus removals, then appends. */
|
|
1709
|
+
#modifyGroup(name, { append = [], prepend = [], remove = [] }) {
|
|
1710
|
+
const current = this.router.getMiddlewareGroups()[name] ?? [];
|
|
1711
|
+
const moved = /* @__PURE__ */ new Set([...append, ...prepend, ...remove]);
|
|
1712
|
+
this.router.middlewareGroup(name, [...prepend, ...current.filter((entry) => !moved.has(entry)), ...append]);
|
|
1713
|
+
return this;
|
|
1714
|
+
}
|
|
1715
|
+
};
|
|
1716
|
+
function applyMiddlewareCallbacks(app, kernel) {
|
|
1717
|
+
if (!app.bound(MiddlewareCallbacks)) return;
|
|
1718
|
+
const callbacks = app.make(MiddlewareCallbacks);
|
|
1719
|
+
if (callbacks.length === 0) return;
|
|
1720
|
+
const config = app.bound(ConfigRepository) ? app.make(ConfigRepository) : void 0;
|
|
1721
|
+
const configurator = new MiddlewareConfiguration(kernel, app.make(Router), config);
|
|
1722
|
+
for (const callback of callbacks) callback(configurator);
|
|
1723
|
+
}
|
|
1724
|
+
var STATUS_TEXTS = {
|
|
1725
|
+
400: "Bad Request",
|
|
1726
|
+
401: "Unauthorized",
|
|
1727
|
+
402: "Payment Required",
|
|
1728
|
+
403: "Forbidden",
|
|
1729
|
+
404: "Not Found",
|
|
1730
|
+
405: "Method Not Allowed",
|
|
1731
|
+
408: "Request Timeout",
|
|
1732
|
+
409: "Conflict",
|
|
1733
|
+
410: "Gone",
|
|
1734
|
+
413: "Content Too Large",
|
|
1735
|
+
415: "Unsupported Media Type",
|
|
1736
|
+
419: "Page Expired",
|
|
1737
|
+
422: "Unprocessable Content",
|
|
1738
|
+
429: "Too Many Requests",
|
|
1739
|
+
500: "Server Error",
|
|
1740
|
+
502: "Bad Gateway",
|
|
1741
|
+
503: "Service Unavailable",
|
|
1742
|
+
504: "Gateway Timeout"
|
|
1743
|
+
};
|
|
1744
|
+
function statusText(status) {
|
|
1745
|
+
return STATUS_TEXTS[status] ?? (status >= 500 ? "Server Error" : "Error");
|
|
1746
|
+
}
|
|
1747
|
+
function escapeHtml(value) {
|
|
1748
|
+
return value.replace(/[&<>"']/g, (char) => `&#${char.charCodeAt(0)};`);
|
|
1749
|
+
}
|
|
1750
|
+
function minimalPage(status, message) {
|
|
1751
|
+
const title = escapeHtml(message);
|
|
1752
|
+
return `<!DOCTYPE html>
|
|
1753
|
+
<html lang="en">
|
|
1754
|
+
<head>
|
|
1755
|
+
<meta charset="utf-8">
|
|
1756
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1757
|
+
<title>${title}</title>
|
|
1758
|
+
<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>
|
|
1759
|
+
</head>
|
|
1760
|
+
<body><div class="code">${status}</div><div class="message">${title}</div></body>
|
|
1761
|
+
</html>`;
|
|
1762
|
+
}
|
|
1763
|
+
function stackFrames(error) {
|
|
1764
|
+
const frames = [];
|
|
1765
|
+
for (const raw of (error.stack ?? "").split("\n").slice(1)) {
|
|
1766
|
+
const text = raw.trim();
|
|
1767
|
+
const match = /(?:\(|@|at )((?:file:\/\/)?(?:\/|[A-Za-z]:\\)[^()]*?):(\d+):(\d+)\)?$/.exec(text);
|
|
1768
|
+
if (match === null) continue;
|
|
1769
|
+
const location = match[1];
|
|
1770
|
+
const file = location.startsWith("file://") ? fileURLToPath(location) : location;
|
|
1771
|
+
frames.push({ file, line: Number(match[2]), column: Number(match[3]), text });
|
|
1772
|
+
}
|
|
1773
|
+
return frames;
|
|
1774
|
+
}
|
|
1775
|
+
async function excerpt(frame, radius = 5) {
|
|
1776
|
+
const source = await readFile(frame.file, "utf8").catch(() => void 0);
|
|
1777
|
+
if (source === void 0) return void 0;
|
|
1778
|
+
const lines = source.split("\n");
|
|
1779
|
+
const start = Math.max(1, frame.line - radius);
|
|
1780
|
+
const end = Math.min(lines.length, frame.line + radius);
|
|
1781
|
+
const rows = [];
|
|
1782
|
+
for (let number = start; number <= end; number++) {
|
|
1783
|
+
const current = number === frame.line ? ' class="current"' : "";
|
|
1784
|
+
rows.push(`<tr${current}><td class="n">${number}</td><td><code>${escapeHtml(lines[number - 1] ?? "")}</code></td></tr>`);
|
|
1785
|
+
}
|
|
1786
|
+
return `<table class="source">${rows.join("")}</table>`;
|
|
1787
|
+
}
|
|
1788
|
+
async function debugPage(error, method, url2) {
|
|
1789
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1790
|
+
const frames = stackFrames(err);
|
|
1791
|
+
const appFrame = frames.find((frame) => !frame.file.includes("node_modules") && !frame.file.startsWith("node:")) ?? frames[0];
|
|
1792
|
+
const source = appFrame === void 0 ? void 0 : await excerpt(appFrame);
|
|
1793
|
+
const causes = [];
|
|
1794
|
+
for (let cause = err.cause, depth = 0; cause !== void 0 && depth < 5; cause = cause.cause, depth++) {
|
|
1795
|
+
causes.push(`<li>${escapeHtml(cause instanceof Error ? `${cause.name}: ${cause.message}` : String(cause))}</li>`);
|
|
1796
|
+
}
|
|
1797
|
+
return `<!DOCTYPE html>
|
|
1798
|
+
<html lang="en">
|
|
1799
|
+
<head>
|
|
1800
|
+
<meta charset="utf-8">
|
|
1801
|
+
<title>${escapeHtml(err.message || err.name)}</title>
|
|
1802
|
+
<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>
|
|
1803
|
+
</head>
|
|
1804
|
+
<body><main>
|
|
1805
|
+
<header><div class="class">${escapeHtml(err.name)}</div><div class="message">${escapeHtml(err.message)}</div><div class="request">${escapeHtml(method)} ${escapeHtml(url2)}</div></header>
|
|
1806
|
+
${appFrame === void 0 ? "" : `<section><div>${escapeHtml(appFrame.file)}:${appFrame.line}</div>${source ?? ""}</section>`}
|
|
1807
|
+
<section><ol>${frames.map((frame) => `<li>${escapeHtml(frame.text)}</li>`).join("")}</ol></section>
|
|
1808
|
+
${causes.length === 0 ? "" : `<section><div>Caused by</div><ul>${causes.join("")}</ul></section>`}
|
|
1809
|
+
</main></body>
|
|
1810
|
+
</html>`;
|
|
1811
|
+
}
|
|
1812
|
+
var NOT_REPORTED = /* @__PURE__ */ new Set(["Http", "ModelNotFound", "Validation", "Authentication", "Authorization", "TokenMismatch"]);
|
|
1813
|
+
var HTTP_KINDS = /* @__PURE__ */ new Set(["Http", "ModelNotFound", "Authorization", "TokenMismatch"]);
|
|
1814
|
+
var kindOf = (error) => typeof error === "object" && error !== null && typeof error.kind === "string" ? error.kind : void 0;
|
|
1815
|
+
function statusFor(error) {
|
|
1816
|
+
const status = error?.status;
|
|
1817
|
+
switch (kindOf(error)) {
|
|
1818
|
+
case "Http":
|
|
1819
|
+
return status ?? 500;
|
|
1820
|
+
case "ModelNotFound":
|
|
1821
|
+
return 404;
|
|
1822
|
+
case "Authorization":
|
|
1823
|
+
return status ?? 403;
|
|
1824
|
+
case "TokenMismatch":
|
|
1825
|
+
return 419;
|
|
1826
|
+
case "Authentication":
|
|
1827
|
+
return 401;
|
|
1828
|
+
case "Validation":
|
|
1829
|
+
return status ?? 422;
|
|
1830
|
+
case "Server":
|
|
1831
|
+
return status ?? 500;
|
|
1832
|
+
default:
|
|
1833
|
+
return 500;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
function summarizeValidation(errors) {
|
|
1837
|
+
const messages = Object.values(errors).flat();
|
|
1838
|
+
if (messages.length === 0) return "The given data was invalid.";
|
|
1839
|
+
const more = messages.length - 1;
|
|
1840
|
+
return more === 0 ? messages[0] : `${messages[0]} (and ${more} more ${more === 1 ? "error" : "errors"})`;
|
|
1841
|
+
}
|
|
1842
|
+
var isHttpLike = (error) => {
|
|
1843
|
+
const kind = kindOf(error);
|
|
1844
|
+
return kind !== void 0 && HTTP_KINDS.has(kind);
|
|
1845
|
+
};
|
|
1846
|
+
var ErrorHandler = class {
|
|
1847
|
+
constructor(app) {
|
|
1848
|
+
this.app = app;
|
|
1849
|
+
}
|
|
1850
|
+
app;
|
|
1851
|
+
/** Input keys never flashed back on a validation redirect. */
|
|
1852
|
+
dontFlash = ["current_password", "password", "password_confirmation"];
|
|
1853
|
+
#reportCallbacks = [];
|
|
1854
|
+
#renderCallbacks = [];
|
|
1855
|
+
#dontReport = [];
|
|
1856
|
+
#reported = /* @__PURE__ */ new WeakSet();
|
|
1857
|
+
#jsonWhen;
|
|
1858
|
+
reportUsing(callback) {
|
|
1859
|
+
this.#reportCallbacks.push(callback);
|
|
1860
|
+
}
|
|
1861
|
+
renderUsing(callback) {
|
|
1862
|
+
this.#renderCallbacks.push(callback);
|
|
1863
|
+
}
|
|
1864
|
+
ignore(...classes) {
|
|
1865
|
+
this.#dontReport.push(...classes);
|
|
1866
|
+
}
|
|
1867
|
+
/** Laravel's shouldRenderJsonWhen(): the callback decides JSON or HTML instead of the Accept header. */
|
|
1868
|
+
shouldRenderJsonWhen(callback) {
|
|
1869
|
+
this.#jsonWhen = callback;
|
|
1870
|
+
}
|
|
1871
|
+
/** Laravel's shouldReturnJson(): the shouldRenderJsonWhen() callback when set, else request.expectsJson(). */
|
|
1872
|
+
shouldReturnJson(request2, error) {
|
|
1873
|
+
return this.#jsonWhen !== void 0 ? this.#jsonWhen(request2, error) : request2.expectsJson();
|
|
1874
|
+
}
|
|
1875
|
+
shouldReport(error) {
|
|
1876
|
+
const kind = kindOf(error);
|
|
1877
|
+
if (kind !== void 0 && NOT_REPORTED.has(kind)) return false;
|
|
1878
|
+
return !this.#dontReport.some((cls) => error instanceof cls);
|
|
1879
|
+
}
|
|
1880
|
+
/** Report callbacks first (returning false stops the default log), then LogManager.error(), else console.error. Once per error. */
|
|
1881
|
+
async report(error) {
|
|
1882
|
+
if (!this.shouldReport(error)) return;
|
|
1883
|
+
if (typeof error === "object" && error !== null) {
|
|
1884
|
+
if (this.#reported.has(error)) return;
|
|
1885
|
+
this.#reported.add(error);
|
|
1886
|
+
}
|
|
1887
|
+
try {
|
|
1888
|
+
for (const callback of this.#reportCallbacks) if (await callback(error) === false) return;
|
|
1889
|
+
const container = this.#container();
|
|
1890
|
+
if (container.bound(LogManager)) {
|
|
1891
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1892
|
+
container.make(LogManager).error(message, { exception: error });
|
|
1893
|
+
} else {
|
|
1894
|
+
console.error(error);
|
|
1895
|
+
}
|
|
1896
|
+
} catch (reportError) {
|
|
1897
|
+
console.error(reportError);
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
async render(request2, error) {
|
|
1901
|
+
for (const callback of this.#renderCallbacks) {
|
|
1902
|
+
const response3 = await callback(error, request2);
|
|
1903
|
+
if (response3 instanceof Response) return response3;
|
|
1904
|
+
}
|
|
1905
|
+
if (error instanceof HttpResponseException) return error.response;
|
|
1906
|
+
const custom = await this.#renderCustom(request2, error);
|
|
1907
|
+
if (custom !== void 0) return custom;
|
|
1908
|
+
const http = this.#httpRequest(request2);
|
|
1909
|
+
const kind = kindOf(error);
|
|
1910
|
+
let response2;
|
|
1911
|
+
if (kind === "Validation") response2 = await this.#renderValidation(http, error);
|
|
1912
|
+
else if (kind === "Authentication") response2 = await this.#renderAuthentication(http, error);
|
|
1913
|
+
else if (this.shouldReturnJson(http, error)) response2 = this.#renderJson(error);
|
|
1914
|
+
else response2 = await this.#renderHtml(http, error);
|
|
1915
|
+
const headers = error?.headers;
|
|
1916
|
+
if (headers !== void 0) for (const [name, value] of Object.entries(headers)) response2.headers.set(name, value);
|
|
1917
|
+
return response2;
|
|
1918
|
+
}
|
|
1919
|
+
/** An error with its own render(request) or toResponse(request) (Laravel's renderable exceptions / Responsable). */
|
|
1920
|
+
async #renderCustom(request2, error) {
|
|
1921
|
+
if (typeof error !== "object" || error === null) return void 0;
|
|
1922
|
+
const candidate = error;
|
|
1923
|
+
for (const method of [candidate.render, candidate.toResponse]) {
|
|
1924
|
+
if (typeof method !== "function") continue;
|
|
1925
|
+
const response2 = await method.call(error, request2);
|
|
1926
|
+
if (response2 instanceof Response) return response2;
|
|
1927
|
+
}
|
|
1928
|
+
return void 0;
|
|
1929
|
+
}
|
|
1930
|
+
async #renderValidation(request2, error) {
|
|
1931
|
+
if (this.shouldReturnJson(request2, error)) {
|
|
1932
|
+
return this.#json({ message: summarizeValidation(error.errors), errors: error.errors }, statusFor(error));
|
|
1933
|
+
}
|
|
1934
|
+
const input = { ...request2.input() };
|
|
1935
|
+
for (const key of this.dontFlash) delete input[key];
|
|
1936
|
+
const container = this.#container();
|
|
1937
|
+
if (!container.bound(Redirector)) {
|
|
1938
|
+
return this.#redirect(error.redirectTo ?? request2.header("referer") ?? "/");
|
|
1939
|
+
}
|
|
1940
|
+
const redirector = container.make(Redirector);
|
|
1941
|
+
const redirect2 = error.redirectTo !== void 0 ? redirector.to(error.redirectTo) : redirector.back();
|
|
1942
|
+
const response2 = await redirect2.withInput(input).withErrors(error.errors, error.errorBag).toResponse(request2);
|
|
1943
|
+
return response2;
|
|
1944
|
+
}
|
|
1945
|
+
async #renderAuthentication(request2, error) {
|
|
1946
|
+
if (this.shouldReturnJson(request2, error)) return this.#json({ message: error.message || "Unauthenticated." }, 401);
|
|
1947
|
+
const target2 = error.redirectTo ?? this.#guestRedirect(request2);
|
|
1948
|
+
const container = this.#container();
|
|
1949
|
+
if (!container.bound(Redirector)) return this.#redirect(target2);
|
|
1950
|
+
const redirector = container.make(Redirector);
|
|
1951
|
+
const redirect2 = typeof redirector.guest === "function" ? redirector.guest(target2) : redirector.to(target2);
|
|
1952
|
+
return await redirect2.toResponse(request2);
|
|
1953
|
+
}
|
|
1954
|
+
/** config('http.redirect_guests_to'), then route('login'), then '/login'. */
|
|
1955
|
+
#guestRedirect(request2) {
|
|
1956
|
+
const configured = this.#config("http.redirect_guests_to", void 0);
|
|
1957
|
+
if (typeof configured === "function") return String(configured(request2));
|
|
1958
|
+
if (typeof configured === "string" && configured !== "") return configured;
|
|
1959
|
+
const container = this.#container();
|
|
1960
|
+
if (container.bound(UrlGenerator)) {
|
|
1961
|
+
const url2 = container.make(UrlGenerator);
|
|
1962
|
+
if (url2.hasRoute("login")) return url2.route("login");
|
|
1963
|
+
}
|
|
1964
|
+
return "/login";
|
|
1965
|
+
}
|
|
1966
|
+
#renderJson(error) {
|
|
1967
|
+
const status = statusFor(error);
|
|
1968
|
+
if (isHttpLike(error)) {
|
|
1969
|
+
const message = error instanceof Error && error.message !== "" ? error.message : statusText(status);
|
|
1970
|
+
return this.#json({ message }, status);
|
|
1971
|
+
}
|
|
1972
|
+
if (!this.#debug()) return this.#json({ message: "Server Error" }, status);
|
|
1973
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1974
|
+
const [frame] = stackFrames(err);
|
|
1975
|
+
return this.#json(
|
|
1976
|
+
{
|
|
1977
|
+
message: err.message,
|
|
1978
|
+
exception: err.name,
|
|
1979
|
+
file: frame?.file,
|
|
1980
|
+
line: frame?.line,
|
|
1981
|
+
trace: stackFrames(err).map((entry) => entry.text)
|
|
1982
|
+
},
|
|
1983
|
+
status
|
|
1984
|
+
);
|
|
1985
|
+
}
|
|
1986
|
+
async #renderHtml(request2, error) {
|
|
1987
|
+
const status = statusFor(error);
|
|
1988
|
+
if (!isHttpLike(error) && this.#debug()) {
|
|
1989
|
+
return this.#html(await debugPage(error, request2.method(), request2.fullUrl()), status);
|
|
1990
|
+
}
|
|
1991
|
+
const message = status === 403 && error instanceof Error && error.message !== "" ? error.message : statusText(status);
|
|
1992
|
+
const view = await this.#errorView(status, error, message);
|
|
1993
|
+
return this.#html(view ?? minimalPage(status, message), status);
|
|
1994
|
+
}
|
|
1995
|
+
/** resources/views/errors/{status}.tsx, then errors/4xx or errors/5xx, through the ViewFactory. */
|
|
1996
|
+
async #errorView(status, error, message) {
|
|
1997
|
+
const container = this.#container();
|
|
1998
|
+
if (!container.bound(ViewFactory)) return void 0;
|
|
1999
|
+
const views = container.make(ViewFactory);
|
|
2000
|
+
for (const name of [`errors.${status}`, `errors.${Math.floor(status / 100)}xx`]) {
|
|
2001
|
+
if (!views.exists(name)) continue;
|
|
2002
|
+
try {
|
|
2003
|
+
return await views.make(name, { exception: error, status, message }).render();
|
|
2004
|
+
} catch (viewError) {
|
|
2005
|
+
await this.report(viewError);
|
|
2006
|
+
return void 0;
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
return void 0;
|
|
2010
|
+
}
|
|
2011
|
+
#httpRequest(request2) {
|
|
2012
|
+
if (typeof request2 === "object" && request2 !== null && typeof request2.expectsJson === "function") {
|
|
2013
|
+
return request2;
|
|
2014
|
+
}
|
|
2015
|
+
return new IncomingRequest(request2 instanceof Request ? request2 : new Request("http://localhost/"));
|
|
2016
|
+
}
|
|
2017
|
+
#json(body, status) {
|
|
2018
|
+
return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
2019
|
+
}
|
|
2020
|
+
#html(body, status) {
|
|
2021
|
+
return new Response(body, { status, headers: { "content-type": "text/html; charset=utf-8" } });
|
|
2022
|
+
}
|
|
2023
|
+
#redirect(location) {
|
|
2024
|
+
return new Response(null, { status: 302, headers: { Location: location } });
|
|
2025
|
+
}
|
|
2026
|
+
#debug() {
|
|
2027
|
+
return this.#config("app.debug", false) === true;
|
|
2028
|
+
}
|
|
2029
|
+
#config(key, fallback) {
|
|
2030
|
+
return this.app.bound(ConfigRepository) ? this.app.make(ConfigRepository).get(key, fallback) : fallback;
|
|
2031
|
+
}
|
|
2032
|
+
/** The request scope while handling a request (so scoped services resolve), else the application. */
|
|
2033
|
+
#container() {
|
|
2034
|
+
return currentContainer() ?? this.app;
|
|
2035
|
+
}
|
|
2036
|
+
};
|
|
2037
|
+
var ExceptionsConfiguration = class {
|
|
2038
|
+
constructor(handler) {
|
|
2039
|
+
this.handler = handler;
|
|
2040
|
+
}
|
|
2041
|
+
handler;
|
|
2042
|
+
report(callback) {
|
|
2043
|
+
this.handler.reportUsing(callback);
|
|
2044
|
+
return this;
|
|
2045
|
+
}
|
|
2046
|
+
render(callback) {
|
|
2047
|
+
this.handler.renderUsing(callback);
|
|
2048
|
+
return this;
|
|
2049
|
+
}
|
|
2050
|
+
dontReport(...classes) {
|
|
2051
|
+
this.handler.ignore(...classes);
|
|
2052
|
+
return this;
|
|
2053
|
+
}
|
|
2054
|
+
/** shouldRenderJsonWhen((request) => request.is('api/*')): JSON errors for those requests, whatever they accept. */
|
|
2055
|
+
shouldRenderJsonWhen(callback) {
|
|
2056
|
+
this.handler.shouldRenderJsonWhen(callback);
|
|
2057
|
+
return this;
|
|
2058
|
+
}
|
|
2059
|
+
};
|
|
2060
|
+
function applyExceptionCallbacks(app, handler) {
|
|
2061
|
+
if (!app.bound(ExceptionCallbacks)) return;
|
|
2062
|
+
const configurator = new ExceptionsConfiguration(handler);
|
|
2063
|
+
for (const callback of app.make(ExceptionCallbacks)) callback(configurator);
|
|
2064
|
+
}
|
|
2065
|
+
var HIDDEN_SEGMENT = /^\.|\.(ts|tsx|mts|cts)$|[. ]$/i;
|
|
2066
|
+
async function servePublicFile(publicPath, request2) {
|
|
2067
|
+
if (request2.method !== "GET" && request2.method !== "HEAD") return void 0;
|
|
2068
|
+
let pathname;
|
|
2069
|
+
try {
|
|
2070
|
+
pathname = decodeURIComponent(new URL(request2.url).pathname);
|
|
2071
|
+
} catch {
|
|
2072
|
+
return void 0;
|
|
2073
|
+
}
|
|
2074
|
+
if (pathname === "/" || pathname.includes("\0")) return void 0;
|
|
2075
|
+
const root = resolve(publicPath);
|
|
2076
|
+
const file = resolve(root, `.${pathname}`);
|
|
2077
|
+
if (!file.startsWith(root + sep)) return void 0;
|
|
2078
|
+
if (relative(root, file).split(/[\\/]/).some((segment) => HIDDEN_SEGMENT.test(segment))) return void 0;
|
|
2079
|
+
const info = await stat2(file).catch(() => void 0);
|
|
2080
|
+
if (info === void 0 || !info.isFile()) return void 0;
|
|
2081
|
+
const type = mimeFromExtension(file);
|
|
2082
|
+
const headers = new Headers({
|
|
2083
|
+
"content-type": type.startsWith("text/") || type === "application/json" ? `${type}; charset=utf-8` : type,
|
|
2084
|
+
"content-length": String(info.size),
|
|
2085
|
+
"last-modified": info.mtime.toUTCString()
|
|
2086
|
+
});
|
|
2087
|
+
if (pathname.startsWith("/build/assets/")) headers.set("cache-control", "public, max-age=31536000, immutable");
|
|
2088
|
+
if (request2.method === "HEAD") return new Response(null, { status: 200, headers });
|
|
2089
|
+
const body = Readable2.toWeb(createReadStream2(file));
|
|
2090
|
+
return new Response(body, { status: 200, headers });
|
|
2091
|
+
}
|
|
2092
|
+
async function terminateRequest(app, request2, response2) {
|
|
2093
|
+
try {
|
|
2094
|
+
if (app.bound(HttpKernel)) await app.make(HttpKernel).terminate?.(request2, response2);
|
|
2095
|
+
} catch (error) {
|
|
2096
|
+
console.error(error);
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
async function handleSafely(app, request2, publicPath) {
|
|
2100
|
+
try {
|
|
2101
|
+
if (publicPath !== void 0) {
|
|
2102
|
+
const file = await servePublicFile(publicPath, request2);
|
|
2103
|
+
if (file !== void 0) return file;
|
|
2104
|
+
}
|
|
2105
|
+
return await app.handle(request2);
|
|
2106
|
+
} catch (error) {
|
|
2107
|
+
console.error(error);
|
|
2108
|
+
return plainServerError();
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
function exitOnSignals(close, closed) {
|
|
2112
|
+
const onSignal = () => {
|
|
2113
|
+
void close().then(() => process.exit(0));
|
|
2114
|
+
};
|
|
2115
|
+
process.once("SIGINT", onSignal);
|
|
2116
|
+
process.once("SIGTERM", onSignal);
|
|
2117
|
+
void closed.then(() => {
|
|
2118
|
+
process.off("SIGINT", onSignal);
|
|
2119
|
+
process.off("SIGTERM", onSignal);
|
|
2120
|
+
});
|
|
2121
|
+
}
|
|
2122
|
+
var displayHost = (hostname) => hostname.includes(":") ? `[${hostname}]` : hostname;
|
|
2123
|
+
function bunRuntime() {
|
|
2124
|
+
return globalThis.Bun;
|
|
2125
|
+
}
|
|
2126
|
+
async function serveBun(app, options) {
|
|
2127
|
+
const bun = bunRuntime();
|
|
2128
|
+
if (bun === void 0) throw new HttpServerError("Bun.serve is not available: the process is not running on Bun.");
|
|
2129
|
+
let markClosed = () => void 0;
|
|
2130
|
+
const closed = new Promise((resolve2) => markClosed = resolve2);
|
|
2131
|
+
const server = bun.serve({
|
|
2132
|
+
port: options.port,
|
|
2133
|
+
hostname: options.hostname,
|
|
2134
|
+
async fetch(request2, current) {
|
|
2135
|
+
setClientAddress(request2, current.requestIP(request2)?.address);
|
|
2136
|
+
const response2 = await handleSafely(app, request2, options.publicPath);
|
|
2137
|
+
setTimeout(() => void terminateRequest(app, request2, response2), 0);
|
|
2138
|
+
return response2;
|
|
2139
|
+
},
|
|
2140
|
+
error(error) {
|
|
2141
|
+
console.error(error);
|
|
2142
|
+
return plainServerError();
|
|
2143
|
+
}
|
|
2144
|
+
});
|
|
2145
|
+
let closing;
|
|
2146
|
+
const close = () => closing ??= (async () => {
|
|
2147
|
+
let timer;
|
|
2148
|
+
const forced = new Promise((resolve2) => {
|
|
2149
|
+
timer = setTimeout(() => void Promise.resolve(server.stop(true)).then(resolve2), options.shutdownTimeout);
|
|
2150
|
+
});
|
|
2151
|
+
await Promise.race([Promise.resolve(server.stop(false)), forced]);
|
|
2152
|
+
clearTimeout(timer);
|
|
2153
|
+
markClosed();
|
|
2154
|
+
})();
|
|
2155
|
+
if (options.signals) exitOnSignals(close, closed);
|
|
2156
|
+
return {
|
|
2157
|
+
runtime: "bun",
|
|
2158
|
+
hostname: options.hostname,
|
|
2159
|
+
port: server.port,
|
|
2160
|
+
url: `http://${displayHost(options.hostname)}:${server.port}`,
|
|
2161
|
+
close,
|
|
2162
|
+
closed
|
|
2163
|
+
};
|
|
2164
|
+
}
|
|
2165
|
+
function requestBody(req) {
|
|
2166
|
+
let finished = false;
|
|
2167
|
+
const detach = () => {
|
|
2168
|
+
finished = true;
|
|
2169
|
+
req.off("data", onData);
|
|
2170
|
+
req.off("end", onEnd);
|
|
2171
|
+
req.off("error", onError);
|
|
2172
|
+
req.off("close", onClose);
|
|
2173
|
+
};
|
|
2174
|
+
let controller;
|
|
2175
|
+
const onData = (chunk2) => {
|
|
2176
|
+
if (finished) return;
|
|
2177
|
+
controller.enqueue(new Uint8Array(chunk2.buffer, chunk2.byteOffset, chunk2.byteLength));
|
|
2178
|
+
if ((controller.desiredSize ?? 0) <= 0) req.pause();
|
|
2179
|
+
};
|
|
2180
|
+
const onEnd = () => {
|
|
2181
|
+
if (finished) return;
|
|
2182
|
+
detach();
|
|
2183
|
+
controller.close();
|
|
2184
|
+
};
|
|
2185
|
+
const onError = (error) => {
|
|
2186
|
+
if (finished) return;
|
|
2187
|
+
detach();
|
|
2188
|
+
controller.error(error);
|
|
2189
|
+
};
|
|
2190
|
+
const onClose = () => {
|
|
2191
|
+
if (!finished && !req.complete) onError(new Error("The request was aborted."));
|
|
2192
|
+
};
|
|
2193
|
+
return new ReadableStream(
|
|
2194
|
+
{
|
|
2195
|
+
start(c) {
|
|
2196
|
+
controller = c;
|
|
2197
|
+
req.on("data", onData);
|
|
2198
|
+
req.on("end", onEnd);
|
|
2199
|
+
req.on("error", onError);
|
|
2200
|
+
req.on("close", onClose);
|
|
2201
|
+
req.pause();
|
|
2202
|
+
},
|
|
2203
|
+
pull() {
|
|
2204
|
+
req.resume();
|
|
2205
|
+
},
|
|
2206
|
+
cancel() {
|
|
2207
|
+
detach();
|
|
2208
|
+
}
|
|
2209
|
+
},
|
|
2210
|
+
{ highWaterMark: 0 }
|
|
2211
|
+
);
|
|
2212
|
+
}
|
|
2213
|
+
function toWebRequest(req, res, fallbackHost) {
|
|
2214
|
+
const target2 = req.url ?? "/";
|
|
2215
|
+
const url2 = new URL(`http://${req.headers.host ?? fallbackHost}${target2.startsWith("/") ? target2 : `/${target2}`}`);
|
|
2216
|
+
const headers = new Headers();
|
|
2217
|
+
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
2218
|
+
const name = req.rawHeaders[i];
|
|
2219
|
+
if (!name.startsWith(":")) headers.append(name, req.rawHeaders[i + 1]);
|
|
2220
|
+
}
|
|
2221
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
2222
|
+
const controller = new AbortController();
|
|
2223
|
+
res.once("close", () => {
|
|
2224
|
+
if (!res.writableFinished) controller.abort();
|
|
2225
|
+
});
|
|
2226
|
+
const init = { method, headers, signal: controller.signal };
|
|
2227
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
2228
|
+
init.body = requestBody(req);
|
|
2229
|
+
init.duplex = "half";
|
|
2230
|
+
}
|
|
2231
|
+
return new Request(url2, init);
|
|
2232
|
+
}
|
|
2233
|
+
async function writeResponse(res, response2) {
|
|
2234
|
+
res.statusCode = response2.status;
|
|
2235
|
+
if (response2.statusText !== "") res.statusMessage = response2.statusText;
|
|
2236
|
+
response2.headers.forEach((value, name) => {
|
|
2237
|
+
if (name !== "set-cookie") res.setHeader(name, value);
|
|
2238
|
+
});
|
|
2239
|
+
const cookies = response2.headers.getSetCookie();
|
|
2240
|
+
if (cookies.length > 0) res.setHeader("set-cookie", cookies);
|
|
2241
|
+
if (response2.body === null) {
|
|
2242
|
+
res.end();
|
|
2243
|
+
return;
|
|
582
2244
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
2245
|
+
await pipeline(Readable3.fromWeb(response2.body), res);
|
|
2246
|
+
}
|
|
2247
|
+
async function serveNode(app, options) {
|
|
2248
|
+
const fallbackHost = `${displayHost(options.hostname)}:${options.port}`;
|
|
2249
|
+
const server = createServer((req, res) => {
|
|
2250
|
+
void (async () => {
|
|
2251
|
+
let request2;
|
|
2252
|
+
try {
|
|
2253
|
+
request2 = toWebRequest(req, res, fallbackHost);
|
|
2254
|
+
} catch {
|
|
2255
|
+
res.statusCode = 400;
|
|
2256
|
+
res.end("Bad Request");
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
setClientAddress(request2, req.socket.remoteAddress);
|
|
2260
|
+
const response2 = await handleSafely(app, request2, options.publicPath);
|
|
2261
|
+
try {
|
|
2262
|
+
await writeResponse(res, response2);
|
|
2263
|
+
} catch {
|
|
2264
|
+
res.destroy();
|
|
2265
|
+
}
|
|
2266
|
+
await terminateRequest(app, request2, response2);
|
|
2267
|
+
})();
|
|
2268
|
+
});
|
|
2269
|
+
await new Promise((resolve2, reject) => {
|
|
2270
|
+
server.once("error", reject);
|
|
2271
|
+
server.listen(options.port, options.hostname, () => {
|
|
2272
|
+
server.off("error", reject);
|
|
2273
|
+
resolve2();
|
|
2274
|
+
});
|
|
2275
|
+
});
|
|
2276
|
+
const address = server.address();
|
|
2277
|
+
const closed = new Promise((resolve2) => server.once("close", () => resolve2()));
|
|
2278
|
+
let closing;
|
|
2279
|
+
const close = () => closing ??= (async () => {
|
|
2280
|
+
const done = new Promise((resolve2) => server.close(() => resolve2()));
|
|
2281
|
+
server.closeIdleConnections();
|
|
2282
|
+
const idle = setInterval(() => server.closeIdleConnections(), 50);
|
|
2283
|
+
const force = setTimeout(() => server.closeAllConnections(), options.shutdownTimeout);
|
|
2284
|
+
await done;
|
|
2285
|
+
clearInterval(idle);
|
|
2286
|
+
clearTimeout(force);
|
|
2287
|
+
})();
|
|
2288
|
+
if (options.signals) exitOnSignals(close, closed);
|
|
2289
|
+
return {
|
|
2290
|
+
runtime: "node",
|
|
2291
|
+
hostname: options.hostname,
|
|
2292
|
+
port: address.port,
|
|
2293
|
+
url: `http://${displayHost(options.hostname)}:${address.port}`,
|
|
2294
|
+
close,
|
|
2295
|
+
closed
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
function detectRuntime() {
|
|
2299
|
+
return bunRuntime() !== void 0 ? "bun" : "node";
|
|
2300
|
+
}
|
|
2301
|
+
async function serve(app, options = {}) {
|
|
2302
|
+
const resolved = {
|
|
2303
|
+
port: options.port ?? Number(process.env.PORT ?? process.env.SERVER_PORT ?? 8e3),
|
|
2304
|
+
hostname: options.hostname ?? "127.0.0.1",
|
|
2305
|
+
signals: options.signals ?? true,
|
|
2306
|
+
shutdownTimeout: options.shutdownTimeout ?? 1e4,
|
|
2307
|
+
...options.publicPath !== false && { publicPath: options.publicPath ?? app.publicPath() }
|
|
2308
|
+
};
|
|
2309
|
+
await app.boot();
|
|
2310
|
+
const runtime = options.runtime ?? detectRuntime();
|
|
2311
|
+
const server = runtime === "bun" ? await serveBun(app, resolved) : await serveNode(app, resolved);
|
|
2312
|
+
const onListen = options.onListen ?? ((running) => console.log(`Server running on [${running.url}].`));
|
|
2313
|
+
onListen(server);
|
|
2314
|
+
return server;
|
|
2315
|
+
}
|
|
2316
|
+
function io(command) {
|
|
2317
|
+
return {
|
|
2318
|
+
option: (name) => {
|
|
2319
|
+
const value = command.option(name);
|
|
2320
|
+
return value === void 0 || value === null || value === false || value === "" ? void 0 : String(value);
|
|
2321
|
+
},
|
|
2322
|
+
flag: (name) => {
|
|
2323
|
+
const value = command.option(name);
|
|
2324
|
+
return value === true || value === "true" || value === "1";
|
|
2325
|
+
},
|
|
2326
|
+
info: (message) => command.info(message),
|
|
2327
|
+
line: (message) => command.line(message),
|
|
2328
|
+
warn: (message) => command.warn(message),
|
|
2329
|
+
error: (message) => command.error(message)
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
var middlewareName = (reference) => typeof reference === "string" ? reference : reference.name;
|
|
2333
|
+
function routeListRows(routes) {
|
|
2334
|
+
const rows = routes.map((route2) => {
|
|
2335
|
+
const listable = route2;
|
|
2336
|
+
return {
|
|
2337
|
+
method: route2.methods.join("|"),
|
|
2338
|
+
uri: route2.uri,
|
|
2339
|
+
name: route2.getName() ?? null,
|
|
2340
|
+
action: typeof listable.getActionName === "function" ? listable.getActionName() : "Closure",
|
|
2341
|
+
middleware: typeof listable.gatherMiddleware === "function" ? listable.gatherMiddleware().map(middlewareName) : []
|
|
2342
|
+
};
|
|
2343
|
+
});
|
|
2344
|
+
return rows.sort((a, b) => a.uri < b.uri ? -1 : a.uri > b.uri ? 1 : 0);
|
|
2345
|
+
}
|
|
2346
|
+
function filterRouteList(rows, filters) {
|
|
2347
|
+
const excluded = (filters.exceptPath ?? "").split(",").filter((path) => path !== "");
|
|
2348
|
+
return rows.filter((row) => {
|
|
2349
|
+
if (filters.name !== void 0 && !(row.name ?? "").includes(filters.name)) return false;
|
|
2350
|
+
if (filters.path !== void 0 && !row.uri.includes(filters.path)) return false;
|
|
2351
|
+
if (filters.method !== void 0 && !row.method.includes(filters.method.toUpperCase())) return false;
|
|
2352
|
+
return !excluded.some((path) => row.uri.includes(path));
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
2355
|
+
function formatRouteList(rows, width) {
|
|
2356
|
+
const methodWidth = Math.max(...rows.map((row) => row.method.length));
|
|
2357
|
+
const lines = rows.map((row) => {
|
|
2358
|
+
const spaces = " ".repeat(Math.max(methodWidth + 6 - row.method.length, 0));
|
|
2359
|
+
const action = row.action === "Closure" ? row.name ?? "" : row.name === null ? row.action : `${row.name} \u203A ${row.action}`;
|
|
2360
|
+
const used = row.method.length + spaces.length + row.uri.length + action.length + 6 + (action === "" ? 0 : 1);
|
|
2361
|
+
const dots = ".".repeat(Math.max(width - used, 0));
|
|
2362
|
+
return ` ${row.method} ${spaces}${row.uri}${dots === "" ? "" : ` ${dots}`}${action === "" ? "" : ` ${action}`}`;
|
|
2363
|
+
});
|
|
2364
|
+
const count = `Showing [${rows.length}] ${rows.length === 1 ? "route" : "routes"}`;
|
|
2365
|
+
return [...lines, "", `${" ".repeat(Math.max(width - count.length - 2, 0))}${count}`];
|
|
2366
|
+
}
|
|
2367
|
+
var API_ROUTES_STUB = `import { request, Route } from '@orieljs/framework'
|
|
2368
|
+
|
|
2369
|
+
Route.get('/user', () => request().user()).middleware('auth:token')
|
|
2370
|
+
`;
|
|
2371
|
+
function addApiRouting(source) {
|
|
2372
|
+
const code = source.replace(/^[ \t]*\/\/.*$/gm, "");
|
|
2373
|
+
if (/withRouting\(\s*\{[^}]*\bapi\s*:/.test(code)) return source;
|
|
2374
|
+
const commented = /^([ \t]*)\/\/\s*(api:\s*['"`][^'"`\n]*['"`],?)[ \t]*$/m.exec(source);
|
|
2375
|
+
if (commented !== null) return source.replace(commented[0], () => `${commented[1]}${commented[2]}`);
|
|
2376
|
+
const web = /^([ \t]*)web:\s*(['"`])[^'"`\n]*\2[ \t]*,?[ \t]*$/m.exec(source);
|
|
2377
|
+
if (web !== null) {
|
|
2378
|
+
const line = web[0].trimEnd();
|
|
2379
|
+
return source.replace(web[0], () => `${line.endsWith(",") ? line : `${line},`}
|
|
2380
|
+
${web[1]}api: 'routes/api.ts',`);
|
|
2381
|
+
}
|
|
2382
|
+
const inline = /withRouting\(\s*\{/.exec(source);
|
|
2383
|
+
if (inline !== null) return source.replace(inline[0], () => `${inline[0]} api: 'routes/api.ts',`);
|
|
2384
|
+
const create = /^([ \t]*)\.create\(\)/m.exec(source);
|
|
2385
|
+
if (create !== null) return source.replace(create[0], () => `${create[1]}.withRouting({ api: 'routes/api.ts' })
|
|
2386
|
+
${create[0]}`);
|
|
2387
|
+
return void 0;
|
|
2388
|
+
}
|
|
2389
|
+
function registerHttpCommands(kernel, app, options = {}) {
|
|
2390
|
+
const serveApp = options.serve ?? serve;
|
|
2391
|
+
const terminalWidth = options.terminalWidth ?? (() => process.stdout.columns ?? 120);
|
|
2392
|
+
kernel.command(
|
|
2393
|
+
"serve {--host=127.0.0.1 : The host address to serve the application on} {--port= : The port to serve the application on}",
|
|
2394
|
+
async function() {
|
|
2395
|
+
const out = io(this);
|
|
2396
|
+
const port = out.option("port");
|
|
2397
|
+
if (port !== void 0 && !/^\d+$/.test(port)) {
|
|
2398
|
+
out.error("The --port option must be a number.");
|
|
2399
|
+
return 1;
|
|
2400
|
+
}
|
|
2401
|
+
const server = await serveApp(app, {
|
|
2402
|
+
hostname: out.option("host") ?? "127.0.0.1",
|
|
2403
|
+
...port !== void 0 && { port: Number(port) },
|
|
2404
|
+
onListen: (running) => {
|
|
2405
|
+
out.info(`Server running on [${running.url}].`);
|
|
2406
|
+
out.line("Press Ctrl+C to stop the server");
|
|
2407
|
+
}
|
|
2408
|
+
});
|
|
2409
|
+
await server.closed;
|
|
2410
|
+
return 0;
|
|
588
2411
|
}
|
|
2412
|
+
).purpose("Serve the application on the Oriel development server");
|
|
2413
|
+
kernel.command(
|
|
2414
|
+
"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}",
|
|
2415
|
+
function() {
|
|
2416
|
+
const out = io(this);
|
|
2417
|
+
if (!app.bound(Router)) {
|
|
2418
|
+
out.error("The router is not available. Is the Routing service provider registered?");
|
|
2419
|
+
return 1;
|
|
2420
|
+
}
|
|
2421
|
+
const all = routeListRows(app.make(Router).getRoutes());
|
|
2422
|
+
if (all.length === 0) {
|
|
2423
|
+
out.error("Your application doesn't have any routes.");
|
|
2424
|
+
return 0;
|
|
2425
|
+
}
|
|
2426
|
+
const rows = filterRouteList(all, {
|
|
2427
|
+
...out.option("method") !== void 0 && { method: out.option("method") },
|
|
2428
|
+
...out.option("name") !== void 0 && { name: out.option("name") },
|
|
2429
|
+
...out.option("path") !== void 0 && { path: out.option("path") },
|
|
2430
|
+
...out.option("except-path") !== void 0 && { exceptPath: out.option("except-path") }
|
|
2431
|
+
});
|
|
2432
|
+
if (rows.length === 0) {
|
|
2433
|
+
out.error("Your application doesn't have any routes matching the given criteria.");
|
|
2434
|
+
return 0;
|
|
2435
|
+
}
|
|
2436
|
+
if (out.flag("reverse")) rows.reverse();
|
|
2437
|
+
if (out.flag("json")) {
|
|
2438
|
+
out.line(JSON.stringify(rows));
|
|
2439
|
+
return 0;
|
|
2440
|
+
}
|
|
2441
|
+
for (const line of formatRouteList(rows, terminalWidth())) out.line(line);
|
|
2442
|
+
return 0;
|
|
2443
|
+
}
|
|
2444
|
+
).purpose("List all registered routes");
|
|
2445
|
+
kernel.command("install:api {--force : Overwrite any existing API routes file}", async function() {
|
|
2446
|
+
const out = io(this);
|
|
2447
|
+
const routesFile = app.basePath("routes/api.ts");
|
|
2448
|
+
if (existsSync4(routesFile) && !out.flag("force")) {
|
|
2449
|
+
out.error("API routes file already exists.");
|
|
2450
|
+
} else {
|
|
2451
|
+
await mkdir(dirname(routesFile), { recursive: true });
|
|
2452
|
+
await writeFile(routesFile, API_ROUTES_STUB);
|
|
2453
|
+
out.info("Published API routes file.");
|
|
2454
|
+
}
|
|
2455
|
+
const bootstrapFile = app.bootstrapPath("app.ts");
|
|
2456
|
+
const source = existsSync4(bootstrapFile) ? await readFile2(bootstrapFile, "utf8") : void 0;
|
|
2457
|
+
const updated = source === void 0 ? void 0 : addApiRouting(source);
|
|
2458
|
+
if (updated === void 0) {
|
|
2459
|
+
out.warn(
|
|
2460
|
+
"Unable to automatically add API route definition to bootstrap file. Add api: 'routes/api.ts' to withRouting() in bootstrap/app.ts."
|
|
2461
|
+
);
|
|
2462
|
+
} else if (updated !== source) {
|
|
2463
|
+
await writeFile(bootstrapFile, updated);
|
|
2464
|
+
}
|
|
2465
|
+
out.info("API scaffolding installed. Run [oriel migrate] to create the personal access tokens table.");
|
|
2466
|
+
return 0;
|
|
2467
|
+
}).purpose("Create an API routes file and prepare the application for API authentication");
|
|
2468
|
+
}
|
|
2469
|
+
var HttpServiceProvider = class extends ServiceProvider {
|
|
2470
|
+
register() {
|
|
2471
|
+
this.mergeConfigFrom(
|
|
2472
|
+
{
|
|
2473
|
+
trusted_proxies: [],
|
|
2474
|
+
max_body_size: 8 * 1024 * 1024,
|
|
2475
|
+
max_json_depth: 512,
|
|
2476
|
+
csrf_except: [],
|
|
2477
|
+
redirect_guests_to: void 0,
|
|
2478
|
+
redirect_users_to: void 0
|
|
2479
|
+
},
|
|
2480
|
+
"http"
|
|
2481
|
+
);
|
|
2482
|
+
this.app.singleton(HttpKernel, () => new Kernel(this.app));
|
|
2483
|
+
this.app.singleton(ExceptionHandler, () => {
|
|
2484
|
+
const handler = new ErrorHandler(this.app);
|
|
2485
|
+
applyExceptionCallbacks(this.app, handler);
|
|
2486
|
+
return handler;
|
|
2487
|
+
});
|
|
2488
|
+
this.app.singleton(ResponseFactory, () => new HttpResponseFactory());
|
|
2489
|
+
this.app.singleton(Redirector, () => new HttpRedirector());
|
|
589
2490
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
}
|
|
597
|
-
async handle(request) {
|
|
598
|
-
const handler = this.routes.get(`${request.method} ${new URL(request.url).pathname}`);
|
|
599
|
-
if (!handler) return new Response("Not Found", { status: 404, headers: { "content-type": "text/plain; charset=utf-8" } });
|
|
600
|
-
return toResponse(await handler(request));
|
|
2491
|
+
boot() {
|
|
2492
|
+
this.app.booted(() => {
|
|
2493
|
+
const kernel = this.app.make(HttpKernel);
|
|
2494
|
+
if (kernel instanceof Kernel && this.app.bound(Router)) applyMiddlewareCallbacks(this.app, kernel);
|
|
2495
|
+
});
|
|
2496
|
+
if (this.app.bound(ConsoleKernel)) registerHttpCommands(this.app.make(ConsoleKernel), this.app);
|
|
601
2497
|
}
|
|
602
2498
|
};
|
|
603
|
-
function toResponse(output) {
|
|
604
|
-
if (output instanceof Response) return output;
|
|
605
|
-
if (output === void 0 || output === null) return new Response(null, { status: 204 });
|
|
606
|
-
if (typeof output === "string") return new Response(output, { headers: { "content-type": "text/html; charset=utf-8" } });
|
|
607
|
-
return Response.json(output);
|
|
608
|
-
}
|
|
609
2499
|
|
|
610
2500
|
// ../Live/dist/index.js
|
|
611
2501
|
import { AssertionError } from "assert";
|
|
612
2502
|
import { isDeepStrictEqual } from "util";
|
|
613
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
2503
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
614
2504
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
615
2505
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
616
|
-
import { existsSync as
|
|
617
|
-
import { join as join5, relative, sep } from "path";
|
|
2506
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
|
|
2507
|
+
import { join as join5, relative as relative2, sep as sep2 } from "path";
|
|
618
2508
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2509
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
619
2510
|
import { existsSync as existsSync22 } from "fs";
|
|
620
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
621
|
-
import { dirname, join as join23, relative as
|
|
2511
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
2512
|
+
import { dirname as dirname2, join as join23, relative as relative22, sep as sep22 } from "path";
|
|
622
2513
|
Symbol.metadata ??= /* @__PURE__ */ Symbol.for("Symbol.metadata");
|
|
623
2514
|
var LISTENERS = /* @__PURE__ */ Symbol.for("oriel:live.listeners");
|
|
624
2515
|
var LOCKED = /* @__PURE__ */ Symbol.for("oriel:live.locked");
|
|
@@ -771,9 +2662,9 @@ var Component = class {
|
|
|
771
2662
|
return new PendingEvent(event);
|
|
772
2663
|
}
|
|
773
2664
|
/** Sends the browser to `url` after this request; `navigate` uses o:navigate instead of a full page load. */
|
|
774
|
-
redirect(
|
|
2665
|
+
redirect(url2, options = {}) {
|
|
775
2666
|
const state = stateOf(this);
|
|
776
|
-
state.redirect =
|
|
2667
|
+
state.redirect = url2;
|
|
777
2668
|
if (options.navigate === true) state.redirectUsingNavigate = true;
|
|
778
2669
|
else delete state.redirectUsingNavigate;
|
|
779
2670
|
}
|
|
@@ -825,6 +2716,20 @@ var Component = class {
|
|
|
825
2716
|
return inject(LiveManager).page(this);
|
|
826
2717
|
}
|
|
827
2718
|
};
|
|
2719
|
+
function trackFlash(session, { saved = true } = {}) {
|
|
2720
|
+
if (session === void 0) return () => void 0;
|
|
2721
|
+
const before = new Set(session.get("_flash.new", []));
|
|
2722
|
+
return (redirected) => {
|
|
2723
|
+
if (redirected) return;
|
|
2724
|
+
const flashed = session.get("_flash.new", []);
|
|
2725
|
+
const fresh = flashed.filter((key) => !before.has(key));
|
|
2726
|
+
if (fresh.length === 0) return;
|
|
2727
|
+
session.put("_flash.new", flashed.filter((key) => before.has(key)));
|
|
2728
|
+
if (!saved) return session.forget(...fresh);
|
|
2729
|
+
const old = session.get("_flash.old", []);
|
|
2730
|
+
session.put("_flash.old", [...old, ...fresh.filter((key) => !old.includes(key))]);
|
|
2731
|
+
};
|
|
2732
|
+
}
|
|
828
2733
|
var LiveError = class extends Error {
|
|
829
2734
|
name = "LiveError";
|
|
830
2735
|
kind = "Server";
|
|
@@ -884,14 +2789,14 @@ function rootTag(html, component) {
|
|
|
884
2789
|
let rootEnd = -1;
|
|
885
2790
|
TOKEN.lastIndex = 0;
|
|
886
2791
|
for (let match = TOKEN.exec(source); match !== null; match = TOKEN.exec(source)) {
|
|
887
|
-
const [
|
|
2792
|
+
const [token2, closing, opening, selfClosing] = match;
|
|
888
2793
|
if (root === void 0 && match.index !== 0) fail22();
|
|
889
2794
|
if (depth === 0 && root !== void 0) fail22();
|
|
890
2795
|
if (opening !== void 0) {
|
|
891
2796
|
const tag = opening.toLowerCase();
|
|
892
2797
|
root ??= tag;
|
|
893
2798
|
if (selfClosing === "/" || VOID.has(tag)) {
|
|
894
|
-
if (depth === 0) rootEnd = match.index +
|
|
2799
|
+
if (depth === 0) rootEnd = match.index + token2.length;
|
|
895
2800
|
continue;
|
|
896
2801
|
}
|
|
897
2802
|
if (RAW_TEXT.has(tag)) {
|
|
@@ -903,7 +2808,7 @@ function rootTag(html, component) {
|
|
|
903
2808
|
depth++;
|
|
904
2809
|
} else if (closing !== void 0) {
|
|
905
2810
|
if (--depth < 0) fail22();
|
|
906
|
-
if (depth === 0) rootEnd = match.index +
|
|
2811
|
+
if (depth === 0) rootEnd = match.index + token2.length;
|
|
907
2812
|
}
|
|
908
2813
|
}
|
|
909
2814
|
if (root === void 0 || depth !== 0 || source.slice(rootEnd).trim() !== "") fail22();
|
|
@@ -920,23 +2825,40 @@ var fail = (message, actual, expected) => {
|
|
|
920
2825
|
throw new AssertionError({ message, actual, expected, operator: "live" });
|
|
921
2826
|
};
|
|
922
2827
|
var getPath = (target2, path) => path.split(".").reduce((node, segment) => typeof node === "object" && node !== null ? node[segment] : void 0, target2);
|
|
2828
|
+
var TestRequestScope = token("Testing.RequestScope");
|
|
2829
|
+
function requestScope2() {
|
|
2830
|
+
const container = currentContainer() ?? Container.getInstance();
|
|
2831
|
+
return container?.bound(TestRequestScope) === true ? container.make(TestRequestScope) : (run) => run();
|
|
2832
|
+
}
|
|
2833
|
+
function currentSession() {
|
|
2834
|
+
const container = currentContainer();
|
|
2835
|
+
if (container?.bound(Session) !== true) return void 0;
|
|
2836
|
+
try {
|
|
2837
|
+
return container.make(Session);
|
|
2838
|
+
} catch {
|
|
2839
|
+
return void 0;
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
923
2842
|
var Testable = class _Testable {
|
|
924
|
-
constructor(manager, result) {
|
|
2843
|
+
constructor(manager, scope, result) {
|
|
925
2844
|
this.manager = manager;
|
|
2845
|
+
this.scope = scope;
|
|
926
2846
|
this.#snapshot = JSON.stringify(result.snapshot);
|
|
927
2847
|
this.#html = result.html ?? "";
|
|
928
2848
|
this.#effects = result.effects;
|
|
929
2849
|
this.#instance = result.instance;
|
|
930
2850
|
}
|
|
931
2851
|
manager;
|
|
2852
|
+
scope;
|
|
932
2853
|
#snapshot;
|
|
933
2854
|
#html;
|
|
934
2855
|
#effects;
|
|
935
2856
|
#instance;
|
|
936
2857
|
/** Mounts `component` with `params`, as `<Live component={…} {...params} />` would. */
|
|
937
2858
|
static async create(component, params = {}) {
|
|
2859
|
+
const scope = requestScope2();
|
|
938
2860
|
const manager = inject(LiveManager);
|
|
939
|
-
return new _Testable(manager, await manager.mountComponent(component, params));
|
|
2861
|
+
return new _Testable(manager, scope, await scope(() => manager.mountComponent(component, params)));
|
|
940
2862
|
}
|
|
941
2863
|
/** The component instance after the last request. */
|
|
942
2864
|
instance() {
|
|
@@ -971,7 +2893,12 @@ var Testable = class _Testable {
|
|
|
971
2893
|
return this.#update({}, [{ method: "$refresh", params: [] }]);
|
|
972
2894
|
}
|
|
973
2895
|
async #update(updates, calls) {
|
|
974
|
-
const result = await this.
|
|
2896
|
+
const result = await this.scope(async () => {
|
|
2897
|
+
const flashed = trackFlash(currentSession(), { saved: false });
|
|
2898
|
+
const updated = await this.manager.updateComponent(this.#snapshot, updates, calls);
|
|
2899
|
+
flashed(updated.effects.redirect !== void 0);
|
|
2900
|
+
return updated;
|
|
2901
|
+
});
|
|
975
2902
|
this.#snapshot = JSON.stringify(result.snapshot);
|
|
976
2903
|
if (result.html !== void 0) this.#html = result.html;
|
|
977
2904
|
this.#effects = result.effects;
|
|
@@ -1029,10 +2956,10 @@ var Testable = class _Testable {
|
|
|
1029
2956
|
if (found.length > 0) fail(`Component has errors: "${found.join('", "')}"`, errors);
|
|
1030
2957
|
return this;
|
|
1031
2958
|
}
|
|
1032
|
-
assertRedirect(
|
|
1033
|
-
const
|
|
1034
|
-
if (
|
|
1035
|
-
if (
|
|
2959
|
+
assertRedirect(url2) {
|
|
2960
|
+
const redirect2 = this.#effects.redirect;
|
|
2961
|
+
if (redirect2 === void 0) fail("Component did not perform a redirect.");
|
|
2962
|
+
if (url2 !== void 0 && redirect2 !== url2) fail("Component redirected to an unexpected URL.", redirect2, url2);
|
|
1036
2963
|
return this;
|
|
1037
2964
|
}
|
|
1038
2965
|
assertNoRedirect() {
|
|
@@ -1088,8 +3015,8 @@ function defaultName(cls) {
|
|
|
1088
3015
|
return typeof name === "string" && name !== "" ? Str.kebab(name) : void 0;
|
|
1089
3016
|
}
|
|
1090
3017
|
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(
|
|
3018
|
+
if (!existsSync5(directory)) return [];
|
|
3019
|
+
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
3020
|
}
|
|
1094
3021
|
async function discover(directory) {
|
|
1095
3022
|
const found = [];
|
|
@@ -1098,16 +3025,132 @@ async function discover(directory) {
|
|
|
1098
3025
|
/* @vite-ignore */
|
|
1099
3026
|
pathToFileURL2(join5(directory, file)).href
|
|
1100
3027
|
);
|
|
1101
|
-
found.push([componentName(
|
|
3028
|
+
found.push([componentName(relative2(directory, join5(directory, file))), module.default]);
|
|
1102
3029
|
}
|
|
1103
3030
|
return found;
|
|
1104
3031
|
}
|
|
3032
|
+
var PERSISTENT_MIDDLEWARE = ["auth", "verified", "guest", "can"];
|
|
3033
|
+
var ROUTER_METHODS = ["getRoutesByMatchOrder", "getPatterns", "getPatternVersion", "getMiddleware", "getMiddlewareGroups", "substituteBindings"];
|
|
3034
|
+
function canMatch(router) {
|
|
3035
|
+
const methods = router;
|
|
3036
|
+
return ROUTER_METHODS.every((name) => typeof methods[name] === "function");
|
|
3037
|
+
}
|
|
3038
|
+
function isMatchingRoute(route2) {
|
|
3039
|
+
const candidate = route2;
|
|
3040
|
+
return typeof candidate === "object" && candidate !== null && Array.isArray(candidate.methods) && ["match", "gatherMiddleware", "excludedMiddleware"].every((name) => typeof candidate[name] === "function");
|
|
3041
|
+
}
|
|
3042
|
+
function routePath(path) {
|
|
3043
|
+
const trimmed = path.replace(/^\/+|\/+$/g, "").replace(/\/{2,}/g, "/");
|
|
3044
|
+
return trimmed === "" ? "" : `/${trimmed}`;
|
|
3045
|
+
}
|
|
3046
|
+
function expand(references, aliases, groups, out = [], visiting = /* @__PURE__ */ new Set()) {
|
|
3047
|
+
for (const reference of references) {
|
|
3048
|
+
if (typeof reference === "function") {
|
|
3049
|
+
out.push({ cls: reference, parameters: [] });
|
|
3050
|
+
continue;
|
|
3051
|
+
}
|
|
3052
|
+
const index = reference.indexOf(":");
|
|
3053
|
+
const name = index === -1 ? reference : reference.slice(0, index);
|
|
3054
|
+
if (index === -1 && Object.hasOwn(groups, name)) {
|
|
3055
|
+
if (visiting.has(name)) continue;
|
|
3056
|
+
visiting.add(name);
|
|
3057
|
+
expand(groups[name], aliases, groups, out, visiting);
|
|
3058
|
+
visiting.delete(name);
|
|
3059
|
+
} else if (Object.hasOwn(aliases, name)) {
|
|
3060
|
+
out.push({ cls: aliases[name], parameters: index === -1 ? [] : reference.slice(index + 1).split(",") });
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
return out;
|
|
3064
|
+
}
|
|
3065
|
+
function persistentPass(router, persistent, method, path) {
|
|
3066
|
+
if (!canMatch(router)) throw new LiveError("Live re-applies the page route's middleware to updates, which needs Oriel's router.");
|
|
3067
|
+
const routes = router.getRoutesByMatchOrder();
|
|
3068
|
+
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.");
|
|
3069
|
+
const verb = method.toUpperCase();
|
|
3070
|
+
const target2 = routePath(path);
|
|
3071
|
+
const patterns = router.getPatterns();
|
|
3072
|
+
const version = router.getPatternVersion();
|
|
3073
|
+
let matched;
|
|
3074
|
+
for (const candidate of routes) {
|
|
3075
|
+
const values2 = candidate.methods.includes(verb) ? candidate.match(target2, patterns, version) : void 0;
|
|
3076
|
+
if (values2 !== void 0) {
|
|
3077
|
+
matched = { route: candidate, values: values2 };
|
|
3078
|
+
break;
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
if (matched === void 0) return void 0;
|
|
3082
|
+
const { route: route2, values } = matched;
|
|
3083
|
+
const aliases = router.getMiddleware();
|
|
3084
|
+
const groups = router.getMiddlewareGroups();
|
|
3085
|
+
const wanted = new Set(persistent.flatMap((name) => Object.hasOwn(aliases, name) ? [aliases[name]] : []));
|
|
3086
|
+
const excluded = new Set(expand(route2.excludedMiddleware(), aliases, groups).map((entry) => entry.cls));
|
|
3087
|
+
const ids = /* @__PURE__ */ new Map();
|
|
3088
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3089
|
+
const middleware = expand(route2.gatherMiddleware(), aliases, groups).filter((entry) => {
|
|
3090
|
+
if (!ids.has(entry.cls)) ids.set(entry.cls, ids.size);
|
|
3091
|
+
const key = `${ids.get(entry.cls)}:${entry.parameters.join(",")}`;
|
|
3092
|
+
if (!wanted.has(entry.cls) || excluded.has(entry.cls) || seen.has(key)) return false;
|
|
3093
|
+
seen.add(key);
|
|
3094
|
+
return true;
|
|
3095
|
+
});
|
|
3096
|
+
const early = new Set(expand(["web", "api"], aliases, groups).map((entry) => entry.cls));
|
|
3097
|
+
for (const name of ["auth", "guest"]) if (Object.hasOwn(aliases, name)) early.add(aliases[name]);
|
|
3098
|
+
const before = middleware.filter((entry) => early.has(entry.cls));
|
|
3099
|
+
const after = middleware.filter((entry) => !early.has(entry.cls));
|
|
3100
|
+
return { router, route: route2, values, middleware: [...before, ...after], at: before.length };
|
|
3101
|
+
}
|
|
3102
|
+
function persistentMiddleware(router, persistent, method, path) {
|
|
3103
|
+
return [...persistentPass(router, persistent, method, path)?.middleware ?? []];
|
|
3104
|
+
}
|
|
3105
|
+
function currentRoute(container) {
|
|
3106
|
+
if (!container.bound(CurrentRoute)) return void 0;
|
|
3107
|
+
try {
|
|
3108
|
+
return container.make(CurrentRoute);
|
|
3109
|
+
} catch {
|
|
3110
|
+
return void 0;
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
async function throughMiddleware(container, request2, pass, destination) {
|
|
3114
|
+
const original = currentRoute(container);
|
|
3115
|
+
const page = { route: pass.route, parameters: { ...pass.values }, middleware: [] };
|
|
3116
|
+
let swapped = false;
|
|
3117
|
+
const show = () => {
|
|
3118
|
+
container.instance(CurrentRoute, page);
|
|
3119
|
+
swapped = true;
|
|
3120
|
+
};
|
|
3121
|
+
const restore = () => {
|
|
3122
|
+
if (!swapped) return;
|
|
3123
|
+
if (original === void 0) container.forgetInstance(CurrentRoute);
|
|
3124
|
+
else container.instance(CurrentRoute, original);
|
|
3125
|
+
swapped = false;
|
|
3126
|
+
};
|
|
3127
|
+
let ran;
|
|
3128
|
+
const run = async (index) => {
|
|
3129
|
+
if (index === pass.at) Object.assign(page.parameters, await pass.router.substituteBindings(pass.route, pass.values, container));
|
|
3130
|
+
if (index === pass.middleware.length) {
|
|
3131
|
+
restore();
|
|
3132
|
+
ran = { result: await destination() };
|
|
3133
|
+
return new Response(null, { status: 204 });
|
|
3134
|
+
}
|
|
3135
|
+
const { cls, parameters } = pass.middleware[index];
|
|
3136
|
+
const instance = container.make(cls);
|
|
3137
|
+
const next = () => run(index + 1);
|
|
3138
|
+
return instance.handle(request2, next, ...parameters);
|
|
3139
|
+
};
|
|
3140
|
+
show();
|
|
3141
|
+
try {
|
|
3142
|
+
const response2 = await run(0);
|
|
3143
|
+
return ran ? ran.result : response2;
|
|
3144
|
+
} finally {
|
|
3145
|
+
restore();
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
1105
3148
|
function isModel(value) {
|
|
1106
3149
|
if (typeof value !== "object" || value === null) return false;
|
|
1107
3150
|
const candidate = value;
|
|
1108
3151
|
return typeof candidate.getKey === "function" && typeof candidate.getKeyName === "function" && typeof candidate.getTable === "function" && typeof candidate.exists === "boolean";
|
|
1109
3152
|
}
|
|
1110
|
-
var
|
|
3153
|
+
var isPlainObject2 = (value) => {
|
|
1111
3154
|
const proto = Object.getPrototypeOf(value);
|
|
1112
3155
|
return proto === Object.prototype || proto === null;
|
|
1113
3156
|
};
|
|
@@ -1154,7 +3197,7 @@ function dehydrate(value, registry, path, seen = /* @__PURE__ */ new Set()) {
|
|
|
1154
3197
|
}
|
|
1155
3198
|
return [items.map((item, i) => dehydrate(item, registry, `${path}.${i}`, seen)), { s: "clctn" }];
|
|
1156
3199
|
}
|
|
1157
|
-
if (
|
|
3200
|
+
if (isPlainObject2(object)) {
|
|
1158
3201
|
const out = {};
|
|
1159
3202
|
for (const [key, item] of Object.entries(object)) out[key] = dehydrate(item, registry, `${path}.${key}`, seen);
|
|
1160
3203
|
return [out, { s: "obj" }];
|
|
@@ -1240,11 +3283,68 @@ function isUpdatable(instance, path) {
|
|
|
1240
3283
|
}
|
|
1241
3284
|
return !isModel(node) && !(node instanceof Collection);
|
|
1242
3285
|
}
|
|
1243
|
-
var
|
|
1244
|
-
function
|
|
1245
|
-
|
|
3286
|
+
var PAGINATED = /* @__PURE__ */ Symbol.for("oriel:live.pagination");
|
|
3287
|
+
function toPage(value) {
|
|
3288
|
+
const page = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : Number.NaN;
|
|
3289
|
+
return Number.isFinite(page) && page >= 1 ? Math.min(Math.floor(page), Number.MAX_SAFE_INTEGER) : 1;
|
|
3290
|
+
}
|
|
3291
|
+
async function hook(instance, name, ...args) {
|
|
3292
|
+
const method = instance[name];
|
|
3293
|
+
if (typeof method === "function") await method.apply(instance, args);
|
|
3294
|
+
}
|
|
3295
|
+
function WithPagination(Base) {
|
|
3296
|
+
class WithPaginationMixin extends Base {
|
|
3297
|
+
page = 1;
|
|
3298
|
+
nextPage() {
|
|
3299
|
+
return this.setPage(toPage(this.page) + 1);
|
|
3300
|
+
}
|
|
3301
|
+
previousPage() {
|
|
3302
|
+
return this.setPage(Math.max(toPage(this.page) - 1, 1));
|
|
3303
|
+
}
|
|
3304
|
+
gotoPage(page) {
|
|
3305
|
+
return this.setPage(page);
|
|
3306
|
+
}
|
|
3307
|
+
resetPage() {
|
|
3308
|
+
return this.setPage(1);
|
|
3309
|
+
}
|
|
3310
|
+
async setPage(page) {
|
|
3311
|
+
const next = toPage(page);
|
|
3312
|
+
await hook(this, "updatingPage", next);
|
|
3313
|
+
this.page = next;
|
|
3314
|
+
await hook(this, "updatedPage", next);
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
Object.defineProperty(WithPaginationMixin.prototype, PAGINATED, { value: true });
|
|
3318
|
+
return WithPaginationMixin;
|
|
3319
|
+
}
|
|
3320
|
+
function isPaginated(component) {
|
|
3321
|
+
return component[PAGINATED] === true;
|
|
3322
|
+
}
|
|
3323
|
+
var rendering = new AsyncLocalStorage();
|
|
3324
|
+
function renderPaginated(component, url2, run) {
|
|
3325
|
+
return rendering.run(isPaginated(component) ? { component, url: url2 } : void 0, run);
|
|
3326
|
+
}
|
|
3327
|
+
function outsidePagination(run) {
|
|
3328
|
+
return rendering.run(void 0, run);
|
|
3329
|
+
}
|
|
3330
|
+
var livePaginationState = {
|
|
3331
|
+
currentPage(pageName) {
|
|
3332
|
+
const current = rendering.getStore();
|
|
3333
|
+
if (current === void 0 || pageName !== "page") return void 0;
|
|
3334
|
+
const page = toPage(current.component.page);
|
|
3335
|
+
current.component.page = page;
|
|
3336
|
+
return page;
|
|
3337
|
+
},
|
|
3338
|
+
currentPath() {
|
|
3339
|
+
return rendering.getStore()?.url;
|
|
3340
|
+
}
|
|
3341
|
+
};
|
|
3342
|
+
var renderContext = new AsyncLocalStorage2();
|
|
3343
|
+
var pageRequest = new AsyncLocalStorage2();
|
|
3344
|
+
function optional(container, token2) {
|
|
3345
|
+
if (!container.bound(token2)) return void 0;
|
|
1246
3346
|
try {
|
|
1247
|
-
return container.make(
|
|
3347
|
+
return container.make(token2);
|
|
1248
3348
|
} catch {
|
|
1249
3349
|
return void 0;
|
|
1250
3350
|
}
|
|
@@ -1253,7 +3353,7 @@ var isRecord = (value) => typeof value === "object" && value !== null && !Array.
|
|
|
1253
3353
|
function setOwn(object, key, value) {
|
|
1254
3354
|
Object.defineProperty(object, key, { value, enumerable: true, writable: true, configurable: true });
|
|
1255
3355
|
}
|
|
1256
|
-
async function
|
|
3356
|
+
async function hook2(instance, name, ...args) {
|
|
1257
3357
|
const method = instance[name];
|
|
1258
3358
|
if (typeof method === "function") await method.apply(instance, args);
|
|
1259
3359
|
}
|
|
@@ -1291,8 +3391,8 @@ var LiveComponentManager = class {
|
|
|
1291
3391
|
if (!isComponentClass(cls)) throw new LiveComponentNotFoundError(name);
|
|
1292
3392
|
return cls;
|
|
1293
3393
|
}
|
|
1294
|
-
isLiveRequest(
|
|
1295
|
-
return
|
|
3394
|
+
isLiveRequest(request2) {
|
|
3395
|
+
return request2.header("X-Oriel-Live") === "true";
|
|
1296
3396
|
}
|
|
1297
3397
|
/** The first render: HTML with o:id, o:snapshot and o:effects on the root element. */
|
|
1298
3398
|
async mount(component, params = {}, key) {
|
|
@@ -1304,7 +3404,7 @@ var LiveComponentManager = class {
|
|
|
1304
3404
|
setOwn(context.children, childKey, existing);
|
|
1305
3405
|
return `<${existing[0]} o:id="${escapeAttribute2(existing[1])}"></${existing[0]}>`;
|
|
1306
3406
|
}
|
|
1307
|
-
const result = await this.mountComponent(component, params);
|
|
3407
|
+
const result = await outsidePagination(() => this.mountComponent(component, params));
|
|
1308
3408
|
setOwn(context.children, childKey, [result.tag, result.snapshot.memo.id]);
|
|
1309
3409
|
return result.html;
|
|
1310
3410
|
}
|
|
@@ -1316,14 +3416,15 @@ var LiveComponentManager = class {
|
|
|
1316
3416
|
const state = stateOf(instance);
|
|
1317
3417
|
state.id = randomBytes2(15).toString("base64url");
|
|
1318
3418
|
state.name = name;
|
|
1319
|
-
await
|
|
3419
|
+
await hook2(instance, "boot");
|
|
1320
3420
|
for (const [property, value] of Object.entries(params)) {
|
|
1321
3421
|
if (Object.hasOwn(instance, property)) instance[property] = value;
|
|
1322
3422
|
}
|
|
1323
3423
|
this.prepareComputed(instance);
|
|
1324
|
-
|
|
1325
|
-
const
|
|
1326
|
-
|
|
3424
|
+
const request2 = optional(this.container(), HttpRequest);
|
|
3425
|
+
const page = pageRequest.getStore() ?? { path: request2?.path() ?? "", method: request2?.method() ?? "GET" };
|
|
3426
|
+
await renderPaginated(instance, this.pageUrl(page.path), () => hook2(instance, "mount", params));
|
|
3427
|
+
const memo = { id: state.id, name, path: page.path, method: page.method, children: {}, errors: {} };
|
|
1327
3428
|
const { html, tag } = await this.render(instance, memo, {});
|
|
1328
3429
|
const effects = this.effects(instance);
|
|
1329
3430
|
const listeners = Object.keys(listenersOf(cls));
|
|
@@ -1333,10 +3434,11 @@ var LiveComponentManager = class {
|
|
|
1333
3434
|
return { instance, snapshot, effects, html: insertAttributesIntoRoot(html, attributes), tag };
|
|
1334
3435
|
}
|
|
1335
3436
|
/** Applies each component's updates and calls, re-renders it and returns its new snapshot and effects. */
|
|
1336
|
-
async update(
|
|
3437
|
+
async update(request2) {
|
|
1337
3438
|
const components = [];
|
|
1338
|
-
for (const component of
|
|
1339
|
-
const
|
|
3439
|
+
for (const component of request2.components) {
|
|
3440
|
+
const { memo } = this.verify(component.snapshot);
|
|
3441
|
+
const result = await pageRequest.run({ path: memo.path, method: memo.method }, () => this.updateComponent(component.snapshot, component.updates, component.calls));
|
|
1340
3442
|
components.push({ snapshot: JSON.stringify(result.snapshot), effects: result.effects });
|
|
1341
3443
|
}
|
|
1342
3444
|
return { components };
|
|
@@ -1349,22 +3451,24 @@ var LiveComponentManager = class {
|
|
|
1349
3451
|
state.id = snapshot.memo.id;
|
|
1350
3452
|
state.name = snapshot.memo.name;
|
|
1351
3453
|
state.errors = { ...snapshot.memo.errors };
|
|
1352
|
-
await
|
|
3454
|
+
await hook2(instance, "boot");
|
|
1353
3455
|
const registry = this.registry();
|
|
1354
3456
|
for (const [property, value] of Object.entries(snapshot.data)) {
|
|
1355
3457
|
Object.defineProperty(instance, property, { value: await hydrate(value, registry), enumerable: true, writable: true, configurable: true });
|
|
1356
3458
|
}
|
|
1357
3459
|
this.prepareComputed(instance);
|
|
1358
|
-
await hook(instance, "hydrate");
|
|
1359
3460
|
const returns = [];
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
3461
|
+
await renderPaginated(instance, this.pageUrl(snapshot.memo.path), async () => {
|
|
3462
|
+
await hook2(instance, "hydrate");
|
|
3463
|
+
try {
|
|
3464
|
+
for (const [path, value] of Object.entries(updates)) await this.applyUpdate(instance, path, value);
|
|
3465
|
+
for (const call of calls) returns.push(await this.call(instance, call.method, call.params));
|
|
3466
|
+
} catch (error) {
|
|
3467
|
+
const errors = error.errors;
|
|
3468
|
+
if (error.kind !== "Validation" || !isRecord(errors)) throw error;
|
|
3469
|
+
state.errors = { ...errors };
|
|
3470
|
+
}
|
|
3471
|
+
});
|
|
1368
3472
|
const memo = { ...snapshot.memo, children: { ...snapshot.memo.children } };
|
|
1369
3473
|
const effects = this.effects(instance);
|
|
1370
3474
|
let html;
|
|
@@ -1378,10 +3482,32 @@ var LiveComponentManager = class {
|
|
|
1378
3482
|
if (calls.length > 0) effects.returns = returns;
|
|
1379
3483
|
return { instance, snapshot: await this.snapshot(instance, memo), effects, html, tag };
|
|
1380
3484
|
}
|
|
1381
|
-
/**
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
3485
|
+
/**
|
|
3486
|
+
* The route action of POST /oriel/live/update. Each component's update runs behind the persistent middleware of the
|
|
3487
|
+
* route that rendered its page (Livewire's): `auth`, `verified`, `guest` and `can`, with the page route's parameters
|
|
3488
|
+
* bound again. A signed-out tab gets a 401, a revoked ability a 403 and a deleted model a 404, before the component
|
|
3489
|
+
* hydrates. When a middleware answers instead (a redirect), that is the response. A key first flashed during the
|
|
3490
|
+
* update shows in this response only, unless the update redirects.
|
|
3491
|
+
*/
|
|
3492
|
+
async handle(request2) {
|
|
3493
|
+
if (!this.isLiveRequest(request2)) throw new InvalidLiveRequestError("Live update requests need the X-Oriel-Live header.");
|
|
3494
|
+
const container = this.container();
|
|
3495
|
+
const persistent = optional(container, ConfigRepository)?.get("live.persistent_middleware", PERSISTENT_MIDDLEWARE) ?? PERSISTENT_MIDDLEWARE;
|
|
3496
|
+
const components = [];
|
|
3497
|
+
const flashed = trackFlash(optional(container, Session));
|
|
3498
|
+
for (const component of this.payload(request2).components) {
|
|
3499
|
+
const { memo } = this.verify(component.snapshot);
|
|
3500
|
+
const pass = persistentPass(container.make(Router), persistent, memo.method, memo.path);
|
|
3501
|
+
const update = () => this.updateComponent(component.snapshot, component.updates, component.calls);
|
|
3502
|
+
const result = await pageRequest.run(
|
|
3503
|
+
{ path: memo.path, method: memo.method },
|
|
3504
|
+
() => pass === void 0 || pass.middleware.length === 0 ? update() : throughMiddleware(container, request2, pass, update)
|
|
3505
|
+
);
|
|
3506
|
+
if (result instanceof Response) return result;
|
|
3507
|
+
components.push({ snapshot: JSON.stringify(result.snapshot), effects: result.effects });
|
|
3508
|
+
}
|
|
3509
|
+
flashed(components.some((component) => component.effects.redirect !== void 0));
|
|
3510
|
+
return { components };
|
|
1385
3511
|
}
|
|
1386
3512
|
/** A full-page component (Route.get('/counter', Counter)): mounted with the route parameters, inside the layout. */
|
|
1387
3513
|
async page(component) {
|
|
@@ -1389,9 +3515,9 @@ var LiveComponentManager = class {
|
|
|
1389
3515
|
const params = { ...optional(container, CurrentRoute)?.parameters ?? {} };
|
|
1390
3516
|
const cls = component.constructor;
|
|
1391
3517
|
const result = await this.mountComponent(cls, params, component);
|
|
1392
|
-
const
|
|
1393
|
-
if (
|
|
1394
|
-
return optional(container, Redirector)?.to(
|
|
3518
|
+
const redirect2 = result.effects.redirect;
|
|
3519
|
+
if (redirect2 !== void 0) {
|
|
3520
|
+
return optional(container, Redirector)?.to(redirect2) ?? new Response(null, { status: 302, headers: { Location: redirect2 } });
|
|
1395
3521
|
}
|
|
1396
3522
|
const layout = cls.layout ?? optional(container, ConfigRepository)?.get("live.layout") ?? "components.layouts.app";
|
|
1397
3523
|
const html = result.html;
|
|
@@ -1424,11 +3550,11 @@ var LiveComponentManager = class {
|
|
|
1424
3550
|
return this.container().make(Encrypter).getKey();
|
|
1425
3551
|
}
|
|
1426
3552
|
/** The request body, read from the raw JSON when the request has it, so TrimStrings can't alter typed values. */
|
|
1427
|
-
payload(
|
|
3553
|
+
payload(request2) {
|
|
1428
3554
|
let body;
|
|
1429
3555
|
try {
|
|
1430
|
-
const content =
|
|
1431
|
-
body = typeof content === "function" ? JSON.parse(content.call(
|
|
3556
|
+
const content = request2.content;
|
|
3557
|
+
body = typeof content === "function" ? JSON.parse(content.call(request2)) : request2.all();
|
|
1432
3558
|
} catch {
|
|
1433
3559
|
throw new InvalidLiveRequestError("The Live update request body is not valid JSON.");
|
|
1434
3560
|
}
|
|
@@ -1455,8 +3581,8 @@ var LiveComponentManager = class {
|
|
|
1455
3581
|
const [root, ...rest] = path.split(".");
|
|
1456
3582
|
const key = rest.length > 0 ? rest.join(".") : void 0;
|
|
1457
3583
|
const studly = Str.studly(root);
|
|
1458
|
-
await
|
|
1459
|
-
await
|
|
3584
|
+
await hook2(instance, "updating", path, value);
|
|
3585
|
+
await hook2(instance, `updating${studly}`, value, key);
|
|
1460
3586
|
const target2 = instance;
|
|
1461
3587
|
if (rest.length === 0) target2[root] = value;
|
|
1462
3588
|
else {
|
|
@@ -1474,8 +3600,8 @@ var LiveComponentManager = class {
|
|
|
1474
3600
|
assertSlot(node, last);
|
|
1475
3601
|
Object.defineProperty(node, last, { value, enumerable: true, writable: true, configurable: true });
|
|
1476
3602
|
}
|
|
1477
|
-
await
|
|
1478
|
-
await
|
|
3603
|
+
await hook2(instance, "updated", path, value);
|
|
3604
|
+
await hook2(instance, `updated${studly}`, value, key);
|
|
1479
3605
|
}
|
|
1480
3606
|
/** Runs one call from the browser: a magic action, an event, or a public method. Returns its return value. */
|
|
1481
3607
|
async call(instance, method, params) {
|
|
@@ -1503,18 +3629,35 @@ var LiveComponentManager = class {
|
|
|
1503
3629
|
if (!isCallable(instance, method)) throw new MethodNotCallableError(method, name);
|
|
1504
3630
|
return instance[method](...params);
|
|
1505
3631
|
}
|
|
1506
|
-
/**
|
|
3632
|
+
/**
|
|
3633
|
+
* Renders the component (rendering/rendered hooks, nested children tracked) and checks for a single root. A
|
|
3634
|
+
* WithPagination component's page and page URL are what paginate() reads while it renders, as in its mount() and in
|
|
3635
|
+
* its update's hooks and actions.
|
|
3636
|
+
*/
|
|
1507
3637
|
async render(instance, memo, previous) {
|
|
1508
|
-
await
|
|
3638
|
+
await hook2(instance, "rendering");
|
|
1509
3639
|
const context = { previous, children: {}, index: 0 };
|
|
1510
|
-
const html = await renderContext.run(
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
3640
|
+
const html = await renderContext.run(
|
|
3641
|
+
context,
|
|
3642
|
+
() => renderPaginated(instance, this.pageUrl(memo.path), async () => {
|
|
3643
|
+
const output = await instance.render();
|
|
3644
|
+
return typeof output === "string" ? output : this.container().make(ViewFactory).renderToString(output);
|
|
3645
|
+
})
|
|
3646
|
+
);
|
|
3647
|
+
await hook2(instance, "rendered", html);
|
|
1515
3648
|
memo.children = context.children;
|
|
1516
3649
|
return { html: html.trim(), tag: rootTag(html, memo.name) };
|
|
1517
3650
|
}
|
|
3651
|
+
/** The absolute URL of the page a memo path belongs to, on the current request's origin ('/' + path without one). */
|
|
3652
|
+
pageUrl(path) {
|
|
3653
|
+
const relative3 = `/${path.replace(/^\/+/, "")}`;
|
|
3654
|
+
const url2 = optional(this.container(), HttpRequest)?.url();
|
|
3655
|
+
try {
|
|
3656
|
+
return url2 === void 0 ? relative3 : new URL(relative3, url2).href;
|
|
3657
|
+
} catch {
|
|
3658
|
+
return relative3;
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
1518
3661
|
/** Dispatches, redirects: the effects every response carries. */
|
|
1519
3662
|
effects(instance) {
|
|
1520
3663
|
const state = stateOf(instance);
|
|
@@ -1533,7 +3676,7 @@ var LiveComponentManager = class {
|
|
|
1533
3676
|
}
|
|
1534
3677
|
/** dehydrate() hook, then the state as snapshot data, the error bag, and the checksum over both. */
|
|
1535
3678
|
async snapshot(instance, memo) {
|
|
1536
|
-
await
|
|
3679
|
+
await hook2(instance, "dehydrate");
|
|
1537
3680
|
const registry = this.registry();
|
|
1538
3681
|
const data = {};
|
|
1539
3682
|
for (const [property, value] of Object.entries(instance)) data[property] = dehydrate(value, registry, `${memo.name}.${property}`);
|
|
@@ -1563,7 +3706,7 @@ test('{{ name }} renders', async () => {
|
|
|
1563
3706
|
`;
|
|
1564
3707
|
var CLASS_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1565
3708
|
function importPath(from, to) {
|
|
1566
|
-
const path =
|
|
3709
|
+
const path = relative22(dirname2(from), to).split(sep22).join("/").replace(/\.tsx?$/, "");
|
|
1567
3710
|
return path.startsWith(".") ? path : `./${path}`;
|
|
1568
3711
|
}
|
|
1569
3712
|
function registerLiveCommands(kernel, app) {
|
|
@@ -1579,23 +3722,23 @@ function registerLiveCommands(kernel, app) {
|
|
|
1579
3722
|
const file = join23(directory, ...segments.slice(0, -1), `${className}.tsx`);
|
|
1580
3723
|
const force = this.option("force") === true;
|
|
1581
3724
|
if (existsSync22(file) && !force) {
|
|
1582
|
-
this.error(`Component [${
|
|
3725
|
+
this.error(`Component [${relative22(app.basePath(), file)}] already exists.`);
|
|
1583
3726
|
return 1;
|
|
1584
3727
|
}
|
|
1585
|
-
await
|
|
1586
|
-
await
|
|
1587
|
-
this.info(`Component [${
|
|
3728
|
+
await mkdir2(dirname2(file), { recursive: true });
|
|
3729
|
+
await writeFile2(file, COMPONENT_STUB.replace("{{ class }}", () => className));
|
|
3730
|
+
this.info(`Component [${relative22(app.basePath(), file)}] created successfully.`);
|
|
1588
3731
|
if (this.option("test") === true) {
|
|
1589
3732
|
const testFile = app.basePath(join23("tests", "Feature", "Live", ...segments.slice(0, -1), `${className}.test.ts`));
|
|
1590
3733
|
if (existsSync22(testFile) && !force) {
|
|
1591
|
-
this.error(`Test [${
|
|
3734
|
+
this.error(`Test [${relative22(app.basePath(), testFile)}] already exists.`);
|
|
1592
3735
|
return 1;
|
|
1593
3736
|
}
|
|
1594
3737
|
const name = segments.map((segment) => Str.kebab(segment)).join(".");
|
|
1595
3738
|
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 [${
|
|
3739
|
+
await mkdir2(dirname2(testFile), { recursive: true });
|
|
3740
|
+
await writeFile2(testFile, test);
|
|
3741
|
+
this.info(`Test [${relative22(app.basePath(), testFile)}] created successfully.`);
|
|
1599
3742
|
}
|
|
1600
3743
|
return 0;
|
|
1601
3744
|
}).purpose("Create a new Live component");
|
|
@@ -1603,9 +3746,10 @@ function registerLiveCommands(kernel, app) {
|
|
|
1603
3746
|
var UPDATE_URI = "/oriel/live/update";
|
|
1604
3747
|
var LiveServiceProvider = class extends ServiceProvider {
|
|
1605
3748
|
register() {
|
|
1606
|
-
this.mergeConfigFrom({ class_path: this.app.appPath("Live"), layout: "components.layouts.app" }, "live");
|
|
3749
|
+
this.mergeConfigFrom({ class_path: this.app.appPath("Live"), layout: "components.layouts.app", persistent_middleware: [...PERSISTENT_MIDDLEWARE] }, "live");
|
|
1607
3750
|
this.app.singleton(LiveComponentManager, () => new LiveComponentManager(this.app));
|
|
1608
3751
|
this.app.singleton(LiveManager, (container) => container.make(LiveComponentManager));
|
|
3752
|
+
this.app.singleton(PaginationState, () => livePaginationState);
|
|
1609
3753
|
}
|
|
1610
3754
|
async boot() {
|
|
1611
3755
|
const manager = this.app.make(LiveComponentManager);
|
|
@@ -1619,17 +3763,52 @@ var LiveServiceProvider = class extends ServiceProvider {
|
|
|
1619
3763
|
}
|
|
1620
3764
|
}
|
|
1621
3765
|
};
|
|
3766
|
+
function urlWindow(current, last, onEachSide) {
|
|
3767
|
+
const range = (from, to) => Array.from({ length: Math.max(to - from + 1, 0) }, (_, index) => from + index);
|
|
3768
|
+
if (last < onEachSide * 2 + 8) return range(1, last);
|
|
3769
|
+
const size = onEachSide + 4;
|
|
3770
|
+
if (current <= size) return [...range(1, size + onEachSide), "...", ...range(last - 1, last)];
|
|
3771
|
+
if (current > last - size) return [...range(1, 2), "...", ...range(last - (size + (onEachSide - 1)), last)];
|
|
3772
|
+
return [...range(1, 2), "...", ...range(current - onEachSide, current + onEachSide), "...", ...range(last - 1, last)];
|
|
3773
|
+
}
|
|
3774
|
+
function button(label, action, attributes = "") {
|
|
3775
|
+
const live2 = action === void 0 ? " disabled" : ` o:click="${escapeAttribute2(action)}"`;
|
|
3776
|
+
return `<button type="button" class="btn btn-secondary"${attributes}${live2}>${escapeAttribute2(label)}</button>`;
|
|
3777
|
+
}
|
|
3778
|
+
function Pagination({ paginator, class: className }) {
|
|
3779
|
+
if (typeof paginator.nextCursor === "function") {
|
|
3780
|
+
throw new LiveError("<Pagination> needs paginate() or simplePaginate(): a cursor paginator has no page numbers.");
|
|
3781
|
+
}
|
|
3782
|
+
const current = paginator.currentPage();
|
|
3783
|
+
const onFirst = paginator.onFirstPage();
|
|
3784
|
+
const hasMore = paginator.hasMorePages();
|
|
3785
|
+
if (onFirst && !hasMore) return { toHtml: () => "" };
|
|
3786
|
+
const parts = [button("\xAB Previous", onFirst ? void 0 : "previousPage")];
|
|
3787
|
+
if (typeof paginator.lastPage === "function") {
|
|
3788
|
+
const onEachSide = Number.isInteger(paginator.onEachSideCount) && paginator.onEachSideCount >= 0 ? paginator.onEachSideCount : 3;
|
|
3789
|
+
for (const page of urlWindow(current, paginator.lastPage(), onEachSide)) {
|
|
3790
|
+
if (page === "...") parts.push('<span aria-hidden="true">...</span>');
|
|
3791
|
+
else if (page === current) parts.push(`<button type="button" class="btn" aria-current="page" disabled>${page}</button>`);
|
|
3792
|
+
else parts.push(button(String(page), `gotoPage(${page})`, ` aria-label="Go to page ${page}"`));
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
parts.push(button("Next \xBB", hasMore ? "nextPage" : void 0));
|
|
3796
|
+
const classes = className === void 0 || className === "" ? "" : ` class="${escapeAttribute2(className)}"`;
|
|
3797
|
+
const html = `<nav aria-label="Pagination"${classes}>${parts.join("")}</nav>`;
|
|
3798
|
+
return { toHtml: () => html };
|
|
3799
|
+
}
|
|
1622
3800
|
|
|
1623
3801
|
// ../Testing/dist/index.js
|
|
1624
3802
|
import { AssertionError as AssertionError2 } from "assert";
|
|
1625
3803
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
1626
|
-
import { existsSync as
|
|
3804
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1627
3805
|
import { join as join6 } from "path";
|
|
1628
3806
|
import { createHmac as createHmac2 } from "crypto";
|
|
1629
3807
|
import { randomUUID } from "crypto";
|
|
1630
3808
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1631
3809
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
1632
3810
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
3811
|
+
import { posix } from "path";
|
|
1633
3812
|
import { deflateSync } from "zlib";
|
|
1634
3813
|
function fail2(message, details = {}) {
|
|
1635
3814
|
const error = new AssertionError2({ message, actual: details.actual, expected: details.expected, operator: "fail" });
|
|
@@ -1649,7 +3828,7 @@ function format(value) {
|
|
|
1649
3828
|
return String(value);
|
|
1650
3829
|
}
|
|
1651
3830
|
}
|
|
1652
|
-
function
|
|
3831
|
+
function escapeHtml2(value) {
|
|
1653
3832
|
return value.replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]);
|
|
1654
3833
|
}
|
|
1655
3834
|
function stripTags(html) {
|
|
@@ -2162,12 +4341,12 @@ function sizeOf(value) {
|
|
|
2162
4341
|
function toPlain(value) {
|
|
2163
4342
|
return value === void 0 ? void 0 : JSON.parse(JSON.stringify(value));
|
|
2164
4343
|
}
|
|
2165
|
-
function inertiaPageOf(
|
|
4344
|
+
function inertiaPageOf(response2) {
|
|
2166
4345
|
let page;
|
|
2167
4346
|
try {
|
|
2168
|
-
if (
|
|
4347
|
+
if (response2.headers.has("X-Inertia")) page = JSON.parse(response2.content);
|
|
2169
4348
|
else {
|
|
2170
|
-
const match = /<script data-page="[^"]*" type="application\/json">([\s\S]*?)<\/script>/.exec(
|
|
4349
|
+
const match = /<script data-page="[^"]*" type="application\/json">([\s\S]*?)<\/script>/.exec(response2.content);
|
|
2171
4350
|
page = match === null ? void 0 : JSON.parse(match[1]);
|
|
2172
4351
|
}
|
|
2173
4352
|
} catch {
|
|
@@ -2180,11 +4359,11 @@ function inertiaPageOf(response) {
|
|
|
2180
4359
|
var AssertableInertia = class _AssertableInertia extends AssertableJson {
|
|
2181
4360
|
#page;
|
|
2182
4361
|
#response;
|
|
2183
|
-
static fromTestResponse(
|
|
2184
|
-
const page = inertiaPageOf(
|
|
4362
|
+
static fromTestResponse(response2) {
|
|
4363
|
+
const page = inertiaPageOf(response2);
|
|
2185
4364
|
const instance = new _AssertableInertia(page.props);
|
|
2186
4365
|
instance.#page = page;
|
|
2187
|
-
instance.#response =
|
|
4366
|
+
instance.#response = response2;
|
|
2188
4367
|
return instance;
|
|
2189
4368
|
}
|
|
2190
4369
|
/** Scopes are plain AssertableJson: the page-level methods only make sense at the root. */
|
|
@@ -2204,7 +4383,7 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
|
|
|
2204
4383
|
if (shouldExist === true || shouldExist === void 0 && config?.get("inertia.testing.ensure_pages_exist", true) !== false) {
|
|
2205
4384
|
const paths = config?.get("inertia.pages.paths", []) ?? [];
|
|
2206
4385
|
const extensions = config?.get("inertia.pages.extensions", []) ?? [];
|
|
2207
|
-
const exists = paths.some((path) => extensions.some((extension) =>
|
|
4386
|
+
const exists = paths.some((path) => extensions.some((extension) => existsSync6(join6(path, `${value}.${extension}`))));
|
|
2208
4387
|
if (!exists) fail2(`Inertia page component file [${value}] does not exist.`);
|
|
2209
4388
|
}
|
|
2210
4389
|
return this;
|
|
@@ -2253,8 +4432,8 @@ var AssertableInertia = class _AssertableInertia extends AssertableJson {
|
|
|
2253
4432
|
if (onlyList !== void 0 || exceptList !== void 0) headers["X-Inertia-Partial-Component"] = this.page.component;
|
|
2254
4433
|
if (onlyList !== void 0) headers["X-Inertia-Partial-Data"] = onlyList;
|
|
2255
4434
|
if (exceptList !== void 0) headers["X-Inertia-Partial-Except"] = exceptList;
|
|
2256
|
-
const
|
|
2257
|
-
const reloaded = _AssertableInertia.fromTestResponse(
|
|
4435
|
+
const response2 = await client.request("GET", this.page.url, { headers });
|
|
4436
|
+
const reloaded = _AssertableInertia.fromTestResponse(response2);
|
|
2258
4437
|
reloaded.component(this.page.component, false).url(this.page.url).version(this.page.version);
|
|
2259
4438
|
await callback?.(reloaded);
|
|
2260
4439
|
return this;
|
|
@@ -2349,8 +4528,8 @@ function isExpired(cookie, now = Date.now()) {
|
|
|
2349
4528
|
if (cookie.maxAge !== void 0 && !Number.isNaN(cookie.maxAge)) return cookie.maxAge <= 0;
|
|
2350
4529
|
return cookie.expires !== void 0 && cookie.expires.getTime() <= now;
|
|
2351
4530
|
}
|
|
2352
|
-
function responseCookies(
|
|
2353
|
-
return
|
|
4531
|
+
function responseCookies(response2) {
|
|
4532
|
+
return response2.headers.getSetCookie().map(parseSetCookie);
|
|
2354
4533
|
}
|
|
2355
4534
|
function cookieHeader(cookies) {
|
|
2356
4535
|
return [...cookies].map(([name, value]) => `${encodeURIComponent(name)}=${encodeURIComponent(value)}`).join("; ");
|
|
@@ -2367,8 +4546,8 @@ var TestResponse = class _TestResponse {
|
|
|
2367
4546
|
context;
|
|
2368
4547
|
decoded;
|
|
2369
4548
|
/** Reads the body once, so every assertion is synchronous. */
|
|
2370
|
-
static async from(
|
|
2371
|
-
return new _TestResponse(
|
|
4549
|
+
static async from(response2, context = {}) {
|
|
4550
|
+
return new _TestResponse(response2, await response2.text(), context);
|
|
2372
4551
|
}
|
|
2373
4552
|
get status() {
|
|
2374
4553
|
return this.baseResponse.status;
|
|
@@ -2860,11 +5039,11 @@ ${JSON.stringify(errors, null, 2)}`, { actual: errors, expected: {} });
|
|
|
2860
5039
|
return Arr.get(this.viewDataOf(this.ensureView()), key);
|
|
2861
5040
|
}
|
|
2862
5041
|
// Internals ---------------------------------------------------------------------------------------------
|
|
2863
|
-
absolute(
|
|
5042
|
+
absolute(url2) {
|
|
2864
5043
|
try {
|
|
2865
|
-
return new URL(
|
|
5044
|
+
return new URL(url2, this.context.request?.url ?? "http://localhost").href;
|
|
2866
5045
|
} catch {
|
|
2867
|
-
return
|
|
5046
|
+
return url2;
|
|
2868
5047
|
}
|
|
2869
5048
|
}
|
|
2870
5049
|
excerpt(text = this.content) {
|
|
@@ -2919,7 +5098,7 @@ ${this.excerpt()}`;
|
|
|
2919
5098
|
return decryptCookie(app.make(Encrypter), cookie.name, cookie.value);
|
|
2920
5099
|
}
|
|
2921
5100
|
needles(value, escape) {
|
|
2922
|
-
return [value].flat().map((needle) => escape ?
|
|
5101
|
+
return [value].flat().map((needle) => escape ? escapeHtml2(needle) : needle);
|
|
2923
5102
|
}
|
|
2924
5103
|
inOrder(haystack, needles) {
|
|
2925
5104
|
let position = 0;
|
|
@@ -2964,9 +5143,9 @@ function recordingViewFactory(factory, views) {
|
|
|
2964
5143
|
}
|
|
2965
5144
|
function recordingExceptionHandler(handler, record) {
|
|
2966
5145
|
return forward(handler, {
|
|
2967
|
-
async render(
|
|
5146
|
+
async render(request2, error) {
|
|
2968
5147
|
record(error);
|
|
2969
|
-
return handler.render(
|
|
5148
|
+
return handler.render(request2, error);
|
|
2970
5149
|
}
|
|
2971
5150
|
});
|
|
2972
5151
|
}
|
|
@@ -3004,13 +5183,13 @@ var HttpClient = class {
|
|
|
3004
5183
|
/** Requests whose actingAs users reached the guards (the AuthFactory they went through). */
|
|
3005
5184
|
actingAsApplied = /* @__PURE__ */ new WeakMap();
|
|
3006
5185
|
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
|
-
|
|
5186
|
+
let response2 = await this.send(this.buildRequest(method, uri, options));
|
|
5187
|
+
if (!this.followRedirects) return response2;
|
|
5188
|
+
for (let hops = 0; isRedirect(response2.status); hops++) {
|
|
5189
|
+
if (hops >= MAX_REDIRECTS) fail2(`Too many redirects (more than ${MAX_REDIRECTS}), last location [${response2.headers.get("Location")}].`);
|
|
5190
|
+
response2 = await this.send(this.buildRequest("GET", response2.headers.get("Location") ?? "/", {}));
|
|
3012
5191
|
}
|
|
3013
|
-
return
|
|
5192
|
+
return response2;
|
|
3014
5193
|
}
|
|
3015
5194
|
/** Runs `fn` in a scope prepared like a request's (carried session, actingAs users), e.g. for assertAuthenticated(). */
|
|
3016
5195
|
async inScope(fn) {
|
|
@@ -3021,23 +5200,48 @@ var HttpClient = class {
|
|
|
3021
5200
|
return fn(scope);
|
|
3022
5201
|
});
|
|
3023
5202
|
}
|
|
5203
|
+
/**
|
|
5204
|
+
* Runs `run` in a request scope like this client's requests have (a request for `method uri` bound, the carried
|
|
5205
|
+
* session, actingAs users), then carries the session and the users on, as after a request. Live.test() runs its
|
|
5206
|
+
* mount and updates through it.
|
|
5207
|
+
*/
|
|
5208
|
+
async withinRequest(run, method = "GET", uri = "/") {
|
|
5209
|
+
await this.app.boot();
|
|
5210
|
+
const request2 = this.buildRequest(method, uri, {});
|
|
5211
|
+
setClientAddress2(request2, "127.0.0.1");
|
|
5212
|
+
const scope = this.app.createScope();
|
|
5213
|
+
const context = { request: request2, app: this.app, client: this };
|
|
5214
|
+
const result = await scope.run(async () => {
|
|
5215
|
+
this.beforeRequest(scope, context);
|
|
5216
|
+
scope.instance(HttpRequest, new IncomingRequest(request2));
|
|
5217
|
+
if (scope.bound(AuthFactory)) {
|
|
5218
|
+
try {
|
|
5219
|
+
await scope.make(AuthFactory).guard().check();
|
|
5220
|
+
} catch {
|
|
5221
|
+
}
|
|
5222
|
+
}
|
|
5223
|
+
return run();
|
|
5224
|
+
});
|
|
5225
|
+
await scope.run(() => this.afterRequest(scope, context));
|
|
5226
|
+
return result;
|
|
5227
|
+
}
|
|
3024
5228
|
/** Runs the request through the app's real HttpKernel, in a request scope this client owns. */
|
|
3025
|
-
async send(
|
|
5229
|
+
async send(request2) {
|
|
3026
5230
|
const app = this.app;
|
|
3027
5231
|
await app.boot();
|
|
3028
|
-
|
|
5232
|
+
setClientAddress2(request2, "127.0.0.1");
|
|
3029
5233
|
const scope = app.createScope();
|
|
3030
|
-
const context = { request, app, client: this };
|
|
3031
|
-
const
|
|
5234
|
+
const context = { request: request2, app, client: this };
|
|
5235
|
+
const response2 = await scope.run(async () => {
|
|
3032
5236
|
await this.beforeRequest(scope, context);
|
|
3033
5237
|
this.previousUrl = void 0;
|
|
3034
|
-
return scope.make(HttpKernel).handle(
|
|
5238
|
+
return scope.make(HttpKernel).handle(request2);
|
|
3035
5239
|
});
|
|
3036
5240
|
await scope.run(() => this.afterRequest(scope, context));
|
|
3037
|
-
this.rememberCookies(
|
|
3038
|
-
await app.make(HttpKernel).terminate?.(
|
|
5241
|
+
this.rememberCookies(response2);
|
|
5242
|
+
await app.make(HttpKernel).terminate?.(request2, response2);
|
|
3039
5243
|
if (!this.handleExceptions && context.exception !== void 0) throw context.exception;
|
|
3040
|
-
return TestResponse.from(
|
|
5244
|
+
return TestResponse.from(response2, context);
|
|
3041
5245
|
}
|
|
3042
5246
|
/** Prepares the request scope: recorders, the carried session and actingAs users. */
|
|
3043
5247
|
beforeRequest(scope, context) {
|
|
@@ -3092,12 +5296,12 @@ var HttpClient = class {
|
|
|
3092
5296
|
}
|
|
3093
5297
|
buildRequest(method, uri, options) {
|
|
3094
5298
|
const verb = method.toUpperCase();
|
|
3095
|
-
const
|
|
5299
|
+
const url2 = new URL(uri, this.baseUrl());
|
|
3096
5300
|
const headers = new Headers({ ...this.headers, ...options.headers });
|
|
3097
5301
|
let body;
|
|
3098
5302
|
const data = options.data ?? {};
|
|
3099
5303
|
if (verb === "GET" || verb === "HEAD") {
|
|
3100
|
-
for (const [key, value] of flatten(data)) if (typeof value === "string")
|
|
5304
|
+
for (const [key, value] of flatten(data)) if (typeof value === "string") url2.searchParams.append(key, value);
|
|
3101
5305
|
} else if (options.json) {
|
|
3102
5306
|
body = JSON.stringify(data);
|
|
3103
5307
|
} else if (Object.keys(data).length > 0) {
|
|
@@ -3116,7 +5320,7 @@ var HttpClient = class {
|
|
|
3116
5320
|
}
|
|
3117
5321
|
const cookies = this.cookieHeader();
|
|
3118
5322
|
if (cookies !== "") headers.set("Cookie", cookies);
|
|
3119
|
-
return new Request(
|
|
5323
|
+
return new Request(url2, { method: verb, headers, body });
|
|
3120
5324
|
}
|
|
3121
5325
|
baseUrl() {
|
|
3122
5326
|
const configured = this.app.bound(ConfigRepository) ? this.app.make(ConfigRepository).get("app.url") : void 0;
|
|
@@ -3131,8 +5335,8 @@ var HttpClient = class {
|
|
|
3131
5335
|
return cookieHeader(values);
|
|
3132
5336
|
}
|
|
3133
5337
|
/** 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(
|
|
5338
|
+
rememberCookies(response2) {
|
|
5339
|
+
for (const cookie of responseCookies(response2)) {
|
|
3136
5340
|
if (isExpired(cookie)) this.cookies.delete(cookie.name);
|
|
3137
5341
|
else this.cookies.set(cookie.name, { value: cookie.value, encrypt: false });
|
|
3138
5342
|
}
|
|
@@ -3141,9 +5345,9 @@ var HttpClient = class {
|
|
|
3141
5345
|
function isRedirect(status) {
|
|
3142
5346
|
return status >= 300 && status < 400 && status !== 304;
|
|
3143
5347
|
}
|
|
3144
|
-
function
|
|
5348
|
+
function setClientAddress2(request2, address) {
|
|
3145
5349
|
const g = globalThis;
|
|
3146
|
-
(g[CLIENT_ADDRESS] ??= /* @__PURE__ */ new WeakMap()).set(
|
|
5350
|
+
(g[CLIENT_ADDRESS] ??= /* @__PURE__ */ new WeakMap()).set(request2, address);
|
|
3147
5351
|
}
|
|
3148
5352
|
function flatten(data, prefix = "") {
|
|
3149
5353
|
const out = [];
|
|
@@ -3239,11 +5443,43 @@ var InteractsWithApplication = class {
|
|
|
3239
5443
|
if (global === app) Container.setInstance(void 0);
|
|
3240
5444
|
}
|
|
3241
5445
|
}
|
|
5446
|
+
/**
|
|
5447
|
+
* Laravel's withoutVite(): in this test's requests vite([...]) and viteReactRefresh() render nothing, so feature
|
|
5448
|
+
* tests of pages pass before `vite build` has written public/build/manifest.json.
|
|
5449
|
+
*/
|
|
5450
|
+
withoutVite() {
|
|
5451
|
+
const app = this.app;
|
|
5452
|
+
app.scoped(Vite, () => new WithoutVite(app));
|
|
5453
|
+
return this;
|
|
5454
|
+
}
|
|
3242
5455
|
/** Runs in tearDown(), before the application is thrown away (in registration order). */
|
|
3243
5456
|
beforeApplicationDestroyed(callback) {
|
|
3244
5457
|
this.destroying.push(callback);
|
|
3245
5458
|
}
|
|
3246
5459
|
};
|
|
5460
|
+
var WithoutVite = class extends Vite {
|
|
5461
|
+
tags() {
|
|
5462
|
+
return new ViteHtml("");
|
|
5463
|
+
}
|
|
5464
|
+
reactRefresh() {
|
|
5465
|
+
return void 0;
|
|
5466
|
+
}
|
|
5467
|
+
asset() {
|
|
5468
|
+
return "";
|
|
5469
|
+
}
|
|
5470
|
+
content() {
|
|
5471
|
+
return "";
|
|
5472
|
+
}
|
|
5473
|
+
manifestHash() {
|
|
5474
|
+
return void 0;
|
|
5475
|
+
}
|
|
5476
|
+
preloadedAssets() {
|
|
5477
|
+
return {};
|
|
5478
|
+
}
|
|
5479
|
+
toHtml() {
|
|
5480
|
+
return "";
|
|
5481
|
+
}
|
|
5482
|
+
};
|
|
3247
5483
|
async function disconnectAll(db) {
|
|
3248
5484
|
const manager = db;
|
|
3249
5485
|
if (typeof manager.disconnectAll === "function") await manager.disconnectAll();
|
|
@@ -3258,6 +5494,11 @@ var MakesHttpRequests = class extends InteractsWithApplication {
|
|
|
3258
5494
|
createHttpClient() {
|
|
3259
5495
|
return new HttpClient(this.app);
|
|
3260
5496
|
}
|
|
5497
|
+
/** Live.test() runs in this test's request scope (Live reads the same token: no import between the packages). */
|
|
5498
|
+
async setUp() {
|
|
5499
|
+
await super.setUp();
|
|
5500
|
+
this.app.instance(token("Testing.RequestScope"), (run) => this.http.withinRequest(run));
|
|
5501
|
+
}
|
|
3261
5502
|
async tearDown() {
|
|
3262
5503
|
try {
|
|
3263
5504
|
await super.tearDown();
|
|
@@ -3273,8 +5514,8 @@ var MakesHttpRequests = class extends InteractsWithApplication {
|
|
|
3273
5514
|
this.http.headers[name] = value;
|
|
3274
5515
|
return this;
|
|
3275
5516
|
}
|
|
3276
|
-
withToken(
|
|
3277
|
-
return this.withHeader("Authorization", `${type} ${
|
|
5517
|
+
withToken(token2, type = "Bearer") {
|
|
5518
|
+
return this.withHeader("Authorization", `${type} ${token2}`);
|
|
3278
5519
|
}
|
|
3279
5520
|
withBasicAuth(username, password) {
|
|
3280
5521
|
return this.withToken(Buffer.from(`${username}:${password}`).toString("base64"), "Basic");
|
|
@@ -3305,9 +5546,9 @@ var MakesHttpRequests = class extends InteractsWithApplication {
|
|
|
3305
5546
|
return this;
|
|
3306
5547
|
}
|
|
3307
5548
|
/** 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",
|
|
5549
|
+
from(url2) {
|
|
5550
|
+
this.http.previousUrl = url2;
|
|
5551
|
+
return this.withHeader("Referer", url2);
|
|
3311
5552
|
}
|
|
3312
5553
|
/** Puts data in the session for the following requests (Laravel's withSession()). */
|
|
3313
5554
|
withSession(data) {
|
|
@@ -3457,13 +5698,13 @@ var InteractsWithConsole = class extends InteractsWithDatabase {
|
|
|
3457
5698
|
};
|
|
3458
5699
|
var TestCase = class extends InteractsWithConsole {
|
|
3459
5700
|
};
|
|
3460
|
-
function install(
|
|
3461
|
-
facade(
|
|
5701
|
+
function install(token2, fake) {
|
|
5702
|
+
facade(token2).swap(fake);
|
|
3462
5703
|
return fake;
|
|
3463
5704
|
}
|
|
3464
|
-
function bound(
|
|
5705
|
+
function bound(token2) {
|
|
3465
5706
|
const container = currentContainer();
|
|
3466
|
-
return container !== void 0 && container.bound(
|
|
5707
|
+
return container !== void 0 && container.bound(token2) ? container.make(token2) : void 0;
|
|
3467
5708
|
}
|
|
3468
5709
|
function nameOf(target2) {
|
|
3469
5710
|
if (typeof target2 === "string") return target2;
|
|
@@ -3486,7 +5727,7 @@ var QUEUE_OPTIONS = ["connection", "queue", "delay", "tries", "backoff", "timeou
|
|
|
3486
5727
|
function isQueueable(job) {
|
|
3487
5728
|
if (typeof job !== "object" || job === null) return false;
|
|
3488
5729
|
const members = job;
|
|
3489
|
-
if (members[SHOULD_QUEUE] === true) return true;
|
|
5730
|
+
if (members.shouldQueue === true || members[SHOULD_QUEUE] === true) return true;
|
|
3490
5731
|
if (QUEUE_OPTIONS.some((option) => members[option] !== void 0)) return true;
|
|
3491
5732
|
return typeof members.uniqueId === "function" || typeof members.retryUntil === "function";
|
|
3492
5733
|
}
|
|
@@ -4159,8 +6400,8 @@ var AnonymousNotifiable = class {
|
|
|
4159
6400
|
}
|
|
4160
6401
|
dispatcher;
|
|
4161
6402
|
routes = /* @__PURE__ */ new Map();
|
|
4162
|
-
route(channel,
|
|
4163
|
-
this.routes.set(channel,
|
|
6403
|
+
route(channel, route2) {
|
|
6404
|
+
this.routes.set(channel, route2);
|
|
4164
6405
|
return this;
|
|
4165
6406
|
}
|
|
4166
6407
|
routeNotificationFor(channel) {
|
|
@@ -4204,8 +6445,8 @@ var NotificationFake = class _NotificationFake {
|
|
|
4204
6445
|
async sendNow(notifiables, notification, channels) {
|
|
4205
6446
|
this.record(notifiables, notification, channels);
|
|
4206
6447
|
}
|
|
4207
|
-
route(channel,
|
|
4208
|
-
return new AnonymousNotifiable(this).route(channel,
|
|
6448
|
+
route(channel, route2) {
|
|
6449
|
+
return new AnonymousNotifiable(this).route(channel, route2);
|
|
4209
6450
|
}
|
|
4210
6451
|
/** The Notifications provider calls this in boot(); a fake has no channels to register. */
|
|
4211
6452
|
extend(_channel, _factory) {
|
|
@@ -4304,8 +6545,74 @@ var MIME = {
|
|
|
4304
6545
|
mp4: "video/mp4"
|
|
4305
6546
|
};
|
|
4306
6547
|
function guessMimeType(path) {
|
|
4307
|
-
const
|
|
4308
|
-
|
|
6548
|
+
const base = path.slice(path.lastIndexOf("/") + 1);
|
|
6549
|
+
const dot = base.lastIndexOf(".");
|
|
6550
|
+
const extension = dot === -1 ? "" : base.slice(dot + 1).toLowerCase();
|
|
6551
|
+
return Object.hasOwn(MIME, extension) ? MIME[extension] : void 0;
|
|
6552
|
+
}
|
|
6553
|
+
var DISK_TYPES = {
|
|
6554
|
+
txt: "text/plain",
|
|
6555
|
+
html: "text/html",
|
|
6556
|
+
htm: "text/html",
|
|
6557
|
+
css: "text/css",
|
|
6558
|
+
csv: "text/csv",
|
|
6559
|
+
md: "text/markdown",
|
|
6560
|
+
js: "text/javascript",
|
|
6561
|
+
mjs: "text/javascript",
|
|
6562
|
+
json: "application/json",
|
|
6563
|
+
xml: "application/xml",
|
|
6564
|
+
pdf: "application/pdf",
|
|
6565
|
+
zip: "application/zip",
|
|
6566
|
+
gz: "application/gzip",
|
|
6567
|
+
tar: "application/x-tar",
|
|
6568
|
+
"7z": "application/x-7z-compressed",
|
|
6569
|
+
rar: "application/vnd.rar",
|
|
6570
|
+
epub: "application/epub+zip",
|
|
6571
|
+
rtf: "application/rtf",
|
|
6572
|
+
doc: "application/msword",
|
|
6573
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
6574
|
+
xls: "application/vnd.ms-excel",
|
|
6575
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
6576
|
+
ppt: "application/vnd.ms-powerpoint",
|
|
6577
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
6578
|
+
odt: "application/vnd.oasis.opendocument.text",
|
|
6579
|
+
ods: "application/vnd.oasis.opendocument.spreadsheet",
|
|
6580
|
+
odp: "application/vnd.oasis.opendocument.presentation",
|
|
6581
|
+
png: "image/png",
|
|
6582
|
+
jpg: "image/jpeg",
|
|
6583
|
+
jpeg: "image/jpeg",
|
|
6584
|
+
gif: "image/gif",
|
|
6585
|
+
webp: "image/webp",
|
|
6586
|
+
avif: "image/avif",
|
|
6587
|
+
svg: "image/svg+xml",
|
|
6588
|
+
ico: "image/x-icon",
|
|
6589
|
+
bmp: "image/bmp",
|
|
6590
|
+
tif: "image/tiff",
|
|
6591
|
+
tiff: "image/tiff",
|
|
6592
|
+
heic: "image/heic",
|
|
6593
|
+
heif: "image/heif",
|
|
6594
|
+
mp3: "audio/mpeg",
|
|
6595
|
+
m4a: "audio/mp4",
|
|
6596
|
+
wav: "audio/wav",
|
|
6597
|
+
ogg: "audio/ogg",
|
|
6598
|
+
oga: "audio/ogg",
|
|
6599
|
+
opus: "audio/ogg",
|
|
6600
|
+
flac: "audio/flac",
|
|
6601
|
+
aac: "audio/aac",
|
|
6602
|
+
mp4: "video/mp4",
|
|
6603
|
+
mov: "video/quicktime",
|
|
6604
|
+
webm: "video/webm",
|
|
6605
|
+
ogv: "video/ogg",
|
|
6606
|
+
avi: "video/x-msvideo",
|
|
6607
|
+
woff: "font/woff",
|
|
6608
|
+
woff2: "font/woff2",
|
|
6609
|
+
ttf: "font/ttf",
|
|
6610
|
+
otf: "font/otf",
|
|
6611
|
+
wasm: "application/wasm"
|
|
6612
|
+
};
|
|
6613
|
+
function diskType(extension) {
|
|
6614
|
+
const key = extension.replace(/^\./, "").toLowerCase();
|
|
6615
|
+
return Object.hasOwn(DISK_TYPES, key) ? DISK_TYPES[key] : void 0;
|
|
4309
6616
|
}
|
|
4310
6617
|
function hashName(file) {
|
|
4311
6618
|
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -4361,17 +6668,24 @@ var FakeDisk = class {
|
|
|
4361
6668
|
async put(path, contents, options = {}) {
|
|
4362
6669
|
const key = normalize(path);
|
|
4363
6670
|
const bytes = typeof contents === "string" ? new TextEncoder().encode(contents) : contents instanceof Uint8Array ? contents.slice() : await collect(contents);
|
|
4364
|
-
this.
|
|
6671
|
+
this.write(key, bytes, options.mimeType ?? diskType(posix.extname(key)), options);
|
|
4365
6672
|
return true;
|
|
4366
6673
|
}
|
|
4367
6674
|
putFile(directory, file, options = {}) {
|
|
4368
6675
|
return this.putFileAs(directory, file, hashName(file), options);
|
|
4369
6676
|
}
|
|
6677
|
+
/**
|
|
6678
|
+
* The upload's own type, as the S3 disk sends it: its extension(), never the MIME type the client sent as such nor
|
|
6679
|
+
* the stored name's type.
|
|
6680
|
+
*/
|
|
4370
6681
|
async putFileAs(directory, file, name, options = {}) {
|
|
4371
6682
|
const path = normalize(`${directory}/${name}`);
|
|
4372
|
-
|
|
6683
|
+
this.write(path, new Uint8Array(await file.arrayBuffer()), options.mimeType ?? diskType(file.extension()), options);
|
|
4373
6684
|
return path;
|
|
4374
6685
|
}
|
|
6686
|
+
write(key, bytes, mimeType, options) {
|
|
6687
|
+
this.stored.set(key, { bytes, visibility: options.visibility ?? "private", mimeType, modified: /* @__PURE__ */ new Date() });
|
|
6688
|
+
}
|
|
4375
6689
|
async delete(paths) {
|
|
4376
6690
|
for (const path of [paths].flat()) this.stored.delete(normalize(path));
|
|
4377
6691
|
return true;
|
|
@@ -4565,6 +6879,37 @@ var StorageFake = class _StorageFake {
|
|
|
4565
6879
|
return this.disk().path(path);
|
|
4566
6880
|
}
|
|
4567
6881
|
};
|
|
6882
|
+
var ascii = (text) => [...text].map((char) => char.charCodeAt(0));
|
|
6883
|
+
var u16be = (n) => [n >>> 8 & 255, n & 255];
|
|
6884
|
+
var u16le = (n) => [n & 255, n >>> 8 & 255];
|
|
6885
|
+
var u32le = (n) => [n & 255, n >>> 8 & 255, n >>> 16 & 255, n >>> 24 & 255];
|
|
6886
|
+
function concat(...parts) {
|
|
6887
|
+
const out = new Uint8Array(parts.reduce((n, part) => n + part.length, 0));
|
|
6888
|
+
let offset = 0;
|
|
6889
|
+
for (const part of parts) {
|
|
6890
|
+
out.set(part, offset);
|
|
6891
|
+
offset += part.length;
|
|
6892
|
+
}
|
|
6893
|
+
return out;
|
|
6894
|
+
}
|
|
6895
|
+
var BitWriter = class {
|
|
6896
|
+
bytes = [];
|
|
6897
|
+
buffer = 0;
|
|
6898
|
+
count = 0;
|
|
6899
|
+
write(value, bits) {
|
|
6900
|
+
this.buffer |= value << this.count;
|
|
6901
|
+
this.count += bits;
|
|
6902
|
+
while (this.count >= 8) {
|
|
6903
|
+
this.bytes.push(this.buffer & 255);
|
|
6904
|
+
this.buffer >>>= 8;
|
|
6905
|
+
this.count -= 8;
|
|
6906
|
+
}
|
|
6907
|
+
}
|
|
6908
|
+
finish() {
|
|
6909
|
+
if (this.count > 0) this.bytes.push(this.buffer & 255);
|
|
6910
|
+
return Uint8Array.from(this.bytes);
|
|
6911
|
+
}
|
|
6912
|
+
};
|
|
4568
6913
|
var CRC_TABLE = Array.from({ length: 256 }, (_, n) => {
|
|
4569
6914
|
let c = n;
|
|
4570
6915
|
for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
@@ -4592,23 +6937,175 @@ function png(width, height) {
|
|
|
4592
6937
|
header.set([8, 2, 0, 0, 0], 8);
|
|
4593
6938
|
const rows = new Uint8Array((width * 3 + 1) * height).fill(255);
|
|
4594
6939
|
for (let y = 0; y < height; y++) rows[y * (width * 3 + 1)] = 0;
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
6940
|
+
return concat([137, 80, 78, 71, 13, 10, 26, 10], chunk("IHDR", header), chunk("IDAT", deflateSync(rows)), chunk("IEND", new Uint8Array()));
|
|
6941
|
+
}
|
|
6942
|
+
var oneCodeTable = (tableClass) => [255, 196, ...u16be(20), tableClass, 1, ...new Array(15).fill(0), 0];
|
|
6943
|
+
function jpeg(width, height) {
|
|
6944
|
+
const bits = Math.ceil(width / 8) * Math.ceil(height / 8) * 2;
|
|
6945
|
+
const scan = new Uint8Array(Math.ceil(bits / 8));
|
|
6946
|
+
const padding = scan.length * 8 - bits;
|
|
6947
|
+
if (padding > 0) scan[scan.length - 1] = (1 << padding) - 1;
|
|
6948
|
+
return concat(
|
|
6949
|
+
[255, 216],
|
|
6950
|
+
// SOI
|
|
6951
|
+
[255, 224, ...u16be(16), ...ascii("JFIF"), 0, 1, 1, 0, 0, 1, 0, 1, 0, 0],
|
|
6952
|
+
// APP0
|
|
6953
|
+
[255, 219, ...u16be(67), 0, ...new Array(64).fill(1)],
|
|
6954
|
+
// DQT: table 0, all ones
|
|
6955
|
+
[255, 192, ...u16be(11), 8, ...u16be(height), ...u16be(width), 1, 1, 17, 0],
|
|
6956
|
+
// SOF0: one component
|
|
6957
|
+
oneCodeTable(0),
|
|
6958
|
+
// DHT: DC table 0
|
|
6959
|
+
oneCodeTable(16),
|
|
6960
|
+
// DHT: AC table 0
|
|
6961
|
+
[255, 218, ...u16be(8), 1, 1, 0, 0, 63, 0],
|
|
6962
|
+
// SOS
|
|
6963
|
+
scan,
|
|
6964
|
+
[255, 217]
|
|
6965
|
+
// EOI
|
|
6966
|
+
);
|
|
6967
|
+
}
|
|
6968
|
+
function lzwZeros(pixels, minCodeSize) {
|
|
6969
|
+
const clear = 1 << minCodeSize;
|
|
6970
|
+
const end = clear + 1;
|
|
6971
|
+
const out = new BitWriter();
|
|
6972
|
+
const table = /* @__PURE__ */ new Map();
|
|
6973
|
+
let codeSize = minCodeSize + 1;
|
|
6974
|
+
let next = end + 1;
|
|
6975
|
+
let current = 0;
|
|
6976
|
+
out.write(clear, codeSize);
|
|
6977
|
+
for (let i = 1; i < pixels; i++) {
|
|
6978
|
+
const key = current << 8;
|
|
6979
|
+
const known = table.get(key);
|
|
6980
|
+
if (known !== void 0) {
|
|
6981
|
+
current = known;
|
|
6982
|
+
continue;
|
|
6983
|
+
}
|
|
6984
|
+
out.write(current, codeSize);
|
|
6985
|
+
if (next === 4096) {
|
|
6986
|
+
out.write(clear, codeSize);
|
|
6987
|
+
table.clear();
|
|
6988
|
+
codeSize = minCodeSize + 1;
|
|
6989
|
+
next = end + 1;
|
|
6990
|
+
} else {
|
|
6991
|
+
if (next >= 1 << codeSize) codeSize++;
|
|
6992
|
+
table.set(key, next++);
|
|
6993
|
+
}
|
|
6994
|
+
current = 0;
|
|
4601
6995
|
}
|
|
6996
|
+
out.write(current, codeSize);
|
|
6997
|
+
out.write(end, codeSize);
|
|
6998
|
+
return out.finish();
|
|
6999
|
+
}
|
|
7000
|
+
function gif(width, height) {
|
|
7001
|
+
const data = lzwZeros(width * height, 2);
|
|
7002
|
+
const blocks = [];
|
|
7003
|
+
for (let offset = 0; offset < data.length; offset += 255) {
|
|
7004
|
+
const block = data.subarray(offset, offset + 255);
|
|
7005
|
+
blocks.push(block.length, ...block);
|
|
7006
|
+
}
|
|
7007
|
+
return concat(
|
|
7008
|
+
ascii("GIF89a"),
|
|
7009
|
+
[...u16le(width), ...u16le(height), 128, 0, 0],
|
|
7010
|
+
// a global palette of two colours
|
|
7011
|
+
[255, 255, 255, 0, 0, 0],
|
|
7012
|
+
[44, 0, 0, 0, 0, ...u16le(width), ...u16le(height), 0],
|
|
7013
|
+
// the image descriptor
|
|
7014
|
+
[2],
|
|
7015
|
+
blocks,
|
|
7016
|
+
[0, 59]
|
|
7017
|
+
// the end of the data, the trailer
|
|
7018
|
+
);
|
|
7019
|
+
}
|
|
7020
|
+
function webp(width, height) {
|
|
7021
|
+
const bits = new BitWriter();
|
|
7022
|
+
bits.write(47, 8);
|
|
7023
|
+
bits.write(width - 1, 14);
|
|
7024
|
+
bits.write(height - 1, 14);
|
|
7025
|
+
bits.write(0, 4);
|
|
7026
|
+
bits.write(0, 3);
|
|
7027
|
+
for (const symbol of [255, 255, 255, 255, 0]) {
|
|
7028
|
+
bits.write(1, 1);
|
|
7029
|
+
bits.write(0, 1);
|
|
7030
|
+
bits.write(1, 1);
|
|
7031
|
+
bits.write(symbol, 8);
|
|
7032
|
+
}
|
|
7033
|
+
const data = bits.finish();
|
|
7034
|
+
const padded = data.length % 2 === 0 ? data : concat(data, [0]);
|
|
7035
|
+
return concat(ascii("RIFF"), u32le(12 + padded.length), ascii("WEBP"), ascii("VP8L"), u32le(data.length), padded);
|
|
7036
|
+
}
|
|
7037
|
+
function bmp(width, height) {
|
|
7038
|
+
const row = Math.ceil(width * 3 / 4) * 4;
|
|
7039
|
+
const out = new Uint8Array(54 + row * height);
|
|
7040
|
+
const view = new DataView(out.buffer);
|
|
7041
|
+
out.set(ascii("BM"));
|
|
7042
|
+
view.setUint32(2, out.length, true);
|
|
7043
|
+
view.setUint32(10, 54, true);
|
|
7044
|
+
view.setUint32(14, 40, true);
|
|
7045
|
+
view.setInt32(18, width, true);
|
|
7046
|
+
view.setInt32(22, height, true);
|
|
7047
|
+
view.setUint16(26, 1, true);
|
|
7048
|
+
view.setUint16(28, 24, true);
|
|
7049
|
+
view.setUint32(34, row * height, true);
|
|
7050
|
+
for (let y = 0; y < height; y++) out.fill(255, 54 + y * row, 54 + y * row + width * 3);
|
|
7051
|
+
return out;
|
|
7052
|
+
}
|
|
7053
|
+
var IMAGE_FORMATS = {
|
|
7054
|
+
png: { type: "image/png", encode: png },
|
|
7055
|
+
jpg: { type: "image/jpeg", encode: jpeg },
|
|
7056
|
+
jpeg: { type: "image/jpeg", encode: jpeg },
|
|
7057
|
+
gif: { type: "image/gif", encode: gif },
|
|
7058
|
+
webp: { type: "image/webp", encode: webp },
|
|
7059
|
+
bmp: { type: "image/bmp", encode: bmp }
|
|
7060
|
+
};
|
|
7061
|
+
var extensionOf = (name) => name.includes(".") ? name.slice(name.lastIndexOf(".") + 1).toLowerCase() : "";
|
|
7062
|
+
var MORE_MIME_TYPES = {
|
|
7063
|
+
bmp: "image/bmp",
|
|
7064
|
+
avif: "image/avif",
|
|
7065
|
+
xml: "text/xml",
|
|
7066
|
+
htm: "text/html",
|
|
7067
|
+
md: "text/markdown",
|
|
7068
|
+
tsv: "text/tab-separated-values",
|
|
7069
|
+
text: "text/plain"
|
|
7070
|
+
};
|
|
7071
|
+
var LEADING_BYTES = {
|
|
7072
|
+
"image/jpeg": { bytes: [255, 216, 255, 224, ...u16be(16), ...ascii("JFIF"), 0, 1, 1, 0, 0, 1, 0, 1, 0, 0] },
|
|
7073
|
+
"image/png": { bytes: [137, 80, 78, 71, 13, 10, 26, 10] },
|
|
7074
|
+
"image/gif": { bytes: "GIF89a" },
|
|
7075
|
+
"image/webp": { bytes: "RIFF\0\0\0\0WEBP" },
|
|
7076
|
+
"image/avif": { bytes: [0, 0, 0, 20, ...ascii("ftypavif"), 0, 0, 0, 0, ...ascii("avifmif1")] },
|
|
7077
|
+
"image/bmp": { bytes: [...ascii("BM"), 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 40, 0, 0, 0] },
|
|
7078
|
+
"application/pdf": { bytes: "%PDF-1.4\n" },
|
|
7079
|
+
"image/svg+xml": { bytes: '<svg xmlns="http://www.w3.org/2000/svg"/>\n', text: true },
|
|
7080
|
+
"text/html": { bytes: "<!doctype html>\n", text: true },
|
|
7081
|
+
"text/xml": { bytes: '<?xml version="1.0"?>\n', text: true }
|
|
7082
|
+
};
|
|
7083
|
+
function contentFor(type, size) {
|
|
7084
|
+
const text = type.startsWith("text/") || type === "application/json";
|
|
7085
|
+
const leading = LEADING_BYTES[type] ?? { bytes: text ? "\n" : [], text };
|
|
7086
|
+
const head = typeof leading.bytes === "string" ? new TextEncoder().encode(leading.bytes) : Uint8Array.from(leading.bytes);
|
|
7087
|
+
const out = new Uint8Array(Math.max(size, head.length, 1)).fill(leading.text ? 32 : 0);
|
|
7088
|
+
out.set(head);
|
|
4602
7089
|
return out;
|
|
4603
7090
|
}
|
|
4604
7091
|
var FakeFile = {
|
|
4605
|
-
/**
|
|
7092
|
+
/**
|
|
7093
|
+
* A file of `kilobytes` KiB typed by `mimeType` or the name's extension. Validation reads a file's bytes, so the
|
|
7094
|
+
* file starts like its type (a PDF's `%PDF-`, a PNG's signature, text for text types) and passes `mimes` for its
|
|
7095
|
+
* name, as Laravel's fakes do. It is at least its type's leading bytes and never empty, whatever the size asked for.
|
|
7096
|
+
* An image made this way has no readable size: use image() for `dimensions`.
|
|
7097
|
+
*/
|
|
4606
7098
|
create(name, kilobytes = 0, mimeType) {
|
|
4607
|
-
|
|
7099
|
+
const type = mimeType ?? guessMimeType(name) ?? MORE_MIME_TYPES[extensionOf(name)] ?? "application/octet-stream";
|
|
7100
|
+
return new File([contentFor(type, Math.round(kilobytes * 1024))], name, { type });
|
|
4608
7101
|
},
|
|
4609
|
-
/**
|
|
7102
|
+
/**
|
|
7103
|
+
* A real white image of the given size in the format the name's extension asks for: png, jpg/jpeg, gif, webp or
|
|
7104
|
+
* bmp (any other name gets a JPEG, as Laravel's fakes do), so image, mimes and dimensions pass on it.
|
|
7105
|
+
*/
|
|
4610
7106
|
image(name, width = 10, height = 10) {
|
|
4611
|
-
|
|
7107
|
+
const format2 = IMAGE_FORMATS[extensionOf(name)] ?? IMAGE_FORMATS.jpeg;
|
|
7108
|
+
return new File([format2.encode(width, height)], name, { type: guessMimeType(name) ?? format2.type });
|
|
4612
7109
|
}
|
|
4613
7110
|
};
|
|
4614
7111
|
|
|
@@ -4626,6 +7123,70 @@ export {
|
|
|
4626
7123
|
viteReactRefresh,
|
|
4627
7124
|
Application2 as Application,
|
|
4628
7125
|
ClosureKernel,
|
|
7126
|
+
HttpException,
|
|
7127
|
+
HttpResponseException,
|
|
7128
|
+
HttpServerError,
|
|
7129
|
+
exceedsJsonDepth,
|
|
7130
|
+
requestScope,
|
|
7131
|
+
currentRequest,
|
|
7132
|
+
optionalSession,
|
|
7133
|
+
optionalCookieJar,
|
|
7134
|
+
HttpRedirectResponse,
|
|
7135
|
+
HttpRedirector,
|
|
7136
|
+
setClientAddress,
|
|
7137
|
+
clientAddress,
|
|
7138
|
+
extensionFromMime,
|
|
7139
|
+
FormUploadedFile,
|
|
7140
|
+
parseBracketKey,
|
|
7141
|
+
normalizeLists,
|
|
7142
|
+
fromEntries,
|
|
7143
|
+
sanitize,
|
|
7144
|
+
mergeRecursive,
|
|
7145
|
+
isBlank,
|
|
7146
|
+
transformLeaves,
|
|
7147
|
+
ipMatches,
|
|
7148
|
+
IncomingRequest,
|
|
7149
|
+
request,
|
|
7150
|
+
response,
|
|
7151
|
+
redirect,
|
|
7152
|
+
back,
|
|
7153
|
+
url,
|
|
7154
|
+
route,
|
|
7155
|
+
abort,
|
|
7156
|
+
abortIf,
|
|
7157
|
+
abortUnless,
|
|
7158
|
+
contentDisposition,
|
|
7159
|
+
HttpResponseFactory,
|
|
7160
|
+
ConvertEmptyStringsToNull,
|
|
7161
|
+
TrimStrings,
|
|
7162
|
+
Kernel,
|
|
7163
|
+
MiddlewareConfiguration,
|
|
7164
|
+
applyMiddlewareCallbacks,
|
|
7165
|
+
statusText,
|
|
7166
|
+
escapeHtml,
|
|
7167
|
+
minimalPage,
|
|
7168
|
+
stackFrames,
|
|
7169
|
+
debugPage,
|
|
7170
|
+
statusFor,
|
|
7171
|
+
summarizeValidation,
|
|
7172
|
+
ErrorHandler,
|
|
7173
|
+
ExceptionsConfiguration,
|
|
7174
|
+
applyExceptionCallbacks,
|
|
7175
|
+
servePublicFile,
|
|
7176
|
+
bunRuntime,
|
|
7177
|
+
serveBun,
|
|
7178
|
+
toWebRequest,
|
|
7179
|
+
writeResponse,
|
|
7180
|
+
serveNode,
|
|
7181
|
+
detectRuntime,
|
|
7182
|
+
serve,
|
|
7183
|
+
routeListRows,
|
|
7184
|
+
filterRouteList,
|
|
7185
|
+
formatRouteList,
|
|
7186
|
+
API_ROUTES_STUB,
|
|
7187
|
+
addApiRouting,
|
|
7188
|
+
registerHttpCommands,
|
|
7189
|
+
HttpServiceProvider,
|
|
4629
7190
|
On,
|
|
4630
7191
|
Locked,
|
|
4631
7192
|
Computed,
|
|
@@ -4638,13 +7199,18 @@ export {
|
|
|
4638
7199
|
PropertyNotUpdatableError,
|
|
4639
7200
|
LiveComponentNotFoundError,
|
|
4640
7201
|
InvalidLiveRequestError,
|
|
7202
|
+
TestRequestScope,
|
|
4641
7203
|
Testable,
|
|
4642
7204
|
live,
|
|
4643
7205
|
Live,
|
|
4644
7206
|
componentName,
|
|
7207
|
+
PERSISTENT_MIDDLEWARE,
|
|
7208
|
+
persistentMiddleware,
|
|
7209
|
+
WithPagination,
|
|
4645
7210
|
LiveComponentManager,
|
|
4646
7211
|
UPDATE_URI,
|
|
4647
7212
|
LiveServiceProvider,
|
|
7213
|
+
Pagination,
|
|
4648
7214
|
PendingCommand,
|
|
4649
7215
|
RefreshDatabaseState,
|
|
4650
7216
|
runCommand,
|
|
@@ -4690,4 +7256,4 @@ export {
|
|
|
4690
7256
|
StorageFake,
|
|
4691
7257
|
FakeFile
|
|
4692
7258
|
};
|
|
4693
|
-
//# sourceMappingURL=chunk-
|
|
7259
|
+
//# sourceMappingURL=chunk-WNJP46B7.js.map
|