bosia 1.1.5 → 1.1.6
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/package.json +1 -1
- package/src/core/cache.ts +3 -0
- package/src/core/renderer.ts +12 -3
- package/src/core/server.ts +18 -9
package/package.json
CHANGED
package/src/core/cache.ts
CHANGED
|
@@ -404,6 +404,9 @@ export function serveCached(entry: CacheEntry, req: Request): Response {
|
|
|
404
404
|
return new Response(null, { status: 304, headers });
|
|
405
405
|
}
|
|
406
406
|
}
|
|
407
|
+
// BosiaApp drops HEAD bodies, and a null body would report Content-Length 0.
|
|
408
|
+
// GET leaves it to the runtime, so a hook that rewrites the body stays valid.
|
|
409
|
+
if (req.method === "HEAD") headers["content-length"] = String(body.byteLength);
|
|
407
410
|
if (encoding) {
|
|
408
411
|
headers["content-encoding"] = encoding;
|
|
409
412
|
return new Response(body, { ...PRECOMPRESSED, status: entry.status, headers });
|
package/src/core/renderer.ts
CHANGED
|
@@ -615,15 +615,24 @@ export async function renderSSRStream(
|
|
|
615
615
|
// into the cached HTML but the CSP header is re-derived each request, so a
|
|
616
616
|
// cached page would ship with a dead nonce and the browser would block its
|
|
617
617
|
// inline scripts. Operators who turn on CSP_DIRECTIVES forfeit the cache.
|
|
618
|
-
|
|
619
|
-
|
|
618
|
+
// HEAD reads the GET entry but never coalesces or writes: it isn't a leader
|
|
619
|
+
// that fills it.
|
|
620
|
+
const cacheRead =
|
|
621
|
+
CACHE_ENABLED &&
|
|
622
|
+
!CSP_ENABLED &&
|
|
623
|
+
!warmingUp &&
|
|
624
|
+
routeCacheable &&
|
|
625
|
+
(req.method === "GET" || req.method === "HEAD");
|
|
626
|
+
const cacheable = cacheRead && req.method === "GET";
|
|
620
627
|
let cacheKey: string | null = null;
|
|
621
628
|
let releaseMiss: (() => void) | null = null;
|
|
622
|
-
if (
|
|
629
|
+
if (cacheRead) {
|
|
623
630
|
cacheKey = computeCacheKey(url, req, cookies as CookieJar);
|
|
624
631
|
if (!cacheBypass) {
|
|
625
632
|
const hit = cacheGet(cacheKey);
|
|
626
633
|
if (hit) return serveCached(hit, req);
|
|
634
|
+
}
|
|
635
|
+
if (cacheable && !cacheBypass) {
|
|
627
636
|
// Miss coalescing: the first miss leads the build; concurrent misses
|
|
628
637
|
// wait, then re-check the cache. A waiter that still misses (leader
|
|
629
638
|
// skipped the write) builds independently — no re-leadering.
|
package/src/core/server.ts
CHANGED
|
@@ -606,15 +606,16 @@ async function resolve(event: RequestEvent): Promise<Response> {
|
|
|
606
606
|
let releaseApiMiss: (() => void) | null = null;
|
|
607
607
|
try {
|
|
608
608
|
const mod = await apiMatch.route.module();
|
|
609
|
-
|
|
609
|
+
// HEAD falls back to GET (an explicit HEAD export still wins). BosiaApp
|
|
610
|
+
// strips the body on the way out, for every route kind.
|
|
611
|
+
const handler = mod[method] ?? (method === "HEAD" ? mod.GET : undefined);
|
|
610
612
|
|
|
611
613
|
if (!handler) {
|
|
612
|
-
const allowed = Object.keys(mod)
|
|
613
|
-
|
|
614
|
-
.join(", ");
|
|
614
|
+
const allowed = Object.keys(mod).filter((k) => /^[A-Z]+$/.test(k));
|
|
615
|
+
if (allowed.includes("GET") && !allowed.includes("HEAD")) allowed.push("HEAD");
|
|
615
616
|
return Response.json(
|
|
616
617
|
{ error: `Method ${method} not allowed` },
|
|
617
|
-
{ status: 405, headers: { Allow: allowed } },
|
|
618
|
+
{ status: 405, headers: { Allow: allowed.join(", ") } },
|
|
618
619
|
);
|
|
619
620
|
}
|
|
620
621
|
|
|
@@ -623,15 +624,23 @@ async function resolve(event: RequestEvent): Promise<Response> {
|
|
|
623
624
|
// ── Response cache for +server.ts GET handlers ──
|
|
624
625
|
// CSP is skipped because cached responses would ship with a stale
|
|
625
626
|
// nonce (see renderer.ts for the same gate). The cache key includes
|
|
626
|
-
// URL + identity so per-user responses stay isolated.
|
|
627
|
-
|
|
628
|
-
|
|
627
|
+
// URL + identity so per-user responses stay isolated. HEAD reads the GET
|
|
628
|
+
// entry but never coalesces or writes: it isn't a leader that fills it.
|
|
629
|
+
// An explicit HEAD export skips the cache so it always runs.
|
|
630
|
+
const apiCacheRead =
|
|
631
|
+
CACHE_ENABLED &&
|
|
632
|
+
!CSP_ENABLED &&
|
|
633
|
+
(mod as any).cache !== false &&
|
|
634
|
+
(method === "GET" || (method === "HEAD" && !mod.HEAD));
|
|
635
|
+
const apiCacheable = apiCacheRead && method === "GET";
|
|
629
636
|
let apiCacheKey: string | null = null;
|
|
630
|
-
if (
|
|
637
|
+
if (apiCacheRead) {
|
|
631
638
|
apiCacheKey = computeCacheKey(url, request, cookies as CookieJar);
|
|
632
639
|
if (!url.searchParams.has("_invalidated")) {
|
|
633
640
|
const hit = cacheGet(apiCacheKey);
|
|
634
641
|
if (hit) return serveCached(hit, request);
|
|
642
|
+
}
|
|
643
|
+
if (apiCacheable && !url.searchParams.has("_invalidated")) {
|
|
635
644
|
// Miss coalescing: first miss runs the handler; concurrent misses
|
|
636
645
|
// wait, re-check the cache, and on a still-miss run independently.
|
|
637
646
|
const gate = coalesceMiss(apiCacheKey);
|