@rsc-kit/core 0.30.28 → 0.30.30
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/edgeShells.d.ts +49 -7
- package/dist/edgeShells.js +117 -8
- package/dist/edgeShells.js.map +1 -1
- package/dist/headers.d.ts +18 -12
- package/dist/headers.js +33 -12
- package/dist/headers.js.map +1 -1
- package/dist/host.js +87 -28
- package/dist/host.js.map +1 -1
- package/dist/js/createViteRscApp.js +12 -3
- package/dist/js/createViteRscApp.js.map +1 -1
- package/dist/js/pagePayload.d.ts +1 -1
- package/dist/js/pagePayload.js +5 -2
- package/dist/js/pagePayload.js.map +1 -1
- package/dist/manifest.d.ts +5 -0
- package/dist/manifest.js.map +1 -1
- package/dist/vite.d.ts +49 -0
- package/dist/vite.js +153 -13
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
package/dist/host.js
CHANGED
|
@@ -81,7 +81,7 @@ export { redirect } from "./redirect.js";
|
|
|
81
81
|
// public surface so a host imports from one place.
|
|
82
82
|
/** @internal For a host adapter. An app never matches its own routes. */
|
|
83
83
|
export { matchIntercept, matchRoute, sharedDepth } from "./routing.js";
|
|
84
|
-
import { FLIGHT_TYPE, HEADER, HTML_TYPE, PER_CLIENT, PER_REQUEST, REVALIDATE,
|
|
84
|
+
import { FLIGHT_TYPE, HEADER, HTML_TYPE, PER_CLIENT, PER_REQUEST, REVALIDATE, EDGE, VARY_ON_RSC, } from "./headers.js";
|
|
85
85
|
import { ServerAuthenticationError, ServerAuthorizationError, } from "./js/errors.js";
|
|
86
86
|
import { assertSigningSecret, serveChanges } from "./changed.js";
|
|
87
87
|
/**
|
|
@@ -433,6 +433,8 @@ export function createRscHandler(options) {
|
|
|
433
433
|
// own. Resolved on the first request: the engine reads it from a build
|
|
434
434
|
// product it only has at runtime.
|
|
435
435
|
let version = engine.buildId ? undefined : options.version;
|
|
436
|
+
// The edge contract's marker, as the build left it beside the pages - see edgeMarker.
|
|
437
|
+
let edgeMarkerRead = null;
|
|
436
438
|
const maxActionBody = options.maxActionBody ?? DEFAULT_MAX_ACTION_BODY;
|
|
437
439
|
const compress = options.compress ?? true;
|
|
438
440
|
// Annotated rather than inferred: the narrowing below is lost inside the
|
|
@@ -517,6 +519,9 @@ export function createRscHandler(options) {
|
|
|
517
519
|
const siteHosts = manifest.build?.hosts ?? [];
|
|
518
520
|
/** Whether responses say what built them. How they were served is always said. */
|
|
519
521
|
const identify = Boolean(manifest.build?.identify);
|
|
522
|
+
// rscKit({ headers }): every response, and every page an edge serves.
|
|
523
|
+
const pageHeaders = manifest.build?.headers ?? {};
|
|
524
|
+
const hasPageHeaders = Object.keys(pageHeaders).length > 0;
|
|
520
525
|
/** How a response was answered, for X-RSC-Kit: a file the build wrote, or a shell of one. */
|
|
521
526
|
const servedFrom = new WeakMap();
|
|
522
527
|
/** What a stored document's head names, read once per file, for the Link header. */
|
|
@@ -635,6 +640,12 @@ export function createRscHandler(options) {
|
|
|
635
640
|
if (!response)
|
|
636
641
|
return null;
|
|
637
642
|
const collected = taken();
|
|
643
|
+
// Under whatever the answer already has, and before the middleware's,
|
|
644
|
+
// so a page or a middleware that asked for its own value keeps it.
|
|
645
|
+
for (const [name, value] of Object.entries(pageHeaders)) {
|
|
646
|
+
if (!response.headers.has(name))
|
|
647
|
+
response.headers.set(name, value);
|
|
648
|
+
}
|
|
638
649
|
collected.forEach((value, name) => {
|
|
639
650
|
// Set-Cookie is applied below, once per cookie: iterating a Headers
|
|
640
651
|
// gives it joined in some runtimes and per-cookie in others, and a
|
|
@@ -686,7 +697,7 @@ export function createRscHandler(options) {
|
|
|
686
697
|
// plain bytes and compresses the page once, so gzip here would be
|
|
687
698
|
// garbage in the middle of a document. The edge drops
|
|
688
699
|
// Accept-Encoding, but this does not rely on it.
|
|
689
|
-
if (compress && !new URL(request.url).pathname.startsWith(
|
|
700
|
+
if (compress && !new URL(request.url).pathname.startsWith(EDGE.prefix)) {
|
|
690
701
|
const answer = await compressed(request, response, servedFrom.get(response) === "stored" ? storedKey(request, response) : undefined);
|
|
691
702
|
const from = servedFrom.get(response);
|
|
692
703
|
if (answer !== response && from)
|
|
@@ -805,10 +816,10 @@ export function createRscHandler(options) {
|
|
|
805
816
|
if (url.pathname === HEADER.changesPath) {
|
|
806
817
|
return await serveChanges(request);
|
|
807
818
|
}
|
|
808
|
-
// The
|
|
809
|
-
// and is a 404 to anyone without the
|
|
810
|
-
if (url.pathname.startsWith(
|
|
811
|
-
return await
|
|
819
|
+
// The edge contract, for a platform's edge. The whole prefix is reserved,
|
|
820
|
+
// and is a 404 to anyone without the token.
|
|
821
|
+
if (url.pathname.startsWith(EDGE.prefix)) {
|
|
822
|
+
return await serveEdgeContract(request, url);
|
|
812
823
|
}
|
|
813
824
|
// A form submitted before the page had a runtime: the browser posts it
|
|
814
825
|
// to the page's own url, as React wrote it, with the action's id among
|
|
@@ -1178,7 +1189,7 @@ export function createRscHandler(options) {
|
|
|
1178
1189
|
return answer;
|
|
1179
1190
|
}
|
|
1180
1191
|
/**
|
|
1181
|
-
* A page's shell, by the rule
|
|
1192
|
+
* A page's shell, by the rule the page endpoint applies: a PPR page, on a
|
|
1182
1193
|
* route with no guard, whose shell the build wrote for this url or for its
|
|
1183
1194
|
* pattern. Anything else is a 404 - the same pages edgeShells leaves out of
|
|
1184
1195
|
* the marker, so the list and the origin cannot disagree.
|
|
@@ -1255,26 +1266,26 @@ export function createRscHandler(options) {
|
|
|
1255
1266
|
"Content-Type": HTML_TYPE,
|
|
1256
1267
|
// Rendered for whoever asked. Never cacheable.
|
|
1257
1268
|
"Cache-Control": PER_CLIENT,
|
|
1258
|
-
...(
|
|
1269
|
+
...(edgeMarkerRead ? { [EDGE.build]: edgeMarkerRead.build } : {}),
|
|
1259
1270
|
}),
|
|
1260
1271
|
});
|
|
1261
1272
|
});
|
|
1262
1273
|
}
|
|
1263
1274
|
/**
|
|
1264
|
-
* The
|
|
1265
|
-
* for a caller holding the
|
|
1266
|
-
* everything under /
|
|
1267
|
-
* app is safe wherever it runs, and only the platform can ask.
|
|
1275
|
+
* The edge contract's two endpoints, for a platform's edge. They exist only
|
|
1276
|
+
* for a caller holding the EDGE_TOKEN the platform set: unset, missing or
|
|
1277
|
+
* wrong, everything under /_edge/ is a 404, as if there were nothing here -
|
|
1278
|
+
* so an app is safe wherever it runs, and only the platform can ask.
|
|
1268
1279
|
*
|
|
1269
|
-
*
|
|
1270
|
-
*
|
|
1271
|
-
*
|
|
1280
|
+
* A page the marker lists as static goes whole, `complete`; a shell goes
|
|
1281
|
+
* with an empty `state`, since the resume reads its own from the build and
|
|
1282
|
+
* ignores the body. Both answers name the build in Edge-Build.
|
|
1272
1283
|
*/
|
|
1273
|
-
async function
|
|
1274
|
-
const answer = await
|
|
1284
|
+
async function serveEdgeContract(request, url) {
|
|
1285
|
+
const answer = await edgeContractAnswer(request, url);
|
|
1275
1286
|
// Every answer here depends on the token, a 404 included: stored by a
|
|
1276
1287
|
// cache in between, one would be served to callers with the wrong token
|
|
1277
|
-
// or none - and a stored 404 would hide a real
|
|
1288
|
+
// or none - and a stored 404 would hide a real page from the edge.
|
|
1278
1289
|
try {
|
|
1279
1290
|
answer.headers.set("Cache-Control", "private, no-store");
|
|
1280
1291
|
return answer;
|
|
@@ -1286,30 +1297,48 @@ export function createRscHandler(options) {
|
|
|
1286
1297
|
});
|
|
1287
1298
|
}
|
|
1288
1299
|
}
|
|
1289
|
-
async function
|
|
1300
|
+
async function edgeContractAnswer(request, url) {
|
|
1290
1301
|
const missing = new Response("Not found", { status: 404 });
|
|
1291
1302
|
const read = options.prerendered;
|
|
1292
|
-
if (!read || !
|
|
1303
|
+
if (!read || !holdsSecret(request, EDGE.token, EDGE.tokenEnv))
|
|
1304
|
+
return missing;
|
|
1305
|
+
const marker = await edgeMarker(read);
|
|
1306
|
+
if (!marker)
|
|
1293
1307
|
return missing;
|
|
1294
|
-
|
|
1295
|
-
|
|
1308
|
+
const { build } = marker;
|
|
1309
|
+
if (url.pathname === EDGE.pagePath && (request.method === "GET" || request.method === "HEAD")) {
|
|
1310
|
+
const asked = url.searchParams.get("path");
|
|
1311
|
+
const pathname = contractPathname(asked, url);
|
|
1296
1312
|
if (pathname === null)
|
|
1297
1313
|
return missing;
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1314
|
+
// Whole only for a path the marker names, and only from the document
|
|
1315
|
+
// the build stored for it; anything else is a shell or nothing.
|
|
1316
|
+
const complete = marker.static.has(asked);
|
|
1317
|
+
const html = complete ? await pageAt(pathname, read) : await shellAt(pathname, read);
|
|
1318
|
+
if (html instanceof Response)
|
|
1319
|
+
return html;
|
|
1320
|
+
return new Response(request.method === "HEAD"
|
|
1321
|
+
? null
|
|
1322
|
+
: JSON.stringify({
|
|
1323
|
+
v: 2,
|
|
1324
|
+
build,
|
|
1325
|
+
html,
|
|
1326
|
+
state: "",
|
|
1327
|
+
...(complete ? { complete: true } : {}),
|
|
1328
|
+
// What the page itself goes out with, so the edge's copy does too.
|
|
1329
|
+
...(hasPageHeaders ? { headers: pageHeaders } : {}),
|
|
1330
|
+
}), {
|
|
1302
1331
|
headers: {
|
|
1303
1332
|
"Content-Type": "application/json",
|
|
1304
1333
|
// Fixed for the build, so no ETag. And never stored on the way:
|
|
1305
1334
|
// it was answered only for the token, and a cache between the
|
|
1306
1335
|
// edge and here would hand it to callers without one.
|
|
1307
1336
|
"Cache-Control": "private, no-store",
|
|
1308
|
-
[
|
|
1337
|
+
[EDGE.build]: build,
|
|
1309
1338
|
},
|
|
1310
1339
|
});
|
|
1311
1340
|
}
|
|
1312
|
-
if (url.pathname ===
|
|
1341
|
+
if (url.pathname === EDGE.resumePath && request.method === "POST") {
|
|
1313
1342
|
const raw = url.searchParams.get("url");
|
|
1314
1343
|
const at = raw === null ? -1 : raw.indexOf("?");
|
|
1315
1344
|
const pathname = contractPathname(at === -1 ? raw : raw.slice(0, at), url);
|
|
@@ -1319,6 +1348,36 @@ export function createRscHandler(options) {
|
|
|
1319
1348
|
}
|
|
1320
1349
|
return missing;
|
|
1321
1350
|
}
|
|
1351
|
+
/**
|
|
1352
|
+
* A static page's whole document, as the build stored it. The marker lists
|
|
1353
|
+
* no guarded page, and this checks again rather than trusting the list:
|
|
1354
|
+
* a document handed to an edge is served to everyone.
|
|
1355
|
+
*/
|
|
1356
|
+
async function pageAt(pathname, read) {
|
|
1357
|
+
const route = matchRoute(routes, pathname);
|
|
1358
|
+
const key = route && !isGuarded(route) ? storedKeyFor(pathname, route) : null;
|
|
1359
|
+
const html = key === null ? null : await read(`${key}.html`);
|
|
1360
|
+
return html ?? new Response("No such page", { status: 404 });
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* The marker, which the build wrote beside the pages as well as into
|
|
1364
|
+
* .output - its build is not the client's id this host sends a browser,
|
|
1365
|
+
* which a server-only change keeps (see edgeBuildId), and its static list
|
|
1366
|
+
* is what may go whole.
|
|
1367
|
+
*/
|
|
1368
|
+
async function edgeMarker(read) {
|
|
1369
|
+
if (edgeMarkerRead)
|
|
1370
|
+
return edgeMarkerRead;
|
|
1371
|
+
try {
|
|
1372
|
+
const marker = JSON.parse((await read(EDGE.storedMarker)) ?? "null");
|
|
1373
|
+
if (marker?.build)
|
|
1374
|
+
edgeMarkerRead = { build: marker.build, static: new Set(marker.static ?? []) };
|
|
1375
|
+
}
|
|
1376
|
+
catch {
|
|
1377
|
+
// A marker this build wrote and cannot read back: no edge, not a guess.
|
|
1378
|
+
}
|
|
1379
|
+
return edgeMarkerRead;
|
|
1380
|
+
}
|
|
1322
1381
|
/**
|
|
1323
1382
|
* A contract path - absolute and decoded once - as the pathname this host
|
|
1324
1383
|
* matches, or null for one that would not survive the trip unchanged: a
|