@rsc-kit/core 0.30.43 → 0.30.45
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/cache.d.ts +2 -0
- package/dist/cache.js +10 -3
- package/dist/cache.js.map +1 -1
- package/dist/changed.js +4 -0
- package/dist/changed.js.map +1 -1
- package/dist/conformance.d.ts +16 -0
- package/dist/conformance.js +106 -14
- package/dist/conformance.js.map +1 -1
- package/dist/host.d.ts +14 -0
- package/dist/host.js +97 -3
- package/dist/host.js.map +1 -1
- package/dist/pageCache.d.ts +27 -0
- package/dist/pageCache.js +161 -3
- package/dist/pageCache.js.map +1 -1
- package/dist/purge.d.ts +57 -0
- package/dist/purge.js +147 -0
- package/dist/purge.js.map +1 -0
- package/dist/request.d.ts +9 -1
- package/dist/request.js +24 -2
- package/dist/request.js.map +1 -1
- package/dist/vite.js +3 -0
- package/dist/vite.js.map +1 -1
- package/package.json +5 -1
package/dist/host.js
CHANGED
|
@@ -19,7 +19,7 @@ import { apiKey } from "./apiPrerender.js";
|
|
|
19
19
|
import { hostPath, hostSegment, routableHost } from "./hostRouting.js";
|
|
20
20
|
import { withRevalidation } from "./revalidate.js";
|
|
21
21
|
import { withoutPayloadKey } from "./js/payloadKey.js";
|
|
22
|
-
import { cachingFor, finishCacheable, noteReadsSoFar } from "./pageCache.js";
|
|
22
|
+
import { cachingFor, createPageCache, finishCacheable, noteReadsSoFar, pageMemoryStore } from "./pageCache.js";
|
|
23
23
|
/**
|
|
24
24
|
* @internal For a host adapter that embeds the engine. An app imports this
|
|
25
25
|
* from `@rsc-kit/core/revalidate`.
|
|
@@ -69,7 +69,7 @@ import { isCrawler } from "./crawler.js";
|
|
|
69
69
|
import { inlineFlight, inlineFlightStart } from "./inlineFlight.js";
|
|
70
70
|
import { currentNotFound, withRedirect } from "./redirect.js";
|
|
71
71
|
import { compressed } from "./compress.js";
|
|
72
|
-
import { setCacheBuild, withCache } from "./cache.js";
|
|
72
|
+
import { installedCacheStore, setCacheBuild, withCache } from "./cache.js";
|
|
73
73
|
import { notFoundRowSent, renderAsNobody, takeAfterWork, withRequest, withResponseDraft, withUrl } from "./request.js";
|
|
74
74
|
import { criticalAssetsOf, linkHeader, mergeAssets } from "./earlyHints.js";
|
|
75
75
|
/**
|
|
@@ -86,6 +86,19 @@ export { matchIntercept, matchRoute, sharedDepth } from "./routing.js";
|
|
|
86
86
|
import { FLIGHT_TYPE, HEADER, HTML_TYPE, PER_CLIENT, PER_REQUEST, REVALIDATE, EDGE, VARY_ON_RSC, } from "./headers.js";
|
|
87
87
|
import { ServerAuthenticationError, ServerAuthorizationError, } from "./js/errors.js";
|
|
88
88
|
import { assertSigningSecret, serveChanges } from "./changed.js";
|
|
89
|
+
/**
|
|
90
|
+
* Whether instances live for a request or a few: a Worker's isolates, a
|
|
91
|
+
* Vercel function. Memory there is a store few requests ever find.
|
|
92
|
+
*/
|
|
93
|
+
function shortLivedInstances() {
|
|
94
|
+
const env = globalThis.process?.env;
|
|
95
|
+
return globalThis.navigator?.userAgent === "Cloudflare-Workers" || Boolean(env?.VERCEL);
|
|
96
|
+
}
|
|
97
|
+
/** Whether the app runs on Homeport, whose edge keeps pages itself. */
|
|
98
|
+
function onHomeport() {
|
|
99
|
+
const env = globalThis.process?.env;
|
|
100
|
+
return Boolean(env?.HOMEPORT_PURGE_URL);
|
|
101
|
+
}
|
|
89
102
|
/**
|
|
90
103
|
* The answer to a redirect that was decided before anything was written.
|
|
91
104
|
*
|
|
@@ -487,6 +500,10 @@ export function createRscHandler(options) {
|
|
|
487
500
|
const maxActionBody = options.maxActionBody ?? DEFAULT_MAX_ACTION_BODY;
|
|
488
501
|
const trustedOrigins = trustedOriginsOf(options.trustedOrigins);
|
|
489
502
|
const compress = options.compress ?? true;
|
|
503
|
+
// The page cache, made on the first request: the app installs its store in
|
|
504
|
+
// register(), which runs after the handler is made.
|
|
505
|
+
let pages;
|
|
506
|
+
const pageCacheOption = onHomeport() ? false : (options.pageCache ?? true);
|
|
490
507
|
// Annotated rather than inferred: the narrowing below is lost inside the
|
|
491
508
|
// closures that use it, and every one of them runs after the throw.
|
|
492
509
|
const manifest = options.manifest ?? engine.manifest?.();
|
|
@@ -692,6 +709,79 @@ export function createRscHandler(options) {
|
|
|
692
709
|
const caching = cachingFor(arrived, page?.route.revalidate);
|
|
693
710
|
if (caching && (page?.route.hostMiddleware?.length ?? 0) > 0)
|
|
694
711
|
caching.refused ??= "host-middleware";
|
|
712
|
+
if (pages === undefined) {
|
|
713
|
+
const store = pageCacheOption === false ? null : installedCacheStore();
|
|
714
|
+
if (pageCacheOption !== false && !store && shortLivedInstances()) {
|
|
715
|
+
console.warn("[rsc-kit] revalidate pages are kept in this instance's memory, which this platform starts and stops per request: few requests will find one. Install a shared store with installCacheStore() - Workers KV, Redis - or set pageCache: false.");
|
|
716
|
+
}
|
|
717
|
+
pages =
|
|
718
|
+
pageCacheOption === false
|
|
719
|
+
? null
|
|
720
|
+
: createPageCache(store ?? pageMemoryStore(typeof pageCacheOption === "object" ? pageCacheOption.maxBytes : undefined));
|
|
721
|
+
}
|
|
722
|
+
// The url as it arrived names the answer whole: the host, the query, and
|
|
723
|
+
// the ?_rsc= key that tells a document from each payload.
|
|
724
|
+
const pageKey = pages && caching && !caching.refused ? `${version ?? ""}|${arrived.url}` : null;
|
|
725
|
+
if (pages && pageKey) {
|
|
726
|
+
const hit = await pages.lookup(pageKey, arrived);
|
|
727
|
+
if (hit) {
|
|
728
|
+
if (hit.stale)
|
|
729
|
+
refreshKept(pages, pageKey, arrived, page.route);
|
|
730
|
+
return await finishKept(request, hit.response);
|
|
731
|
+
}
|
|
732
|
+
const run = await pages.once(pageKey, () => answer(request, caching, pageKey));
|
|
733
|
+
if (run.led)
|
|
734
|
+
return run.value ?? null;
|
|
735
|
+
// Another request rendered it while this one waited.
|
|
736
|
+
const after = await pages.lookup(pageKey, arrived);
|
|
737
|
+
if (after)
|
|
738
|
+
return await finishKept(request, after.response);
|
|
739
|
+
}
|
|
740
|
+
return await answer(request, caching, pageKey);
|
|
741
|
+
};
|
|
742
|
+
/**
|
|
743
|
+
* Render a kept page again behind a visitor who was just sent the stale
|
|
744
|
+
* copy - once per page at a time, in a request of its own: the visitor's
|
|
745
|
+
* may be cancelled the moment their answer is sent, and a HEAD or a
|
|
746
|
+
* conditional request is not the render to keep. A render that fails, or
|
|
747
|
+
* turns out private, keeps nothing, so the stale copy stays until its
|
|
748
|
+
* window ends.
|
|
749
|
+
*/
|
|
750
|
+
function refreshKept(pages, pageKey, arrived, route) {
|
|
751
|
+
if (pages.busy(pageKey))
|
|
752
|
+
return;
|
|
753
|
+
const headers = new Headers(arrived.headers);
|
|
754
|
+
headers.delete("If-None-Match");
|
|
755
|
+
headers.delete("If-Modified-Since");
|
|
756
|
+
const fresh = new Request(arrived.url, { method: "GET", headers });
|
|
757
|
+
const caching = cachingFor(fresh, route.revalidate);
|
|
758
|
+
if (!caching)
|
|
759
|
+
return;
|
|
760
|
+
const work = pages
|
|
761
|
+
.once(pageKey, () => answer(withoutPayloadKey(fresh), caching, pageKey))
|
|
762
|
+
.then(async (run) => {
|
|
763
|
+
await run.value?.body?.cancel();
|
|
764
|
+
})
|
|
765
|
+
.catch((error) => {
|
|
766
|
+
console.warn(`[rsc-kit] refreshing the kept copy of ${caching.path} failed; the stale copy is served until it is rendered:`, error);
|
|
767
|
+
});
|
|
768
|
+
// A Worker keeps the isolate alive only for what is handed to waitUntil.
|
|
769
|
+
const context = arrived.context;
|
|
770
|
+
if (typeof context?.waitUntil === "function")
|
|
771
|
+
context.waitUntil(work);
|
|
772
|
+
}
|
|
773
|
+
/** A page the engine kept, sent the way a rendered one would be. */
|
|
774
|
+
async function finishKept(request, response) {
|
|
775
|
+
response.headers.set("X-RSC-Kit", "cached");
|
|
776
|
+
if (identify)
|
|
777
|
+
response.headers.set("X-Powered-By", "rsc-kit");
|
|
778
|
+
if (compress) {
|
|
779
|
+
const answer = await compressed(request, response, undefined);
|
|
780
|
+
return answer;
|
|
781
|
+
}
|
|
782
|
+
return response;
|
|
783
|
+
}
|
|
784
|
+
async function answer(request, caching, pageKey) {
|
|
695
785
|
return await withRequest(request, () => withCache(() =>
|
|
696
786
|
// Open for the whole request and sealed the moment an answer exists,
|
|
697
787
|
// so middleware — which runs before any rendering — can put headers on
|
|
@@ -752,6 +842,9 @@ export function createRscHandler(options) {
|
|
|
752
842
|
if (link)
|
|
753
843
|
response.headers.set("Link", link);
|
|
754
844
|
}
|
|
845
|
+
// Kept by the engine, when finishCacheable sent it public.
|
|
846
|
+
if (pages && pageKey)
|
|
847
|
+
await pages.keep(pageKey, response);
|
|
755
848
|
// What built it. The name only, never the version - a version in
|
|
756
849
|
// every response is what a vulnerability scanner filters on - and
|
|
757
850
|
// off for a team whose policy strips every framework identifier.
|
|
@@ -784,7 +877,8 @@ export function createRscHandler(options) {
|
|
|
784
877
|
}
|
|
785
878
|
return response;
|
|
786
879
|
})));
|
|
787
|
-
}
|
|
880
|
+
}
|
|
881
|
+
;
|
|
788
882
|
async function route(request) {
|
|
789
883
|
let url = new URL(request.url);
|
|
790
884
|
// A host other than the site's own is matched with its segment in front
|