bosia 0.8.3 → 0.8.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/README.md +14 -0
- package/package.json +1 -1
- package/src/ambient.d.ts +0 -1
- package/src/core/build.ts +12 -3
- package/src/core/cache.ts +106 -34
- package/src/core/cookies.ts +18 -0
- package/src/core/dedup.ts +7 -7
- package/src/core/dev.ts +52 -4
- package/src/core/html.ts +13 -5
- package/src/core/plugin.ts +2 -2
- package/src/core/prerender.ts +4 -3
- package/src/core/renderer.ts +247 -208
- package/src/core/routeFile.ts +0 -2
- package/src/core/scanner.ts +1 -7
- package/src/core/server.ts +96 -44
- package/src/core/staticManifest.ts +35 -1
- package/src/core/twHash.ts +18 -0
- package/src/core/types.ts +0 -6
package/src/core/renderer.ts
CHANGED
|
@@ -7,14 +7,17 @@ import type { Cookies, LoaderDeps } from "./hooks.ts";
|
|
|
7
7
|
import { CSP_ENABLED } from "./csp.ts";
|
|
8
8
|
import {
|
|
9
9
|
CACHE_ENABLED,
|
|
10
|
+
CACHE_MAX_BODY_BYTES,
|
|
10
11
|
buildCompressedVariants,
|
|
11
12
|
cacheGet,
|
|
12
13
|
cacheSet,
|
|
14
|
+
coalesceMiss,
|
|
13
15
|
collectTags,
|
|
14
16
|
computeCacheKey,
|
|
15
17
|
concatChunks,
|
|
16
18
|
serveCached,
|
|
17
19
|
} from "./cache.ts";
|
|
20
|
+
import type { CookieJar } from "./cookies.ts";
|
|
18
21
|
import { HttpError, Redirect } from "./errors.ts";
|
|
19
22
|
import { pickErrorPage, type ErrorOrigin } from "./errorMatch.ts";
|
|
20
23
|
import App from "./client/App.svelte";
|
|
@@ -532,27 +535,96 @@ export async function renderSSRStream(
|
|
|
532
535
|
const cacheable =
|
|
533
536
|
CACHE_ENABLED && !CSP_ENABLED && pageMod.cache !== false && req.method === "GET";
|
|
534
537
|
let cacheKey: string | null = null;
|
|
538
|
+
let releaseMiss: (() => void) | null = null;
|
|
535
539
|
if (cacheable) {
|
|
536
|
-
cacheKey = computeCacheKey(url, req, cookies);
|
|
540
|
+
cacheKey = computeCacheKey(url, req, cookies as CookieJar);
|
|
537
541
|
if (!cacheBypass) {
|
|
538
542
|
const hit = cacheGet(cacheKey);
|
|
539
543
|
if (hit) return serveCached(hit, req);
|
|
544
|
+
// Miss coalescing: the first miss leads the build; concurrent misses
|
|
545
|
+
// wait, then re-check the cache. A waiter that still misses (leader
|
|
546
|
+
// skipped the write) builds independently — no re-leadering.
|
|
547
|
+
const gate = coalesceMiss(cacheKey);
|
|
548
|
+
if (gate.wait) {
|
|
549
|
+
await gate.wait;
|
|
550
|
+
const rehit = cacheGet(cacheKey);
|
|
551
|
+
if (rehit) return serveCached(rehit, req);
|
|
552
|
+
} else {
|
|
553
|
+
releaseMiss = gate.release;
|
|
554
|
+
}
|
|
540
555
|
}
|
|
541
556
|
}
|
|
542
557
|
|
|
543
|
-
//
|
|
544
|
-
//
|
|
545
|
-
|
|
558
|
+
// INVARIANT: releaseMiss must fire exactly once — a missed release() hangs
|
|
559
|
+
// coalesced waiters for the process lifetime. Every early return and throw
|
|
560
|
+
// below must stay inside this try; the cache-write path hands the release
|
|
561
|
+
// off to its cacheSet microtask by nulling releaseMiss first.
|
|
546
562
|
try {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
563
|
+
// ── Pre-stream phase: resolve metadata before committing to a 200 ──
|
|
564
|
+
// Errors here return a proper error response with correct status code.
|
|
565
|
+
let metadata: Metadata | null = null;
|
|
566
|
+
try {
|
|
567
|
+
metadata = await loadMetadata(route, params, url, locals, cookies, req);
|
|
568
|
+
} catch (err) {
|
|
569
|
+
if (err instanceof Redirect) {
|
|
570
|
+
return Response.redirect(err.location, err.status);
|
|
571
|
+
}
|
|
572
|
+
if (err instanceof HttpError) {
|
|
573
|
+
return renderErrorPage(
|
|
574
|
+
err.status,
|
|
575
|
+
err.message,
|
|
576
|
+
url,
|
|
577
|
+
req,
|
|
578
|
+
route,
|
|
579
|
+
undefined,
|
|
580
|
+
undefined,
|
|
581
|
+
undefined,
|
|
582
|
+
nonce,
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
if (isDev) console.error("Metadata load error:", err);
|
|
586
|
+
else console.error("Metadata load error:", (err as Error).message ?? err);
|
|
587
|
+
if (isDev) reportDevErrorFromCatch(err);
|
|
588
|
+
// Continue with null metadata — don't break the page for a metadata failure
|
|
551
589
|
}
|
|
552
|
-
|
|
590
|
+
|
|
591
|
+
// ── Pre-stream phase: run load() + module imports in parallel before committing to a 200 ──
|
|
592
|
+
// This ensures HttpError/Redirect from load() can return a proper response before any bytes are sent.
|
|
593
|
+
const metadataData = metadata?.data ?? null;
|
|
594
|
+
let data: Awaited<ReturnType<typeof loadRouteData>>;
|
|
595
|
+
let layoutMods: any[];
|
|
596
|
+
|
|
597
|
+
try {
|
|
598
|
+
[data, layoutMods] = await Promise.all([
|
|
599
|
+
loadRouteData(url, locals, req, cookies, metadataData, match),
|
|
600
|
+
Promise.all(route.layoutModules.map((l: () => Promise<any>) => l())),
|
|
601
|
+
]);
|
|
602
|
+
} catch (err) {
|
|
603
|
+
if (err instanceof Redirect) return Response.redirect(err.location, err.status);
|
|
604
|
+
if (err instanceof HttpError) {
|
|
605
|
+
const e = err as HttpError & {
|
|
606
|
+
errorDepth?: number;
|
|
607
|
+
errorOrigin?: ErrorOrigin;
|
|
608
|
+
partialLayoutData?: Record<string, any>[];
|
|
609
|
+
};
|
|
610
|
+
return renderErrorPage(
|
|
611
|
+
err.status,
|
|
612
|
+
err.message,
|
|
613
|
+
url,
|
|
614
|
+
req,
|
|
615
|
+
route,
|
|
616
|
+
e.errorDepth,
|
|
617
|
+
e.errorOrigin,
|
|
618
|
+
e.partialLayoutData,
|
|
619
|
+
nonce,
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
if (isDev) console.error("SSR load error:", err);
|
|
623
|
+
else console.error("SSR load error:", (err as Error).message ?? err);
|
|
624
|
+
if (isDev) reportDevErrorFromCatch(err);
|
|
553
625
|
return renderErrorPage(
|
|
554
|
-
|
|
555
|
-
|
|
626
|
+
500,
|
|
627
|
+
"Internal Server Error",
|
|
556
628
|
url,
|
|
557
629
|
req,
|
|
558
630
|
route,
|
|
@@ -562,222 +634,189 @@ export async function renderSSRStream(
|
|
|
562
634
|
nonce,
|
|
563
635
|
);
|
|
564
636
|
}
|
|
565
|
-
if (isDev) console.error("Metadata load error:", err);
|
|
566
|
-
else console.error("Metadata load error:", (err as Error).message ?? err);
|
|
567
|
-
if (isDev) reportDevErrorFromCatch(err);
|
|
568
|
-
// Continue with null metadata — don't break the page for a metadata failure
|
|
569
|
-
}
|
|
570
637
|
|
|
571
|
-
|
|
572
|
-
// This ensures HttpError/Redirect from load() can return a proper response before any bytes are sent.
|
|
573
|
-
const metadataData = metadata?.data ?? null;
|
|
574
|
-
let data: Awaited<ReturnType<typeof loadRouteData>>;
|
|
575
|
-
let layoutMods: any[];
|
|
576
|
-
|
|
577
|
-
try {
|
|
578
|
-
[data, layoutMods] = await Promise.all([
|
|
579
|
-
loadRouteData(url, locals, req, cookies, metadataData, match),
|
|
580
|
-
Promise.all(route.layoutModules.map((l: () => Promise<any>) => l())),
|
|
581
|
-
]);
|
|
582
|
-
} catch (err) {
|
|
583
|
-
if (err instanceof Redirect) return Response.redirect(err.location, err.status);
|
|
584
|
-
if (err instanceof HttpError) {
|
|
585
|
-
const e = err as HttpError & {
|
|
586
|
-
errorDepth?: number;
|
|
587
|
-
errorOrigin?: ErrorOrigin;
|
|
588
|
-
partialLayoutData?: Record<string, any>[];
|
|
589
|
-
};
|
|
638
|
+
if (!data)
|
|
590
639
|
return renderErrorPage(
|
|
591
|
-
|
|
592
|
-
|
|
640
|
+
404,
|
|
641
|
+
"Not Found",
|
|
593
642
|
url,
|
|
594
643
|
req,
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
644
|
+
undefined,
|
|
645
|
+
undefined,
|
|
646
|
+
undefined,
|
|
647
|
+
undefined,
|
|
599
648
|
nonce,
|
|
600
649
|
);
|
|
601
|
-
}
|
|
602
|
-
if (isDev) console.error("SSR load error:", err);
|
|
603
|
-
else console.error("SSR load error:", (err as Error).message ?? err);
|
|
604
|
-
if (isDev) reportDevErrorFromCatch(err);
|
|
605
|
-
return renderErrorPage(
|
|
606
|
-
500,
|
|
607
|
-
"Internal Server Error",
|
|
608
|
-
url,
|
|
609
|
-
req,
|
|
610
|
-
route,
|
|
611
|
-
undefined,
|
|
612
|
-
undefined,
|
|
613
|
-
undefined,
|
|
614
|
-
nonce,
|
|
615
|
-
);
|
|
616
|
-
}
|
|
617
650
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
"Not Found",
|
|
651
|
+
const enc = new TextEncoder();
|
|
652
|
+
const renderCtx: RenderContext = {
|
|
653
|
+
request: req,
|
|
622
654
|
url,
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
);
|
|
630
|
-
|
|
631
|
-
const enc = new TextEncoder();
|
|
632
|
-
const renderCtx: RenderContext = {
|
|
633
|
-
request: req,
|
|
634
|
-
url,
|
|
635
|
-
route: { pattern: route.pattern },
|
|
636
|
-
metadata,
|
|
637
|
-
};
|
|
638
|
-
const [headExtras, bodyEndExtras] = await Promise.all([
|
|
639
|
-
pluginRenderFragments("head", renderCtx),
|
|
640
|
-
pluginRenderFragments("bodyEnd", renderCtx),
|
|
641
|
-
]);
|
|
655
|
+
route: { pattern: route.pattern },
|
|
656
|
+
metadata,
|
|
657
|
+
};
|
|
658
|
+
const [headExtras, bodyEndExtras] = await Promise.all([
|
|
659
|
+
pluginRenderFragments("head", renderCtx),
|
|
660
|
+
pluginRenderFragments("bodyEnd", renderCtx),
|
|
661
|
+
]);
|
|
642
662
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
663
|
+
// SSR always runs every loader, so coerce types from the optional sparse shape.
|
|
664
|
+
const layoutDataFull = (data.layoutData as Record<string, any>[]).map((d) => d ?? {});
|
|
665
|
+
const pageDataFull = data.pageData ?? {};
|
|
646
666
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
667
|
+
// ssr=false → no render() needed; ship shell + hydration as a single response.
|
|
668
|
+
// ssr=false && csr=false is meaningless (nothing renders) — force csr=true.
|
|
669
|
+
if (!data.ssr) {
|
|
670
|
+
if (!data.csr && isDev) {
|
|
671
|
+
console.warn(
|
|
672
|
+
`⚠️ ${url.pathname}: ssr=false && csr=false renders nothing — forcing csr=true`,
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
const html =
|
|
676
|
+
buildHtmlShellOpen(metadata?.lang, nonce, appHtmlSegments) +
|
|
677
|
+
buildMetadataChunk(metadata, headExtras, appHtmlSegments) +
|
|
678
|
+
buildHtmlTail(
|
|
679
|
+
"",
|
|
680
|
+
"",
|
|
681
|
+
pageDataFull,
|
|
682
|
+
layoutDataFull,
|
|
683
|
+
true,
|
|
684
|
+
null,
|
|
685
|
+
false,
|
|
686
|
+
bodyEndExtras,
|
|
687
|
+
nonce,
|
|
688
|
+
data.pageDeps,
|
|
689
|
+
data.layoutDeps,
|
|
690
|
+
appHtmlSegments,
|
|
691
|
+
);
|
|
692
|
+
return new Response(html, {
|
|
693
|
+
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
694
|
+
});
|
|
654
695
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
696
|
+
|
|
697
|
+
// Render-first: run render() before committing to a 200. Failure → proper error page
|
|
698
|
+
// with correct status code, instead of a bare <p> mixed into an already-flushed shell.
|
|
699
|
+
let body: string, head: string;
|
|
700
|
+
try {
|
|
701
|
+
({ body, head } = render(App, {
|
|
702
|
+
props: {
|
|
703
|
+
ssrMode: true,
|
|
704
|
+
ssrPageComponent: pageMod.default,
|
|
705
|
+
ssrLayoutComponents: layoutMods.map((m: any) => m.default),
|
|
706
|
+
ssrPageData: pageDataFull,
|
|
707
|
+
ssrLayoutData: layoutDataFull,
|
|
708
|
+
},
|
|
709
|
+
}));
|
|
710
|
+
} catch (err) {
|
|
711
|
+
if (isDev) console.error("SSR render error:", err);
|
|
712
|
+
else console.error("SSR render error:", (err as Error).message ?? err);
|
|
713
|
+
if (isDev) reportDevErrorFromCatch(err);
|
|
714
|
+
// Render-phase errors fall through to deepest boundary like a page error.
|
|
715
|
+
return renderErrorPage(
|
|
716
|
+
500,
|
|
717
|
+
"Internal Server Error",
|
|
718
|
+
url,
|
|
719
|
+
req,
|
|
720
|
+
route,
|
|
721
|
+
route.layoutModules.length,
|
|
722
|
+
"page",
|
|
662
723
|
layoutDataFull,
|
|
663
|
-
true,
|
|
664
|
-
null,
|
|
665
|
-
false,
|
|
666
|
-
bodyEndExtras,
|
|
667
724
|
nonce,
|
|
668
|
-
data.pageDeps,
|
|
669
|
-
data.layoutDeps,
|
|
670
|
-
appHtmlSegments,
|
|
671
725
|
);
|
|
672
|
-
|
|
673
|
-
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
// Render-first: run render() before committing to a 200. Failure → proper error page
|
|
678
|
-
// with correct status code, instead of a bare <p> mixed into an already-flushed shell.
|
|
679
|
-
let body: string, head: string;
|
|
680
|
-
try {
|
|
681
|
-
({ body, head } = render(App, {
|
|
682
|
-
props: {
|
|
683
|
-
ssrMode: true,
|
|
684
|
-
ssrPageComponent: pageMod.default,
|
|
685
|
-
ssrLayoutComponents: layoutMods.map((m: any) => m.default),
|
|
686
|
-
ssrPageData: pageDataFull,
|
|
687
|
-
ssrLayoutData: layoutDataFull,
|
|
688
|
-
},
|
|
689
|
-
}));
|
|
690
|
-
} catch (err) {
|
|
691
|
-
if (isDev) console.error("SSR render error:", err);
|
|
692
|
-
else console.error("SSR render error:", (err as Error).message ?? err);
|
|
693
|
-
if (isDev) reportDevErrorFromCatch(err);
|
|
694
|
-
// Render-phase errors fall through to deepest boundary like a page error.
|
|
695
|
-
return renderErrorPage(
|
|
696
|
-
500,
|
|
697
|
-
"Internal Server Error",
|
|
698
|
-
url,
|
|
699
|
-
req,
|
|
700
|
-
route,
|
|
701
|
-
route.layoutModules.length,
|
|
702
|
-
"page",
|
|
703
|
-
layoutDataFull,
|
|
704
|
-
nonce,
|
|
705
|
-
);
|
|
706
|
-
}
|
|
726
|
+
}
|
|
707
727
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
728
|
+
// Pre-compute all chunks; pull-based stream gives Bun native backpressure.
|
|
729
|
+
const chunks: Uint8Array[] = [
|
|
730
|
+
enc.encode(buildHtmlShellOpen(metadata?.lang, nonce, appHtmlSegments)),
|
|
731
|
+
enc.encode(buildMetadataChunk(metadata, headExtras, appHtmlSegments)),
|
|
732
|
+
enc.encode(
|
|
733
|
+
buildHtmlTail(
|
|
734
|
+
body,
|
|
735
|
+
head,
|
|
736
|
+
pageDataFull,
|
|
737
|
+
layoutDataFull,
|
|
738
|
+
data.csr,
|
|
739
|
+
null,
|
|
740
|
+
true,
|
|
741
|
+
bodyEndExtras,
|
|
742
|
+
nonce,
|
|
743
|
+
data.pageDeps,
|
|
744
|
+
data.layoutDeps,
|
|
745
|
+
appHtmlSegments,
|
|
746
|
+
),
|
|
726
747
|
),
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
return;
|
|
765
|
-
}
|
|
766
|
-
controller.enqueue(chunks[i++]);
|
|
767
|
-
if (i >= chunks.length) {
|
|
768
|
-
controller.close();
|
|
769
|
-
req.signal.removeEventListener("abort", onAbort);
|
|
748
|
+
];
|
|
749
|
+
|
|
750
|
+
// ── Response cache: write after chunks built, before stream creation ──
|
|
751
|
+
// Skip if the handler set cookies — cached response can't reproduce
|
|
752
|
+
// per-request Set-Cookie headers. Compression runs in a microtask so
|
|
753
|
+
// the response goes out first.
|
|
754
|
+
if (cacheable && cacheKey && (cookies as any).outgoing?.length === 0) {
|
|
755
|
+
const fullBody = concatChunks(chunks);
|
|
756
|
+
// Oversized bodies skip early so they never pay compression; cacheSet
|
|
757
|
+
// re-checks as the authoritative guard.
|
|
758
|
+
if (CACHE_MAX_BODY_BYTES === 0 || fullBody.length <= CACHE_MAX_BODY_BYTES) {
|
|
759
|
+
const tags = collectTags(data.layoutDeps ?? null, data.pageDeps ?? null);
|
|
760
|
+
const keyForWrite = cacheKey;
|
|
761
|
+
// Hand the gate release to the write microtask: waiters resume only
|
|
762
|
+
// after cacheSet ran, so their re-check hits.
|
|
763
|
+
const rel = releaseMiss;
|
|
764
|
+
releaseMiss = null;
|
|
765
|
+
queueMicrotask(() => {
|
|
766
|
+
try {
|
|
767
|
+
const { gzip, brotli } = buildCompressedVariants(fullBody);
|
|
768
|
+
cacheSet(
|
|
769
|
+
keyForWrite,
|
|
770
|
+
{
|
|
771
|
+
raw: fullBody,
|
|
772
|
+
gzip,
|
|
773
|
+
brotli,
|
|
774
|
+
contentType: "text/html; charset=utf-8",
|
|
775
|
+
status: 200,
|
|
776
|
+
extraHeaders: {},
|
|
777
|
+
tags,
|
|
778
|
+
},
|
|
779
|
+
cookies,
|
|
780
|
+
);
|
|
781
|
+
} finally {
|
|
782
|
+
rel?.();
|
|
783
|
+
}
|
|
784
|
+
});
|
|
770
785
|
}
|
|
771
|
-
}
|
|
772
|
-
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
let i = 0;
|
|
789
|
+
let cancelled = false;
|
|
790
|
+
const onAbort = () => {
|
|
773
791
|
cancelled = true;
|
|
774
|
-
|
|
775
|
-
}
|
|
776
|
-
|
|
792
|
+
};
|
|
793
|
+
req.signal.addEventListener("abort", onAbort, { once: true });
|
|
794
|
+
|
|
795
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
796
|
+
pull(controller) {
|
|
797
|
+
if (cancelled || i >= chunks.length) {
|
|
798
|
+
controller.close();
|
|
799
|
+
req.signal.removeEventListener("abort", onAbort);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
controller.enqueue(chunks[i++]);
|
|
803
|
+
if (i >= chunks.length) {
|
|
804
|
+
controller.close();
|
|
805
|
+
req.signal.removeEventListener("abort", onAbort);
|
|
806
|
+
}
|
|
807
|
+
},
|
|
808
|
+
cancel() {
|
|
809
|
+
cancelled = true;
|
|
810
|
+
req.signal.removeEventListener("abort", onAbort);
|
|
811
|
+
},
|
|
812
|
+
});
|
|
777
813
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
814
|
+
return new Response(stream, {
|
|
815
|
+
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
816
|
+
});
|
|
817
|
+
} finally {
|
|
818
|
+
releaseMiss?.();
|
|
819
|
+
}
|
|
781
820
|
}
|
|
782
821
|
|
|
783
822
|
// ─── Form Action Page Renderer ───────────────────────────
|
package/src/core/routeFile.ts
CHANGED
|
@@ -94,7 +94,6 @@ export function generateRoutesFile(manifest: RouteManifest): void {
|
|
|
94
94
|
lines.push(" layoutServers: { loader: () => Promise<any>; depth: number }[];");
|
|
95
95
|
lines.push(" errorPages: { loader: () => Promise<any>; depth: number }[];");
|
|
96
96
|
lines.push(' trailingSlash: "never" | "always" | "ignore";');
|
|
97
|
-
lines.push(' scope: "public" | "private";');
|
|
98
97
|
lines.push("}> = [");
|
|
99
98
|
for (const r of pages) {
|
|
100
99
|
const layoutImports = r.layouts
|
|
@@ -122,7 +121,6 @@ export function generateRoutesFile(manifest: RouteManifest): void {
|
|
|
122
121
|
lines.push(` layoutServers: [${layoutServerImports}],`);
|
|
123
122
|
lines.push(` errorPages: [${errorPageImports}],`);
|
|
124
123
|
lines.push(` trailingSlash: ${JSON.stringify(r.trailingSlash)},`);
|
|
125
|
-
lines.push(` scope: ${JSON.stringify(r.scope)},`);
|
|
126
124
|
lines.push(" },");
|
|
127
125
|
}
|
|
128
126
|
lines.push("];\n");
|
package/src/core/scanner.ts
CHANGED
|
@@ -46,7 +46,6 @@ export function scanRoutes(): RouteManifest {
|
|
|
46
46
|
layoutServerChain: { path: string; depth: number }[],
|
|
47
47
|
errorPageChain: { path: string; depth: number }[],
|
|
48
48
|
inheritedTrailingSlash: TrailingSlash,
|
|
49
|
-
inheritedScope: "public" | "private",
|
|
50
49
|
) {
|
|
51
50
|
const fullDir = join(ROUTES_DIR, dir);
|
|
52
51
|
if (!existsSync(fullDir)) return;
|
|
@@ -110,7 +109,6 @@ export function scanRoutes(): RouteManifest {
|
|
|
110
109
|
layoutServers: [...currentLayoutServers],
|
|
111
110
|
errorPages: [...currentErrorPages],
|
|
112
111
|
trailingSlash: effectiveTs,
|
|
113
|
-
scope: inheritedScope,
|
|
114
112
|
});
|
|
115
113
|
}
|
|
116
114
|
|
|
@@ -122,9 +120,6 @@ export function scanRoutes(): RouteManifest {
|
|
|
122
120
|
const dirName = entry.name;
|
|
123
121
|
// Route groups like (public), (auth) are invisible in URLs
|
|
124
122
|
const isGroup = /^\(.*\)$/.test(dirName);
|
|
125
|
-
// `(private)` anywhere in the chain marks descendants as per-user (dedup off)
|
|
126
|
-
const childScope: "public" | "private" =
|
|
127
|
-
inheritedScope === "private" || dirName === "(private)" ? "private" : "public";
|
|
128
123
|
|
|
129
124
|
walk(
|
|
130
125
|
dir ? join(dir, dirName) : dirName,
|
|
@@ -133,12 +128,11 @@ export function scanRoutes(): RouteManifest {
|
|
|
133
128
|
currentLayoutServers,
|
|
134
129
|
currentErrorPages,
|
|
135
130
|
currentTrailingSlash,
|
|
136
|
-
childScope,
|
|
137
131
|
);
|
|
138
132
|
}
|
|
139
133
|
}
|
|
140
134
|
|
|
141
|
-
walk("", [], [], [], [], "never"
|
|
135
|
+
walk("", [], [], [], [], "never");
|
|
142
136
|
|
|
143
137
|
// Warn when a catch-all exists but no exact route covers its prefix.
|
|
144
138
|
// e.g. "/[...slug]" matches everything EXCEPT "/" (which needs its own +page.svelte).
|