@rsc-kit/core 0.30.29 → 0.30.31
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/earlyHints.js +5 -1
- package/dist/earlyHints.js.map +1 -1
- 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 +83 -38
- 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 +44 -10
- package/dist/vite.js +133 -33
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
package/dist/earlyHints.js
CHANGED
|
@@ -16,8 +16,12 @@ export function linkHeader(assets) {
|
|
|
16
16
|
const parts = [];
|
|
17
17
|
for (const href of assets.styles)
|
|
18
18
|
parts.push(`<${href}>; rel=preload; as=style`);
|
|
19
|
+
// A preload, not a modulepreload: Cloudflare builds its 103 from preload and
|
|
20
|
+
// preconnect alone, and dropped the entry. `crossorigin` is the module
|
|
21
|
+
// script's own fetch - cors, same-origin credentials - so the browser reuses
|
|
22
|
+
// the file; the document's own modulepreload still compiles it early.
|
|
19
23
|
for (const href of assets.modules)
|
|
20
|
-
parts.push(`<${href}>; rel=
|
|
24
|
+
parts.push(`<${href}>; rel=preload; as=script; crossorigin`);
|
|
21
25
|
for (const href of assets.fonts)
|
|
22
26
|
parts.push(`<${href}>; rel=preload; as=font; crossorigin`);
|
|
23
27
|
return parts.join(', ');
|
package/dist/earlyHints.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"earlyHints.js","sourceRoot":"","sources":["../src/earlyHints.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,yEAAyE;AACzE,wEAAwE;AACxE,sEAAsE;AACtE,yEAAyE;AACzE,oBAAoB;AACpB,EAAE;AACF,4EAA4E;AAC5E,0EAA0E;AAC1E,0EAA0E;AAC1E,4EAA4E;AAC5E,4EAA4E;AAC5E,+DAA+D;AAW/D,0EAA0E;AAC1E,MAAM,UAAU,UAAU,CAAC,MAAsB;IAC/C,MAAM,KAAK,GAAa,EAAE,CAAA;IAE1B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,0BAA0B,CAAC,CAAA;IAChF,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,
|
|
1
|
+
{"version":3,"file":"earlyHints.js","sourceRoot":"","sources":["../src/earlyHints.ts"],"names":[],"mappings":"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","sourcesContent":["// The Link header a document answers with, for a CDN to send ahead as 103\n// Early Hints - so the stylesheet, the client entry and the fonts are on\n// their way while the HTML is still being written. Cloudflare turns the\n// header into hints and caches them at the edge for the next visitor;\n// anything else passes it through, where a browser still reads it as the\n// document arrives.\n//\n// The critical set and nothing more: a hint promotes whatever it names, and\n// naming every chunk demotes the document behind them. The stylesheet and\n// the client entry come from the build's own manifest, the same names the\n// document carries. The fonts are the app's - preloaded in its layout - and\n// are read from a stored document's own head; a rendered document's head is\n// not known until it is rendered, after its headers have gone.\n\nexport interface CriticalAssets {\n /** Stylesheets the document links: `<link rel=\"stylesheet\">`. */\n styles: string[];\n /** The client entry: `<script type=\"module\">`'s import. */\n modules: string[];\n /** Fonts the document preloads: `<link rel=\"preload\" as=\"font\">`. */\n fonts: string[];\n}\n\n/** One `Link` header value for the set. Empty string for an empty set. */\nexport function linkHeader(assets: CriticalAssets): string {\n const parts: string[] = []\n\n for (const href of assets.styles) parts.push(`<${href}>; rel=preload; as=style`)\n // A preload, not a modulepreload: Cloudflare builds its 103 from preload and\n // preconnect alone, and dropped the entry. `crossorigin` is the module\n // script's own fetch - cors, same-origin credentials - so the browser reuses\n // the file; the document's own modulepreload still compiles it early.\n for (const href of assets.modules) parts.push(`<${href}>; rel=preload; as=script; crossorigin`)\n for (const href of assets.fonts) parts.push(`<${href}>; rel=preload; as=font; crossorigin`)\n\n return parts.join(', ')\n}\n\nconst HEAD_LIMIT = 16_384\nconst LINK = /<link\\s[^>]*>/g\nconst ATTR = (name: string, tag: string): string | null => {\n const match = new RegExp(`\\\\b${name}=[\"']([^\"']*)[\"']`, 'i').exec(tag)\n\n return match ? match[1] : null\n}\n\n/**\n * What a stored document's head names: its stylesheets and preloaded fonts.\n * The first 16 KB, which is the head of any document this build writes.\n */\nexport function criticalAssetsOf(html: string): CriticalAssets {\n const head = html.slice(0, HEAD_LIMIT)\n const found: CriticalAssets = { styles: [], modules: [], fonts: [] }\n\n for (const tag of head.match(LINK) ?? []) {\n const rel = ATTR('rel', tag)?.toLowerCase()\n const href = ATTR('href', tag)\n\n if (!href || !href.startsWith('/')) continue\n\n // A stylesheet kept only for print is one the document has inlined:\n // hinting it would fetch at the highest priority what the page asked for\n // at the lowest.\n if (rel === 'stylesheet') {\n if (ATTR('media', tag)?.toLowerCase() !== 'print') found.styles.push(href)\n }\n else if (rel === 'preload' && ATTR('as', tag)?.toLowerCase() === 'font') found.fonts.push(href)\n }\n\n // The bootstrap script is the last thing in the body, after the shell; its\n // import names the entry. The tail, then, and the head for a short page.\n const entry = /import\\([\"'](\\/[^\"']+\\.js)[\"']\\)/.exec(html.slice(-4096)) ?? /import\\([\"'](\\/[^\"']+\\.js)[\"']\\)/.exec(head)\n\n if (entry) found.modules.push(entry[1])\n\n return found\n}\n\n/**\n * One set over another: the build's names, for a document whose own are not\n * known.\n *\n * `primary` is null when nothing was read - a document rendered per request,\n * whose head does not exist until after its headers have gone. A document\n * that WAS read is the authority on itself, including when the answer is\n * \"nothing\": a stored page with no client component names no module, and\n * hinting the build's entry made the browser fetch 82 KB of runtime that\n * the page never runs - the whole claim of a route that ships no\n * JavaScript, undone by its own headers. Per field it was worse still: a\n * page with its stylesheet inlined was hinted the build's stylesheet too.\n */\nexport function mergeAssets(primary: CriticalAssets | null, fallback: CriticalAssets | null): CriticalAssets {\n return primary ?? fallback ?? EMPTY\n}\n\nconst EMPTY: CriticalAssets = { styles: [], modules: [], fonts: [] }\n"]}
|
package/dist/edgeShells.d.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* The pages an edge may serve without the app, for `.output/edge.json` - the
|
|
3
|
+
* marker of the edge contract, which an edge reads the same way for every
|
|
4
|
+
* framework that implements it: `shells`, the url patterns of the PPR pages
|
|
5
|
+
* it may answer from a cached shell, and `static`, the paths of the pages it
|
|
6
|
+
* may answer whole.
|
|
5
7
|
*
|
|
6
|
-
* An edge in front of the app asks one question of each request -
|
|
7
|
-
*
|
|
8
|
+
* An edge in front of the app asks one question of each request - may I
|
|
9
|
+
* answer this? - and answers it from a list it was handed at deploy time. Reading
|
|
8
10
|
* `rsc-static/` for it does not work: a guarded page has a shell on disk too,
|
|
9
11
|
* served from a cache it skips its guard; `_team_` cannot say whether it was a
|
|
10
12
|
* parameter or a folder of that name; and the layout is ours to change.
|
|
11
13
|
*
|
|
12
|
-
* So the list is the build's, by the same rule the
|
|
14
|
+
* So the list is the build's, by the same rule the page endpoint applies: a
|
|
13
15
|
* page with a shell, on a route with no middleware - neither a `middleware.ts`
|
|
14
16
|
* above it nor the host's `export const middleware`. The endpoint refuses
|
|
15
17
|
* exactly those, so the list and the origin cannot disagree.
|
|
@@ -18,16 +20,19 @@ import type { RouteSegment } from './manifest.js';
|
|
|
18
20
|
/** What an edge is promised, and what it can hold. */
|
|
19
21
|
export declare const MAX_SHELLS = 200;
|
|
20
22
|
export declare const MAX_SHELL_BYTES = 200;
|
|
23
|
+
/** The same again for `static`, on top of `shells`' count. */
|
|
24
|
+
export declare const MAX_STATIC = 200;
|
|
21
25
|
/**
|
|
22
26
|
* The marker. `bypass` names this framework's own requests on a page's path
|
|
23
27
|
* that are not a visitor's document: every payload fetch - a navigation, a
|
|
24
28
|
* prefetch, a refresh - carries X-RSC, and an action is a POST, which an edge
|
|
25
29
|
* never answers from a shell anyway.
|
|
26
30
|
*/
|
|
27
|
-
export declare function
|
|
31
|
+
export declare function edgeMarker(build: string, shells: string[], statics: string[]): {
|
|
28
32
|
v: number;
|
|
29
33
|
build: string;
|
|
30
34
|
shells: string[];
|
|
35
|
+
static: string[];
|
|
31
36
|
bypass: {
|
|
32
37
|
headers: string[];
|
|
33
38
|
};
|
|
@@ -50,3 +55,40 @@ export declare function edgeShells(results: readonly {
|
|
|
50
55
|
shells: string[];
|
|
51
56
|
left: string[];
|
|
52
57
|
};
|
|
58
|
+
/**
|
|
59
|
+
* The pages an edge may answer whole: frozen, so the document is the same for
|
|
60
|
+
* every visitor until the next build, with no guard of either kind and not
|
|
61
|
+
* bound from the host - and listed as the paths the build rendered, since
|
|
62
|
+
* `static` takes no patterns: a pattern would ask the edge for paths that
|
|
63
|
+
* were never rendered. A frozen redirect is not here: it has no document.
|
|
64
|
+
*
|
|
65
|
+
* A path a listed shell's pattern also matches is left out, so no path is
|
|
66
|
+
* two things to the edge.
|
|
67
|
+
*/
|
|
68
|
+
export declare function edgeStatic(results: readonly {
|
|
69
|
+
type: string;
|
|
70
|
+
component: string;
|
|
71
|
+
url: string;
|
|
72
|
+
}[], routes: readonly ShellRoute[], shells: readonly string[]): {
|
|
73
|
+
static: string[];
|
|
74
|
+
left: string[];
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Why pages an edge could otherwise have served were kept out: the outermost
|
|
78
|
+
* middleware above each, with how many pages it guards. A root middleware -
|
|
79
|
+
* the usual home of security headers - guards every page and empties both
|
|
80
|
+
* lists, correctly, and an empty edge.json with no reason given reads as a
|
|
81
|
+
* bug. So the build names the file.
|
|
82
|
+
*/
|
|
83
|
+
export declare function edgeGuarded(results: readonly {
|
|
84
|
+
type: string;
|
|
85
|
+
component: string;
|
|
86
|
+
}[], routes: readonly ShellRoute[]): string[];
|
|
87
|
+
/**
|
|
88
|
+
* A rendered url as the contract lists it - absolute, decoded once, no query
|
|
89
|
+
* - or null when it has no such form: an empty, `.` or `..` segment, or one
|
|
90
|
+
* holding a `/` once decoded.
|
|
91
|
+
*/
|
|
92
|
+
export declare function contractPath(url: string): string | null;
|
|
93
|
+
/** A marker pattern against a path: `[name]` is one segment, a final `[...name]` one or more. */
|
|
94
|
+
export declare function matchesPattern(pattern: string, path: string): boolean;
|
package/dist/edgeShells.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* The pages an edge may serve without the app, for `.output/edge.json` - the
|
|
3
|
+
* marker of the edge contract, which an edge reads the same way for every
|
|
4
|
+
* framework that implements it: `shells`, the url patterns of the PPR pages
|
|
5
|
+
* it may answer from a cached shell, and `static`, the paths of the pages it
|
|
6
|
+
* may answer whole.
|
|
5
7
|
*
|
|
6
|
-
* An edge in front of the app asks one question of each request -
|
|
7
|
-
*
|
|
8
|
+
* An edge in front of the app asks one question of each request - may I
|
|
9
|
+
* answer this? - and answers it from a list it was handed at deploy time. Reading
|
|
8
10
|
* `rsc-static/` for it does not work: a guarded page has a shell on disk too,
|
|
9
11
|
* served from a cache it skips its guard; `_team_` cannot say whether it was a
|
|
10
12
|
* parameter or a folder of that name; and the layout is ours to change.
|
|
11
13
|
*
|
|
12
|
-
* So the list is the build's, by the same rule the
|
|
14
|
+
* So the list is the build's, by the same rule the page endpoint applies: a
|
|
13
15
|
* page with a shell, on a route with no middleware - neither a `middleware.ts`
|
|
14
16
|
* above it nor the host's `export const middleware`. The endpoint refuses
|
|
15
17
|
* exactly those, so the list and the origin cannot disagree.
|
|
@@ -17,14 +19,16 @@
|
|
|
17
19
|
/** What an edge is promised, and what it can hold. */
|
|
18
20
|
export const MAX_SHELLS = 200;
|
|
19
21
|
export const MAX_SHELL_BYTES = 200;
|
|
22
|
+
/** The same again for `static`, on top of `shells`' count. */
|
|
23
|
+
export const MAX_STATIC = 200;
|
|
20
24
|
/**
|
|
21
25
|
* The marker. `bypass` names this framework's own requests on a page's path
|
|
22
26
|
* that are not a visitor's document: every payload fetch - a navigation, a
|
|
23
27
|
* prefetch, a refresh - carries X-RSC, and an action is a POST, which an edge
|
|
24
28
|
* never answers from a shell anyway.
|
|
25
29
|
*/
|
|
26
|
-
export function
|
|
27
|
-
return { v:
|
|
30
|
+
export function edgeMarker(build, shells, statics) {
|
|
31
|
+
return { v: 2, build, shells, static: statics, bypass: { headers: ['X-RSC'] } };
|
|
28
32
|
}
|
|
29
33
|
/**
|
|
30
34
|
* The patterns, `/` for the index, `[name]` for one segment and a final
|
|
@@ -95,4 +99,109 @@ function patternOf(segments) {
|
|
|
95
99
|
function depth(pattern) {
|
|
96
100
|
return pattern === '/' ? 0 : pattern.split('/').length - 1;
|
|
97
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* The pages an edge may answer whole: frozen, so the document is the same for
|
|
104
|
+
* every visitor until the next build, with no guard of either kind and not
|
|
105
|
+
* bound from the host - and listed as the paths the build rendered, since
|
|
106
|
+
* `static` takes no patterns: a pattern would ask the edge for paths that
|
|
107
|
+
* were never rendered. A frozen redirect is not here: it has no document.
|
|
108
|
+
*
|
|
109
|
+
* A path a listed shell's pattern also matches is left out, so no path is
|
|
110
|
+
* two things to the edge.
|
|
111
|
+
*/
|
|
112
|
+
export function edgeStatic(results, routes, shells) {
|
|
113
|
+
const byComponent = new Map(routes.map((route) => [route.component, route]));
|
|
114
|
+
const statics = [];
|
|
115
|
+
const left = [];
|
|
116
|
+
for (const result of results) {
|
|
117
|
+
if (result.type !== 'frozen')
|
|
118
|
+
continue;
|
|
119
|
+
const route = byComponent.get(result.component);
|
|
120
|
+
if (!route)
|
|
121
|
+
continue;
|
|
122
|
+
if (route.middleware?.length || route.hostMiddleware?.length)
|
|
123
|
+
continue;
|
|
124
|
+
if (route.segments.some((segment) => segment.type === 'host'))
|
|
125
|
+
continue;
|
|
126
|
+
const path = contractPath(result.url);
|
|
127
|
+
if (path === null)
|
|
128
|
+
continue;
|
|
129
|
+
if (new TextEncoder().encode(path).length > MAX_SHELL_BYTES) {
|
|
130
|
+
left.push(`${path.slice(0, 60)}… is over ${MAX_SHELL_BYTES} bytes`);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (shells.some((pattern) => matchesPattern(pattern, path)))
|
|
134
|
+
continue;
|
|
135
|
+
if (!statics.includes(path))
|
|
136
|
+
statics.push(path);
|
|
137
|
+
}
|
|
138
|
+
if (statics.length > MAX_STATIC) {
|
|
139
|
+
statics.sort((a, b) => depth(a) - depth(b) || (a < b ? -1 : 1));
|
|
140
|
+
left.push(`${statics.length - MAX_STATIC} static past the shallowest ${MAX_STATIC}`);
|
|
141
|
+
statics.length = MAX_STATIC;
|
|
142
|
+
}
|
|
143
|
+
statics.sort();
|
|
144
|
+
return { static: statics, left };
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Why pages an edge could otherwise have served were kept out: the outermost
|
|
148
|
+
* middleware above each, with how many pages it guards. A root middleware -
|
|
149
|
+
* the usual home of security headers - guards every page and empties both
|
|
150
|
+
* lists, correctly, and an empty edge.json with no reason given reads as a
|
|
151
|
+
* bug. So the build names the file.
|
|
152
|
+
*/
|
|
153
|
+
export function edgeGuarded(results, routes) {
|
|
154
|
+
const byComponent = new Map(routes.map((route) => [route.component, route]));
|
|
155
|
+
const counts = new Map();
|
|
156
|
+
for (const result of results) {
|
|
157
|
+
if (result.type !== 'frozen' && result.type !== 'shell')
|
|
158
|
+
continue;
|
|
159
|
+
const route = byComponent.get(result.component);
|
|
160
|
+
const guard = route?.middleware?.[0] ?? (route?.hostMiddleware?.length ? `the host's ${route.hostMiddleware[0]}` : null);
|
|
161
|
+
if (guard)
|
|
162
|
+
counts.set(guard, (counts.get(guard) ?? 0) + 1);
|
|
163
|
+
}
|
|
164
|
+
return [...counts].sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1)).map(([guard, n]) => `${guard} (${n})`);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* A rendered url as the contract lists it - absolute, decoded once, no query
|
|
168
|
+
* - or null when it has no such form: an empty, `.` or `..` segment, or one
|
|
169
|
+
* holding a `/` once decoded.
|
|
170
|
+
*/
|
|
171
|
+
export function contractPath(url) {
|
|
172
|
+
const path = url.split(/[?#]/)[0];
|
|
173
|
+
if (!path.startsWith('/'))
|
|
174
|
+
return null;
|
|
175
|
+
if (path === '/')
|
|
176
|
+
return path;
|
|
177
|
+
const parts = [];
|
|
178
|
+
for (const raw of path.slice(1).split('/')) {
|
|
179
|
+
let part;
|
|
180
|
+
try {
|
|
181
|
+
part = decodeURIComponent(raw);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
if (['', '.', '..'].includes(part) || part.includes('/') || /[?#]/.test(part))
|
|
187
|
+
return null;
|
|
188
|
+
parts.push(part);
|
|
189
|
+
}
|
|
190
|
+
return '/' + parts.join('/');
|
|
191
|
+
}
|
|
192
|
+
/** A marker pattern against a path: `[name]` is one segment, a final `[...name]` one or more. */
|
|
193
|
+
export function matchesPattern(pattern, path) {
|
|
194
|
+
const want = pattern.split('/');
|
|
195
|
+
const have = path.split('/');
|
|
196
|
+
for (const [i, part] of want.entries()) {
|
|
197
|
+
if (i === want.length - 1 && /^\[\.\.\..+\]$/.test(part)) {
|
|
198
|
+
return have.length > i && have.slice(i).every(Boolean);
|
|
199
|
+
}
|
|
200
|
+
if (have[i] === undefined)
|
|
201
|
+
return false;
|
|
202
|
+
if (/^\[.+\]$/.test(part) ? have[i] === '' : part !== have[i])
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return want.length === have.length;
|
|
206
|
+
}
|
|
98
207
|
//# sourceMappingURL=edgeShells.js.map
|
package/dist/edgeShells.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edgeShells.js","sourceRoot":"","sources":["../src/edgeShells.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The url patterns of the pages an edge may serve from a cached shell, for\n * `.output/ppr.json` - the marker of the PPR contract, which an edge reads the\n * same way for every framework that implements it.\n *\n * An edge in front of the app asks one question of each request - is this a\n * PPR page? - and answers it from a list it was handed at deploy time. Reading\n * `rsc-static/` for it does not work: a guarded page has a shell on disk too,\n * served from a cache it skips its guard; `_team_` cannot say whether it was a\n * parameter or a folder of that name; and the layout is ours to change.\n *\n * So the list is the build's, by the same rule the shell endpoint applies: a\n * page with a shell, on a route with no middleware - neither a `middleware.ts`\n * above it nor the host's `export const middleware`. The endpoint refuses\n * exactly those, so the list and the origin cannot disagree.\n */\n\nimport type { RouteSegment } from './manifest.js'\n\n/** What an edge is promised, and what it can hold. */\nexport const MAX_SHELLS = 200\nexport const MAX_SHELL_BYTES = 200\n\n/**\n * The marker. `bypass` names this framework's own requests on a page's path\n * that are not a visitor's document: every payload fetch - a navigation, a\n * prefetch, a refresh - carries X-RSC, and an action is a POST, which an edge\n * never answers from a shell anyway.\n */\nexport function pprMarker(build: string, shells: string[]) {\n return { v: 1, build, shells, bypass: { headers: ['X-RSC'] } }\n}\n\nexport interface ShellRoute {\n component: string\n segments: RouteSegment[]\n middleware?: readonly string[]\n hostMiddleware?: readonly string[]\n}\n\n/**\n * The patterns, `/` for the index, `[name]` for one segment and a final\n * `[...name]` for one or more - and what was left out of the list, and why,\n * for the build to say.\n */\nexport function edgeShells(\n results: readonly { type: string; component: string }[],\n routes: readonly ShellRoute[],\n): { shells: string[]; left: string[] } {\n const byComponent = new Map(routes.map((route) => [route.component, route]))\n const shells: string[] = []\n const left: string[] = []\n\n for (const component of new Set(results.filter((r) => r.type === 'shell').map((r) => r.component))) {\n const route = byComponent.get(component)\n\n if (!route) continue\n if (route.middleware?.length || route.hostMiddleware?.length) continue\n\n // Bound from the request's host, which a path pattern cannot say: the\n // same pattern would match every tenant's page on every domain.\n if (route.segments.some((segment) => segment.type === 'host')) continue\n\n const pattern = patternOf(route.segments)\n\n if (pattern === null) continue\n\n if (new TextEncoder().encode(pattern).length > MAX_SHELL_BYTES) {\n left.push(`${pattern.slice(0, 60)}… is over ${MAX_SHELL_BYTES} bytes`)\n continue\n }\n\n if (!shells.includes(pattern)) shells.push(pattern)\n }\n\n // Over the cap, the deepest are the ones not listed: an edge that does not\n // know a page is a PPR page sends it to the origin, which answers it\n // correctly, and the shallow pages are the ones most visited.\n if (shells.length > MAX_SHELLS) {\n shells.sort((a, b) => depth(a) - depth(b) || (a < b ? -1 : 1))\n left.push(`${shells.length - MAX_SHELLS} past the shallowest ${MAX_SHELLS}`)\n shells.length = MAX_SHELLS\n }\n\n shells.sort()\n\n return { shells, left }\n}\n\n/**\n * A route's segments as the edge matches them, or null when it cannot: a\n * catch-all anywhere but last, or a name that is itself bracketed - an\n * optional catch-all, `[[...name]]`, matches no url at all.\n */\nfunction patternOf(segments: readonly RouteSegment[]): string | null {\n const parts: string[] = []\n\n for (const [index, segment] of segments.entries()) {\n // Not a path an edge can match: the contract has no empty, `.` or `..`\n // segment, and a name with a slash would be two.\n if (['', '.', '..'].includes(segment.value) || segment.value.includes('/')) return null\n if (segment.type === 'static') parts.push(segment.value)\n else if (segment.value.includes('[') || segment.value.includes(']')) return null\n else if (segment.type === 'catchAll') {\n if (index !== segments.length - 1) return null\n parts.push(`[...${segment.value}]`)\n } else parts.push(`[${segment.value}]`)\n }\n\n return '/' + parts.join('/')\n}\n\nfunction depth(pattern: string): number {\n return pattern === '/' ? 0 : pattern.split('/').length - 1\n}\n"]}
|
|
1
|
+
{"version":3,"file":"edgeShells.js","sourceRoot":"","sources":["../src/edgeShells.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,sDAAsD;AACtD,MAAM,CAAC,MAAM,UAAU,GAAG,GAAG,CAAA;AAC7B,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA;AAClC,8DAA8D;AAC9D,MAAM,CAAC,MAAM,UAAU,GAAG,GAAG,CAAA;AAE7B;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,MAAgB,EAAE,OAAiB;IAC3E,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,CAAA;AACjF,CAAC;AASD;;;;GAIG;AACH,MAAM,UAAU,UAAU,CACxB,OAAuD,EACvD,MAA6B;IAE7B,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;IAC5E,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,MAAM,IAAI,GAAa,EAAE,CAAA;IAEzB,KAAK,MAAM,SAAS,IAAI,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;QACnG,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QAExC,IAAI,CAAC,KAAK;YAAE,SAAQ;QACpB,IAAI,KAAK,CAAC,UAAU,EAAE,MAAM,IAAI,KAAK,CAAC,cAAc,EAAE,MAAM;YAAE,SAAQ;QAEtE,sEAAsE;QACtE,gEAAgE;QAChE,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC;YAAE,SAAQ;QAEvE,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;QAEzC,IAAI,OAAO,KAAK,IAAI;YAAE,SAAQ;QAE9B,IAAI,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;YAC/D,IAAI,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,eAAe,QAAQ,CAAC,CAAA;YACtE,SAAQ;QACV,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACrD,CAAC;IAED,2EAA2E;IAC3E,qEAAqE;IACrE,8DAA8D;IAC9D,IAAI,MAAM,CAAC,MAAM,GAAG,UAAU,EAAE,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAC9D,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,UAAU,wBAAwB,UAAU,EAAE,CAAC,CAAA;QAC5E,MAAM,CAAC,MAAM,GAAG,UAAU,CAAA;IAC5B,CAAC;IAED,MAAM,CAAC,IAAI,EAAE,CAAA;IAEb,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;AACzB,CAAC;AAED;;;;GAIG;AACH,SAAS,SAAS,CAAC,QAAiC;IAClD,MAAM,KAAK,GAAa,EAAE,CAAA;IAE1B,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;QAClD,uEAAuE;QACvE,iDAAiD;QACjD,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAA;QACvF,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;aACnD,IAAI,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAA;aAC3E,IAAI,OAAO,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YACrC,IAAI,KAAK,KAAK,QAAQ,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAA;YAC9C,KAAK,CAAC,IAAI,CAAC,OAAO,OAAO,CAAC,KAAK,GAAG,CAAC,CAAA;QACrC,CAAC;;YAAM,KAAK,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,GAAG,CAAC,CAAA;IACzC,CAAC;IAED,OAAO,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC9B,CAAC;AAED,SAAS,KAAK,CAAC,OAAe;IAC5B,OAAO,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAA;AAC5D,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU,CACxB,OAAoE,EACpE,MAA6B,EAC7B,MAAyB;IAEzB,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;IAC5E,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,MAAM,IAAI,GAAa,EAAE,CAAA;IAEzB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ;YAAE,SAAQ;QAEtC,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;QAE/C,IAAI,CAAC,KAAK;YAAE,SAAQ;QACpB,IAAI,KAAK,CAAC,UAAU,EAAE,MAAM,IAAI,KAAK,CAAC,cAAc,EAAE,MAAM;YAAE,SAAQ;QACtE,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC;YAAE,SAAQ;QAEvE,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAErC,IAAI,IAAI,KAAK,IAAI;YAAE,SAAQ;QAE3B,IAAI,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;YAC5D,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,eAAe,QAAQ,CAAC,CAAA;YACnE,SAAQ;QACV,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAAE,SAAQ;QACrE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACjD,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,GAAG,UAAU,EAAE,CAAC;QAChC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAC/D,IAAI,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,UAAU,+BAA+B,UAAU,EAAE,CAAC,CAAA;QACpF,OAAO,CAAC,MAAM,GAAG,UAAU,CAAA;IAC7B,CAAC;IAED,OAAO,CAAC,IAAI,EAAE,CAAA;IAEd,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;AAClC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CACzB,OAAuD,EACvD,MAA6B;IAE7B,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;IAC5E,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IAExC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO;YAAE,SAAQ;QAEjE,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;QAC/C,MAAM,KAAK,GAAG,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC,cAAc,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QAExH,IAAI,KAAK;YAAE,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAC5D,CAAC;IAED,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG,CAAC,CAAA;AACjH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;IAEjC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IACtC,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,IAAI,CAAA;IAE7B,MAAM,KAAK,GAAa,EAAE,CAAA;IAE1B,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3C,IAAI,IAAY,CAAA;QAEhB,IAAI,CAAC;YACH,IAAI,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAA;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAA;QACb,CAAC;QAED,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAA;QAC1F,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAClB,CAAC;IAED,OAAO,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC9B,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,IAAY;IAC1D,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAE5B,KAAK,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;QACvC,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACzD,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QACxD,CAAC;QAED,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS;YAAE,OAAO,KAAK,CAAA;QACvC,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC;YAAE,OAAO,KAAK,CAAA;IAC7E,CAAC;IAED,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,CAAA;AACpC,CAAC","sourcesContent":["/**\n * The pages an edge may serve without the app, for `.output/edge.json` - the\n * marker of the edge contract, which an edge reads the same way for every\n * framework that implements it: `shells`, the url patterns of the PPR pages\n * it may answer from a cached shell, and `static`, the paths of the pages it\n * may answer whole.\n *\n * An edge in front of the app asks one question of each request - may I\n * answer this? - and answers it from a list it was handed at deploy time. Reading\n * `rsc-static/` for it does not work: a guarded page has a shell on disk too,\n * served from a cache it skips its guard; `_team_` cannot say whether it was a\n * parameter or a folder of that name; and the layout is ours to change.\n *\n * So the list is the build's, by the same rule the page endpoint applies: a\n * page with a shell, on a route with no middleware - neither a `middleware.ts`\n * above it nor the host's `export const middleware`. The endpoint refuses\n * exactly those, so the list and the origin cannot disagree.\n */\n\nimport type { RouteSegment } from './manifest.js'\n\n/** What an edge is promised, and what it can hold. */\nexport const MAX_SHELLS = 200\nexport const MAX_SHELL_BYTES = 200\n/** The same again for `static`, on top of `shells`' count. */\nexport const MAX_STATIC = 200\n\n/**\n * The marker. `bypass` names this framework's own requests on a page's path\n * that are not a visitor's document: every payload fetch - a navigation, a\n * prefetch, a refresh - carries X-RSC, and an action is a POST, which an edge\n * never answers from a shell anyway.\n */\nexport function edgeMarker(build: string, shells: string[], statics: string[]) {\n return { v: 2, build, shells, static: statics, bypass: { headers: ['X-RSC'] } }\n}\n\nexport interface ShellRoute {\n component: string\n segments: RouteSegment[]\n middleware?: readonly string[]\n hostMiddleware?: readonly string[]\n}\n\n/**\n * The patterns, `/` for the index, `[name]` for one segment and a final\n * `[...name]` for one or more - and what was left out of the list, and why,\n * for the build to say.\n */\nexport function edgeShells(\n results: readonly { type: string; component: string }[],\n routes: readonly ShellRoute[],\n): { shells: string[]; left: string[] } {\n const byComponent = new Map(routes.map((route) => [route.component, route]))\n const shells: string[] = []\n const left: string[] = []\n\n for (const component of new Set(results.filter((r) => r.type === 'shell').map((r) => r.component))) {\n const route = byComponent.get(component)\n\n if (!route) continue\n if (route.middleware?.length || route.hostMiddleware?.length) continue\n\n // Bound from the request's host, which a path pattern cannot say: the\n // same pattern would match every tenant's page on every domain.\n if (route.segments.some((segment) => segment.type === 'host')) continue\n\n const pattern = patternOf(route.segments)\n\n if (pattern === null) continue\n\n if (new TextEncoder().encode(pattern).length > MAX_SHELL_BYTES) {\n left.push(`${pattern.slice(0, 60)}… is over ${MAX_SHELL_BYTES} bytes`)\n continue\n }\n\n if (!shells.includes(pattern)) shells.push(pattern)\n }\n\n // Over the cap, the deepest are the ones not listed: an edge that does not\n // know a page is a PPR page sends it to the origin, which answers it\n // correctly, and the shallow pages are the ones most visited.\n if (shells.length > MAX_SHELLS) {\n shells.sort((a, b) => depth(a) - depth(b) || (a < b ? -1 : 1))\n left.push(`${shells.length - MAX_SHELLS} past the shallowest ${MAX_SHELLS}`)\n shells.length = MAX_SHELLS\n }\n\n shells.sort()\n\n return { shells, left }\n}\n\n/**\n * A route's segments as the edge matches them, or null when it cannot: a\n * catch-all anywhere but last, or a name that is itself bracketed - an\n * optional catch-all, `[[...name]]`, matches no url at all.\n */\nfunction patternOf(segments: readonly RouteSegment[]): string | null {\n const parts: string[] = []\n\n for (const [index, segment] of segments.entries()) {\n // Not a path an edge can match: the contract has no empty, `.` or `..`\n // segment, and a name with a slash would be two.\n if (['', '.', '..'].includes(segment.value) || segment.value.includes('/')) return null\n if (segment.type === 'static') parts.push(segment.value)\n else if (segment.value.includes('[') || segment.value.includes(']')) return null\n else if (segment.type === 'catchAll') {\n if (index !== segments.length - 1) return null\n parts.push(`[...${segment.value}]`)\n } else parts.push(`[${segment.value}]`)\n }\n\n return '/' + parts.join('/')\n}\n\nfunction depth(pattern: string): number {\n return pattern === '/' ? 0 : pattern.split('/').length - 1\n}\n\n/**\n * The pages an edge may answer whole: frozen, so the document is the same for\n * every visitor until the next build, with no guard of either kind and not\n * bound from the host - and listed as the paths the build rendered, since\n * `static` takes no patterns: a pattern would ask the edge for paths that\n * were never rendered. A frozen redirect is not here: it has no document.\n *\n * A path a listed shell's pattern also matches is left out, so no path is\n * two things to the edge.\n */\nexport function edgeStatic(\n results: readonly { type: string; component: string; url: string }[],\n routes: readonly ShellRoute[],\n shells: readonly string[],\n): { static: string[]; left: string[] } {\n const byComponent = new Map(routes.map((route) => [route.component, route]))\n const statics: string[] = []\n const left: string[] = []\n\n for (const result of results) {\n if (result.type !== 'frozen') continue\n\n const route = byComponent.get(result.component)\n\n if (!route) continue\n if (route.middleware?.length || route.hostMiddleware?.length) continue\n if (route.segments.some((segment) => segment.type === 'host')) continue\n\n const path = contractPath(result.url)\n\n if (path === null) continue\n\n if (new TextEncoder().encode(path).length > MAX_SHELL_BYTES) {\n left.push(`${path.slice(0, 60)}… is over ${MAX_SHELL_BYTES} bytes`)\n continue\n }\n\n if (shells.some((pattern) => matchesPattern(pattern, path))) continue\n if (!statics.includes(path)) statics.push(path)\n }\n\n if (statics.length > MAX_STATIC) {\n statics.sort((a, b) => depth(a) - depth(b) || (a < b ? -1 : 1))\n left.push(`${statics.length - MAX_STATIC} static past the shallowest ${MAX_STATIC}`)\n statics.length = MAX_STATIC\n }\n\n statics.sort()\n\n return { static: statics, left }\n}\n\n/**\n * Why pages an edge could otherwise have served were kept out: the outermost\n * middleware above each, with how many pages it guards. A root middleware -\n * the usual home of security headers - guards every page and empties both\n * lists, correctly, and an empty edge.json with no reason given reads as a\n * bug. So the build names the file.\n */\nexport function edgeGuarded(\n results: readonly { type: string; component: string }[],\n routes: readonly ShellRoute[],\n): string[] {\n const byComponent = new Map(routes.map((route) => [route.component, route]))\n const counts = new Map<string, number>()\n\n for (const result of results) {\n if (result.type !== 'frozen' && result.type !== 'shell') continue\n\n const route = byComponent.get(result.component)\n const guard = route?.middleware?.[0] ?? (route?.hostMiddleware?.length ? `the host's ${route.hostMiddleware[0]}` : null)\n\n if (guard) counts.set(guard, (counts.get(guard) ?? 0) + 1)\n }\n\n return [...counts].sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1)).map(([guard, n]) => `${guard} (${n})`)\n}\n\n/**\n * A rendered url as the contract lists it - absolute, decoded once, no query\n * - or null when it has no such form: an empty, `.` or `..` segment, or one\n * holding a `/` once decoded.\n */\nexport function contractPath(url: string): string | null {\n const path = url.split(/[?#]/)[0]\n\n if (!path.startsWith('/')) return null\n if (path === '/') return path\n\n const parts: string[] = []\n\n for (const raw of path.slice(1).split('/')) {\n let part: string\n\n try {\n part = decodeURIComponent(raw)\n } catch {\n return null\n }\n\n if (['', '.', '..'].includes(part) || part.includes('/') || /[?#]/.test(part)) return null\n parts.push(part)\n }\n\n return '/' + parts.join('/')\n}\n\n/** A marker pattern against a path: `[name]` is one segment, a final `[...name]` one or more. */\nexport function matchesPattern(pattern: string, path: string): boolean {\n const want = pattern.split('/')\n const have = path.split('/')\n\n for (const [i, part] of want.entries()) {\n if (i === want.length - 1 && /^\\[\\.\\.\\..+\\]$/.test(part)) {\n return have.length > i && have.slice(i).every(Boolean)\n }\n\n if (have[i] === undefined) return false\n if (/^\\[.+\\]$/.test(part) ? have[i] === '' : part !== have[i]) return false\n }\n\n return want.length === have.length\n}\n"]}
|
package/dist/headers.d.ts
CHANGED
|
@@ -123,19 +123,25 @@ export declare const REVALIDATE = "public, max-age=0, must-revalidate";
|
|
|
123
123
|
*/
|
|
124
124
|
export declare const PER_REQUEST = "private, max-age=0, must-revalidate";
|
|
125
125
|
/**
|
|
126
|
-
* The
|
|
126
|
+
* The edge contract: one marker, one page endpoint, one resume, for any edge
|
|
127
127
|
* and any framework that implements it. Vendor-neutral names on purpose: an
|
|
128
128
|
* edge speaks the contract, and there is no second way in that is rsc-kit's
|
|
129
|
-
* own. docs/
|
|
129
|
+
* own. docs/reference/edge-contract.mdx is the specification.
|
|
130
130
|
*/
|
|
131
|
-
export declare const
|
|
132
|
-
|
|
133
|
-
readonly
|
|
134
|
-
readonly
|
|
135
|
-
readonly
|
|
136
|
-
|
|
137
|
-
readonly
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
readonly build: 'PPR-Build';
|
|
131
|
+
export declare const EDGE: {
|
|
132
|
+
readonly prefix: '/_edge/';
|
|
133
|
+
readonly pagePath: '/_edge/page';
|
|
134
|
+
readonly resumePath: '/_edge/resume';
|
|
135
|
+
readonly tokenEnv: 'EDGE_TOKEN';
|
|
136
|
+
readonly token: 'Edge-Token';
|
|
137
|
+
readonly build: 'Edge-Build';
|
|
138
|
+
/** The marker's copy beside the stored pages, which the host reads. */
|
|
139
|
+
readonly storedMarker: '_edge.json';
|
|
141
140
|
};
|
|
141
|
+
/**
|
|
142
|
+
* The response headers an edge may send with a page it serves from its cache
|
|
143
|
+
* (the contract's `headers`), and so the only ones rscKit({ headers }) sets:
|
|
144
|
+
* one outside this list could not leave with a cached page, and the page would
|
|
145
|
+
* go out without it.
|
|
146
|
+
*/
|
|
147
|
+
export declare const EDGE_HEADERS: readonly ['Content-Language', 'Link', 'Cache-Control', 'X-Robots-Tag', 'Content-Security-Policy', 'Permissions-Policy', 'Referrer-Policy', 'Strict-Transport-Security', 'X-Frame-Options', 'X-Content-Type-Options', 'Cross-Origin-Opener-Policy', 'Cross-Origin-Embedder-Policy', 'Cross-Origin-Resource-Policy', 'X-DNS-Prefetch-Control'];
|
package/dist/headers.js
CHANGED
|
@@ -138,20 +138,41 @@ export const REVALIDATE = 'public, max-age=0, must-revalidate';
|
|
|
138
138
|
*/
|
|
139
139
|
export const PER_REQUEST = 'private, max-age=0, must-revalidate';
|
|
140
140
|
/**
|
|
141
|
-
* The
|
|
141
|
+
* The edge contract: one marker, one page endpoint, one resume, for any edge
|
|
142
142
|
* and any framework that implements it. Vendor-neutral names on purpose: an
|
|
143
143
|
* edge speaks the contract, and there is no second way in that is rsc-kit's
|
|
144
|
-
* own. docs/
|
|
144
|
+
* own. docs/reference/edge-contract.mdx is the specification.
|
|
145
145
|
*/
|
|
146
|
-
export const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
build: 'PPR-Build',
|
|
146
|
+
export const EDGE = {
|
|
147
|
+
prefix: '/_edge/',
|
|
148
|
+
pagePath: '/_edge/page',
|
|
149
|
+
resumePath: '/_edge/resume',
|
|
150
|
+
tokenEnv: 'EDGE_TOKEN',
|
|
151
|
+
token: 'Edge-Token',
|
|
152
|
+
build: 'Edge-Build',
|
|
153
|
+
/** The marker's copy beside the stored pages, which the host reads. */
|
|
154
|
+
storedMarker: '_edge.json',
|
|
156
155
|
};
|
|
156
|
+
/**
|
|
157
|
+
* The response headers an edge may send with a page it serves from its cache
|
|
158
|
+
* (the contract's `headers`), and so the only ones rscKit({ headers }) sets:
|
|
159
|
+
* one outside this list could not leave with a cached page, and the page would
|
|
160
|
+
* go out without it.
|
|
161
|
+
*/
|
|
162
|
+
export const EDGE_HEADERS = [
|
|
163
|
+
'Content-Language',
|
|
164
|
+
'Link',
|
|
165
|
+
'Cache-Control',
|
|
166
|
+
'X-Robots-Tag',
|
|
167
|
+
'Content-Security-Policy',
|
|
168
|
+
'Permissions-Policy',
|
|
169
|
+
'Referrer-Policy',
|
|
170
|
+
'Strict-Transport-Security',
|
|
171
|
+
'X-Frame-Options',
|
|
172
|
+
'X-Content-Type-Options',
|
|
173
|
+
'Cross-Origin-Opener-Policy',
|
|
174
|
+
'Cross-Origin-Embedder-Policy',
|
|
175
|
+
'Cross-Origin-Resource-Policy',
|
|
176
|
+
'X-DNS-Prefetch-Control',
|
|
177
|
+
];
|
|
157
178
|
//# sourceMappingURL=headers.js.map
|
package/dist/headers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headers.js","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,0EAA0E;AAC1E,sEAAsE;AACtE,wEAAwE;AACxE,2EAA2E;AAC3E,gCAAgC;AAChC,EAAE;AACF,gFAAgF;AAEhF,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB;;;OAGG;IACH,GAAG,EAAE,OAAO;IAEZ,iFAAiF;IACjF,QAAQ,EAAE,gBAAgB;IAE1B,kEAAkE;IAClE,YAAY,EAAE,qBAAqB;IAEnC,wCAAwC;IACxC,OAAO,EAAE,eAAe;IAExB;;;;OAIG;IACH,QAAQ,EAAE,iBAAiB;IAE3B,gFAAgF;IAChF,OAAO,EAAE,eAAe;IAExB,uDAAuD;IACvD,SAAS,EAAE,iBAAiB;IAE5B,gDAAgD;IAChD,OAAO,EAAE,eAAe;IAExB,uEAAuE;IACvE,UAAU,EAAE,kBAAkB;IAE9B,0CAA0C;IAC1C,MAAM,EAAE,cAAc;IAEtB;;;;;;;OAOG;IACH,QAAQ,EAAE,gBAAgB;IAE1B;;;;;;OAMG;IACH,WAAW,EAAE,oBAAoB;IAEjC;;;;;OAKG;IACH,UAAU,EAAE,cAAc;IAC1B;;;;;;;;;OASG;IACH;;;;;;;OAOG;IACH,KAAK,EAAE,aAAa;IAEpB,SAAS,EAAE,aAAa;IACxB;;;;OAIG;IACH,SAAS,EAAE,eAAe;IAC1B,+EAA+E;IAC/E,WAAW,EAAE,eAAe;IAC5B,8FAA8F;IAC9F,UAAU,EAAE,cAAc;CAClB,CAAA;AAEV,sBAAsB;AACtB,MAAM,CAAC,MAAM,WAAW,GAAG,iCAAiC,CAAA;AAE5D,2BAA2B;AAC3B,MAAM,CAAC,MAAM,SAAS,GAAG,0BAA0B,CAAA;AAEnD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,MAAM,CAAC,GAAG;IACV,MAAM,CAAC,QAAQ;IACf,MAAM,CAAC,UAAU;IACjB,MAAM,CAAC,SAAS;IAChB,MAAM,CAAC,OAAO;CACf,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAEZ;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,mBAAmB,CAAA;AAE7C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,oCAAoC,CAAA;AAE9D;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,qCAAqC,CAAA;AAEhE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"headers.js","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,0EAA0E;AAC1E,sEAAsE;AACtE,wEAAwE;AACxE,2EAA2E;AAC3E,gCAAgC;AAChC,EAAE;AACF,gFAAgF;AAEhF,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB;;;OAGG;IACH,GAAG,EAAE,OAAO;IAEZ,iFAAiF;IACjF,QAAQ,EAAE,gBAAgB;IAE1B,kEAAkE;IAClE,YAAY,EAAE,qBAAqB;IAEnC,wCAAwC;IACxC,OAAO,EAAE,eAAe;IAExB;;;;OAIG;IACH,QAAQ,EAAE,iBAAiB;IAE3B,gFAAgF;IAChF,OAAO,EAAE,eAAe;IAExB,uDAAuD;IACvD,SAAS,EAAE,iBAAiB;IAE5B,gDAAgD;IAChD,OAAO,EAAE,eAAe;IAExB,uEAAuE;IACvE,UAAU,EAAE,kBAAkB;IAE9B,0CAA0C;IAC1C,MAAM,EAAE,cAAc;IAEtB;;;;;;;OAOG;IACH,QAAQ,EAAE,gBAAgB;IAE1B;;;;;;OAMG;IACH,WAAW,EAAE,oBAAoB;IAEjC;;;;;OAKG;IACH,UAAU,EAAE,cAAc;IAC1B;;;;;;;;;OASG;IACH;;;;;;;OAOG;IACH,KAAK,EAAE,aAAa;IAEpB,SAAS,EAAE,aAAa;IACxB;;;;OAIG;IACH,SAAS,EAAE,eAAe;IAC1B,+EAA+E;IAC/E,WAAW,EAAE,eAAe;IAC5B,8FAA8F;IAC9F,UAAU,EAAE,cAAc;CAClB,CAAA;AAEV,sBAAsB;AACtB,MAAM,CAAC,MAAM,WAAW,GAAG,iCAAiC,CAAA;AAE5D,2BAA2B;AAC3B,MAAM,CAAC,MAAM,SAAS,GAAG,0BAA0B,CAAA;AAEnD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,MAAM,CAAC,GAAG;IACV,MAAM,CAAC,QAAQ;IACf,MAAM,CAAC,UAAU;IACjB,MAAM,CAAC,SAAS;IAChB,MAAM,CAAC,OAAO;CACf,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAEZ;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,mBAAmB,CAAA;AAE7C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,oCAAoC,CAAA;AAE9D;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,qCAAqC,CAAA;AAEhE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB,MAAM,EAAE,SAAS;IACjB,QAAQ,EAAE,aAAa;IACvB,UAAU,EAAE,eAAe;IAC3B,QAAQ,EAAE,YAAY;IACtB,KAAK,EAAE,YAAY;IACnB,KAAK,EAAE,YAAY;IACnB,uEAAuE;IACvE,YAAY,EAAE,YAAY;CAClB,CAAA;AAEV;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,kBAAkB;IAClB,MAAM;IACN,eAAe;IACf,cAAc;IACd,yBAAyB;IACzB,oBAAoB;IACpB,iBAAiB;IACjB,2BAA2B;IAC3B,iBAAiB;IACjB,wBAAwB;IACxB,4BAA4B;IAC5B,8BAA8B;IAC9B,8BAA8B;IAC9B,wBAAwB;CAChB,CAAA","sourcesContent":["// The wire protocol, as names rather than string literals.\n//\n// A host writing these out by hand is how they drift. Two of them are not\n// negotiable at all — the client sends them or asks for them at fixed\n// addresses, and a host that spells one differently fails silently: the\n// request falls through, the decoder never runs, and a button does nothing\n// with no error on either side.\n//\n// PROTOCOL.md describes what each one means. This is the same list, importable.\n\nexport const HEADER = {\n /**\n * Present on a request for a Flight payload rather than the document. One\n * url serves both, which is why every answer must Vary on it.\n */\n rsc: 'X-RSC',\n\n /** The layout chain the client has mounted, outermost first, comma-separated. */\n segments: 'X-RSC-Segments',\n\n /** The boundary the answer replaces. 0 means a whole document. */\n segmentDepth: 'X-RSC-Segment-Depth',\n\n /** The chain to send back next time. */\n layouts: 'X-RSC-Layouts',\n\n /**\n * On a payload request: this url's page said it does not exist, after the\n * shell went out, and the client is asking for the not-found page to put\n * where it was. RedirectBoundary sends it.\n */\n notFound: 'X-RSC-Not-Found',\n\n /** Identifies the build, so a client can notice it is talking to an old one. */\n version: 'X-RSC-Version',\n\n /** The slot an intercepted navigation is targeting. */\n intercept: 'X-RSC-Intercept',\n\n /** Where the navigation or action came from. */\n referer: 'X-RSC-Referer',\n\n /** One named region of the current page to render and return alone. */\n revalidate: 'X-RSC-Revalidate',\n\n /** The server reference being invoked. */\n action: 'X-RSC-Action',\n\n /**\n * Go here instead.\n *\n * Used for a redirect the client has to perform itself: a failed action, and\n * a navigation whose render redirected. Never a 3xx for those — `fetch`\n * follows one transparently, so the client would get the destination's HTML\n * where it expected a Flight payload and decode it as one.\n */\n redirect: 'X-RSC-Redirect',\n\n /**\n * The body's real type.\n *\n * The body itself goes out as application/octet-stream so a host that parses\n * multipart cannot consume it before the action does — reading Content-Type\n * instead leaves an upload undecodable.\n */\n contentType: 'X-RSC-Content-Type',\n\n /**\n * Where the browser posts server actions.\n *\n * Not a convention this can choose: the client runtime posts here\n * unconditionally.\n */\n actionPath: '/_rsc/action',\n /**\n * Where the browser reads queries.\n *\n * A GET, and that is the whole point of it being a separate address: a POST\n * is cacheable by nothing, so a read routed through the action endpoint can\n * never be kept by an HTTP cache, a CDN or a service worker. Only functions\n * declared with `query()` are reachable here — the endpoint checks the mark\n * before invoking anything, because otherwise this address would run any\n * registered action over GET.\n */\n /**\n * Sent on every read, and required by the query endpoint.\n *\n * A GET with no unusual header is a simple request, so any page could\n * trigger one with an <img> and the visitor's cookies would ride along.\n * This header is not CORS-safelisted, so the browser preflights it — the\n * same thing that makes a POST carrying X-RSC-Action safe.\n */\n query: 'X-RSC-Query',\n\n queryPath: '/_rsc/query',\n /**\n * On a refresh a change triggered: which names moved, to which versions\n * (`stock@5,orders@2`). A shared section renders once per such change, and\n * every tab asking with the same one gets that render.\n */\n changedBy: 'X-RSC-Changed',\n /** The stream a tab hears on when a name its page refreshes on has changed. */\n changesPath: '/_rsc/changes',\n /** Whether this renderer can serve: up, and its backend answering. GET only, never cached. */\n healthPath: '/_rsc/health',\n} as const\n\n/** Flight payload. */\nexport const FLIGHT_TYPE = 'text/x-component; charset=utf-8'\n\n/** A rendered document. */\nexport const HTML_TYPE = 'text/html; charset=utf-8'\n\n/**\n * Every request header that changes what a response body contains.\n *\n * One url answers with a whole document, a partial payload, a named region or\n * an interceptor depending on these — so a cache keyed on the url alone, or on\n * `X-RSC` alone, will hand one client another's answer. They are materially\n * different bodies, not variations in presentation.\n */\nexport const VARY_ON_RSC = [\n HEADER.rsc,\n HEADER.segments,\n HEADER.revalidate,\n HEADER.intercept,\n HEADER.referer,\n].join(', ')\n\n/**\n * What a response says about being stored, when it must not be.\n *\n * `Vary` alone is not enough on the platform this targets. Cloudflare ignores\n * `Vary` for everything except `Accept-Encoding`, and this package ships a\n * Workers story as a headline feature — so one url answering with a document,\n * a Flight payload, a segment and an interceptor, keyed only on the url, hands\n * one visitor another's body. Anything narrowed by a request header, and\n * anything behind a guard, says outright that it is not to be stored.\n */\nexport const PER_CLIENT = 'private, no-store'\n\n/**\n * And what a frozen, unguarded page says.\n *\n * Explicit rather than absent: with no directive at all, whether an\n * intermediary stores it comes down to that intermediary's heuristics, which\n * is not a decision this package should be leaving to chance in either\n * direction.\n */\nexport const REVALIDATE = 'public, max-age=0, must-revalidate'\n\n/**\n * And what a page rendered for one request says.\n *\n * It may have read the visitor's cookies, and nothing in the headers - which\n * leave before the render has finished - can know whether it did. So a shared\n * cache may not keep it; the visitor's own browser may, and must ask again.\n */\nexport const PER_REQUEST = 'private, max-age=0, must-revalidate'\n\n/**\n * The edge contract: one marker, one page endpoint, one resume, for any edge\n * and any framework that implements it. Vendor-neutral names on purpose: an\n * edge speaks the contract, and there is no second way in that is rsc-kit's\n * own. docs/reference/edge-contract.mdx is the specification.\n */\nexport const EDGE = {\n prefix: '/_edge/',\n pagePath: '/_edge/page',\n resumePath: '/_edge/resume',\n tokenEnv: 'EDGE_TOKEN',\n token: 'Edge-Token',\n build: 'Edge-Build',\n /** The marker's copy beside the stored pages, which the host reads. */\n storedMarker: '_edge.json',\n} as const\n\n/**\n * The response headers an edge may send with a page it serves from its cache\n * (the contract's `headers`), and so the only ones rscKit({ headers }) sets:\n * one outside this list could not leave with a cached page, and the page would\n * go out without it.\n */\nexport const EDGE_HEADERS = [\n 'Content-Language',\n 'Link',\n 'Cache-Control',\n 'X-Robots-Tag',\n 'Content-Security-Policy',\n 'Permissions-Policy',\n 'Referrer-Policy',\n 'Strict-Transport-Security',\n 'X-Frame-Options',\n 'X-Content-Type-Options',\n 'Cross-Origin-Opener-Policy',\n 'Cross-Origin-Embedder-Policy',\n 'Cross-Origin-Resource-Policy',\n 'X-DNS-Prefetch-Control',\n] as const\n"]}
|