@rsc-kit/core 0.30.5 → 0.30.7
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/headers.d.ts +6 -0
- package/dist/headers.js +6 -0
- package/dist/headers.js.map +1 -1
- package/dist/host.d.ts +6 -0
- package/dist/host.js +30 -5
- package/dist/host.js.map +1 -1
- package/dist/js/RedirectBoundary.d.ts +9 -0
- package/dist/js/RedirectBoundary.js +60 -9
- package/dist/js/RedirectBoundary.js.map +1 -1
- package/dist/js/RouteErrorBoundary.js +6 -2
- package/dist/js/RouteErrorBoundary.js.map +1 -1
- package/dist/js/createViteRscApp.js +7 -1
- package/dist/js/createViteRscApp.js.map +1 -1
- package/dist/js/navigate.d.ts +6 -0
- package/dist/js/navigate.js +11 -3
- package/dist/js/navigate.js.map +1 -1
- package/dist/js/router.d.ts +1 -0
- package/dist/js/router.js.map +1 -1
- package/dist/manifest.d.ts +10 -0
- package/dist/manifest.js.map +1 -1
- package/dist/notFound.d.ts +2 -2
- package/dist/notFound.js +5 -4
- package/dist/notFound.js.map +1 -1
- package/dist/vite.js +76 -20
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
package/dist/notFound.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notFound.js","sourceRoot":"","sources":["../src/notFound.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,uDAAuD;AACvD,EAAE;AACF,yDAAyD;AACzD,uDAAuD;AACvD,4BAA4B;AAC5B,UAAU;AACV,MAAM;AACN,EAAE;AACF,0EAA0E;AAC1E,8EAA8E;AAC9E,2EAA2E;AAC3E,8EAA8E;AAC9E,oEAAoE;AACpE,EAAE;AACF,yEAAyE;AACzE,kDAAkD;AAClD,EAAE;AACF,2EAA2E;AAC3E,2EAA2E;AAC3E,uCAAuC;AACvC,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E
|
|
1
|
+
{"version":3,"file":"notFound.js","sourceRoot":"","sources":["../src/notFound.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,uDAAuD;AACvD,EAAE;AACF,yDAAyD;AACzD,uDAAuD;AACvD,4BAA4B;AAC5B,UAAU;AACV,MAAM;AACN,EAAE;AACF,0EAA0E;AAC1E,8EAA8E;AAC9E,2EAA2E;AAC3E,8EAA8E;AAC9E,oEAAoE;AACpE,EAAE;AACF,yEAAyE;AACzE,kDAAkD;AAClD,EAAE;AACF,2EAA2E;AAC3E,2EAA2E;AAC3E,uCAAuC;AACvC,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E,2EAA2E;AAC3E,8EAA8E;AAC9E,6EAA6E;AAC7E,wEAAwE;AAExE;;;;;;;;;GASG;AACH,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAA;AAEzD;;;;;;;;;;;;;GAaG;AACH,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAA;AAExD,SAAS,MAAM;IACb,MAAM,KAAK,GAAI,UAAsC,CAAC,KAAK,CAE9C,CAAA;IAEb,MAAM,KAAK,GAAG,KAAK,EAAE,QAAQ,EAAE,CAAA;IAE/B,IAAI,KAAK;QAAE,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAA;AAClC,CAAC;AAED,oEAAoE;AACpE,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAO,GAAG,WAAW;QAC/B,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAC3B;QAAC,IAA2C,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;QAE1D,wEAAwE;QACxE,uEAAuE;QACvE,gDAAgD;QAChD,MAAM,EAAE,CAAA;IACV,CAAC;CACF;AAED,4EAA4E;AAC5E,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAK,KAAiC,CAAC,IAAI,CAAC,KAAK,IAAI,CACjG,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,GAAG,eAAe,CAAA;AAE9B,kEAAkE;AAClE,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;AAChD,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,gBAAgB,CAAC,MAAe;IAC9C,OAAO,MAAM,KAAK,MAAM,CAAA;AAC1B,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,QAAQ;IACtB,MAAM,IAAI,cAAc,EAAE,CAAA;AAC5B,CAAC","sourcesContent":["// Saying a url does not name a page, from inside a render.\n//\n// import { notFound } from '@rsc-kit/core/not-found'\n//\n// export default async function PostPage({ params }) {\n// const post = await findPost((await params).slug)\n// if (!post) notFound()\n// ...\n// }\n//\n// Until this existed, \"no such page\" could only be decided by the router,\n// before anything rendered. But most of the time it is not knowable then: the\n// route matched, the url is well-formed, and whether the thing exists is a\n// question only the page can ask. The alternative was an error boundary and a\n// 500, which tells a crawler the page is broken rather than absent.\n//\n// Delivery follows the same two windows a redirect has, and for the same\n// reason — headers flush when the shell resolves:\n//\n// Before the shell — nothing is written yet, so the host answers 404 and\n// renders not-found.tsx. This is where a params check lands, and where a\n// lookup at the top of a page lands.\n//\n// After the shell — the status line is gone. React carries an error digest\n// for every server error, so the mark travels there. The browser's boundary\n// takes it for the page's answer, not a failure, and asks the server for\n// not-found.tsx to put where the page was - layouts kept, the url as it is.\n// The page ends with a noindex tag, and a crawler - which is answered with\n// the finished document, see crawler.ts - gets a real 404 regardless.\n\n/**\n * The mark that says an error is a missing page.\n *\n * A property rather than `instanceof`, for the reason this project keeps\n * meeting: the app's components are bundled apart from the engine that renders\n * them, so each side evaluates its own copy of this module and its own copy of\n * the class. `instanceof` is simply false across that seam — the page would\n * render as an unhandled error, and the only clue would be a 500 where a 404\n * was meant.\n */\nconst MARK = Symbol.for('@rsc-kit/core.not-found-signal')\n\n/**\n * Where a render records that it wants the not-found page.\n *\n * The same scope a redirect records in, reached through the well-known symbol\n * rather than by importing it: this module is evaluated in the app bundle, the\n * engine and the browser, and only one of those should be pulling in the\n * redirect machinery.\n *\n * Recording is necessary because the throw alone does not survive. React\n * catches what a component threw and re-raises its own error, whose message is\n * stripped in production — so the host testing the caught value would work in\n * development and answer 500 in production, which is the worst of the two\n * possible bugs.\n */\nconst SCOPE = Symbol.for('@rsc-kit/core.redirect-scope')\n\nfunction record(): void {\n const scope = (globalThis as Record<symbol, unknown>)[SCOPE] as\n | { getStore(): { notFound?: boolean } | undefined }\n | undefined\n\n const store = scope?.getStore()\n\n if (store) store.notFound = true\n}\n\n/** Thrown to stop rendering a page whose subject does not exist. */\nexport class NotFoundSignal extends Error {\n constructor(message = 'Not found') {\n super(message)\n this.name = 'NotFoundSignal'\n ;(this as unknown as Record<symbol, boolean>)[MARK] = true\n\n // In the constructor rather than in notFound(), so every way of raising\n // one is recorded — a params schema refusing is not routed through the\n // public function and must answer the same way.\n record()\n }\n}\n\n/** Whether this is a missing page, whichever copy of the class threw it. */\nexport function isNotFoundSignal(error: unknown): error is NotFoundSignal {\n return (\n typeof error === 'object' && error !== null && (error as Record<symbol, unknown>)[MARK] === true\n )\n}\n\n/**\n * The prefix that carries a missing page inside React's error digest.\n *\n * React replaces a server error's message with an opaque digest in production,\n * and the digest is the one part it is guaranteed to transmit. Kept distinct\n * from the redirect prefix so neither is ever read as the other.\n */\nconst PREFIX = 'RSC_NOT_FOUND'\n\n/** The digest for a missing page, or null for any other error. */\nexport function notFoundDigest(error: unknown): string | null {\n return isNotFoundSignal(error) ? PREFIX : null\n}\n\n/** Whether a digest describes a missing page. */\nexport function isNotFoundDigest(digest: unknown): boolean {\n return digest === PREFIX\n}\n\n/**\n * Answer this url with the not-found page.\n *\n * Never returns: it throws, which stops the component that called it. Do not\n * wrap a call in `try`/`catch` without rethrowing what you do not recognise —\n * swallowing this turns a missing page into a blank region.\n *\n * Called above every Suspense boundary, the response is a real 404 carrying\n * not-found.tsx. Called inside one, the shell is already on the wire, so the\n * status stays 200 and the page ends with a noindex tag; the browser then\n * shows not-found.tsx where the page was. A search engine is answered with the finished page, so it\n * gets a real 404 wherever this was called.\n */\nexport function notFound(): never {\n throw new NotFoundSignal()\n}\n"]}
|
package/dist/vite.js
CHANGED
|
@@ -703,6 +703,7 @@ function routeManifest() {
|
|
|
703
703
|
layouts: ancestors(name, "layout").map((n) => n),
|
|
704
704
|
loadings: ancestors(name, "loading").map((n) => n),
|
|
705
705
|
errors: ancestors(name, "error"),
|
|
706
|
+
notFounds: ancestors(name, "not-found"),
|
|
706
707
|
middleware: ancestors(name, "middleware").map((n) => n),
|
|
707
708
|
slots,
|
|
708
709
|
sections: names.filter((n) => SECTION_FILE.test(n + ".tsx") && dirOf(n) === dirOf(name)),
|
|
@@ -2953,16 +2954,22 @@ export function instrumentationImport(instrumentation) {
|
|
|
2953
2954
|
}
|
|
2954
2955
|
function generateEntryRsc(fallbackOrigin = "") {
|
|
2955
2956
|
const instrumentation = instrumentationFile();
|
|
2956
|
-
//
|
|
2957
|
-
//
|
|
2958
|
-
//
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2957
|
+
// Every 404 page the app has, and the layouts each renders inside. Computed
|
|
2958
|
+
// here rather than looked up at runtime: not-found is not a route, so the
|
|
2959
|
+
// manifest has no entry to read its chain from. Which one answers is decided
|
|
2960
|
+
// per request: the nearest above the route that said notFound(), and for a
|
|
2961
|
+
// url no route owns, the one at the root.
|
|
2962
|
+
const notFoundFiles = [...components.keys()].filter((name) => name.endsWith("/not-found") && !name.includes("/@"));
|
|
2963
|
+
const notFoundLayouts = {};
|
|
2964
|
+
for (const file of notFoundFiles) {
|
|
2965
|
+
notFoundLayouts[file] = [...components.keys()]
|
|
2962
2966
|
.filter((name) => name.endsWith("/layout") &&
|
|
2963
|
-
|
|
2964
|
-
.sort((a, b) => a.length - b.length)
|
|
2965
|
-
|
|
2967
|
+
file.startsWith(name.slice(0, -"layout".length)))
|
|
2968
|
+
.sort((a, b) => a.length - b.length);
|
|
2969
|
+
}
|
|
2970
|
+
// The root is `app/not-found`: two segments. A nested one alone does not
|
|
2971
|
+
// answer a url nothing owns - it belongs to the routes beneath it.
|
|
2972
|
+
const rootNotFound = notFoundFiles.find((name) => name.split("/").length === 2) ?? null;
|
|
2966
2973
|
const imports = [];
|
|
2967
2974
|
const mapEntries = [];
|
|
2968
2975
|
const metaEntries = [];
|
|
@@ -3035,14 +3042,14 @@ import { notFoundDigest, isNotFoundSignal } from ${JSON.stringify(join(packageDi
|
|
|
3035
3042
|
import { noteRequestRead, notePageProps, urlOf } from ${JSON.stringify(join(packageDir, "request"))}
|
|
3036
3043
|
import { redirectDigest } from ${JSON.stringify(join(packageDir, "redirectDigest"))}
|
|
3037
3044
|
import { cancelledByConsumer } from ${JSON.stringify(join(packageDir, "js/fallbackReport"))}
|
|
3038
|
-
import { createRscHandler, pageSaidNotFound, queryAndParams } from ${JSON.stringify(join(packageDir, "host"))}
|
|
3045
|
+
import { createRscHandler, notFoundRouteOf, pageSaidNotFound, queryAndParams } from ${JSON.stringify(join(packageDir, "host"))}
|
|
3039
3046
|
import { httpHostCalls } from ${JSON.stringify(join(packageDir, "hostCalls"))}
|
|
3040
3047
|
import { backendVersions, installBackendVersionSource } from ${JSON.stringify(join(packageDir, "changed"))}
|
|
3041
3048
|
import { RefreshOn } from ${JSON.stringify(join(packageDir, "js/refreshOn"))}
|
|
3042
3049
|
import { prerenderedBeside } from ${JSON.stringify(join(packageDir, "files"))}
|
|
3043
3050
|
import { renderToReadableStream, decodeReply, decodeAction, decodeFormState, loadServerAction } from '@vitejs/plugin-rsc/rsc'
|
|
3044
3051
|
import { isQuery, queryCacheControl, isQueryValidationError, queryRefusal } from ${JSON.stringify(join(packageDir, "query"))}
|
|
3045
|
-
import { isActionValidationError, isClientBuilt } from ${JSON.stringify(join(packageDir, "action"))}
|
|
3052
|
+
import { isActionValidationError, isActionRefusal, isClientBuilt } from ${JSON.stringify(join(packageDir, "action"))}
|
|
3046
3053
|
import { forgetCached } from ${JSON.stringify(join(packageDir, "cache"))}
|
|
3047
3054
|
import { noteFallback as noteCaughtRead } from ${JSON.stringify(join(packageDir, "request"))}
|
|
3048
3055
|
import { isOutdatedOptimizedDep, outdatedDepResponse } from ${JSON.stringify(join(packageDir, "devReload"))}
|
|
@@ -3699,6 +3706,21 @@ function checkedSearchParams(
|
|
|
3699
3706
|
|
|
3700
3707
|
let errorChains: Record<string, string[]> | null = null
|
|
3701
3708
|
|
|
3709
|
+
let notFoundChains: Record<string, string[]> | null = null
|
|
3710
|
+
|
|
3711
|
+
/** The not-found.tsx files above a component, outermost first. */
|
|
3712
|
+
function notFoundChain(component: string): string[] {
|
|
3713
|
+
if (!notFoundChains) {
|
|
3714
|
+
notFoundChains = {}
|
|
3715
|
+
|
|
3716
|
+
for (const route of manifest().routes as { component: string; notFounds?: string[] }[]) {
|
|
3717
|
+
if (route.notFounds?.length) notFoundChains[route.component] = route.notFounds
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
|
|
3721
|
+
return notFoundChains[component] ?? []
|
|
3722
|
+
}
|
|
3723
|
+
|
|
3702
3724
|
/** The error.tsx files above a component, outermost first. */
|
|
3703
3725
|
function errorChain(component: string): string[] {
|
|
3704
3726
|
if (!errorChains) {
|
|
@@ -4984,14 +5006,31 @@ export async function handleAction(
|
|
|
4984
5006
|
// returned { validationErrors } that <Form> reads. Left thrown, React
|
|
4985
5007
|
// serialises the rejection opaquely — production strips the message — and the
|
|
4986
5008
|
// fields it named never reach the browser.
|
|
5009
|
+
//
|
|
5010
|
+
// A refusal is the same: the stub the build writes for a backend action is a
|
|
5011
|
+
// plain function, and when the backend turns the call down (Refuse(503, ...),
|
|
5012
|
+
// abort(429, ...), Rsc::refuse) it throws a refusal whose message was written
|
|
5013
|
+
// for the person asking. Its message is the result's serverError, which is
|
|
5014
|
+
// where a form's formError reads. Its data is not sent: there is no
|
|
5015
|
+
// .refusal(schema) on a plain function to check it against.
|
|
4987
5016
|
let result: unknown
|
|
4988
5017
|
|
|
4989
5018
|
try {
|
|
4990
5019
|
result = await (action as (...a: unknown[]) => unknown)(...args)
|
|
4991
5020
|
} catch (error) {
|
|
4992
|
-
if (
|
|
5021
|
+
if (isActionRefusal(error)) {
|
|
5022
|
+
if (error.data !== undefined) {
|
|
5023
|
+
console.error(
|
|
5024
|
+
'[rsc-kit] ' + actionId + ' refused with data, and is a plain function with no .refusal(schema) to check it - the message was sent, the data was not: ' + error.message,
|
|
5025
|
+
)
|
|
5026
|
+
}
|
|
4993
5027
|
|
|
4994
|
-
|
|
5028
|
+
result = { serverError: error.message }
|
|
5029
|
+
} else if (isActionValidationError(error)) {
|
|
5030
|
+
result = { validationErrors: error.errors }
|
|
5031
|
+
} else {
|
|
5032
|
+
throw error
|
|
5033
|
+
}
|
|
4995
5034
|
}
|
|
4996
5035
|
|
|
4997
5036
|
// Read after the action has run: what it invalidated is only known once its
|
|
@@ -5702,21 +5741,36 @@ function wantsPage(request: Request): boolean {
|
|
|
5702
5741
|
return (request.headers.get('Accept') ?? '').includes('text/html')
|
|
5703
5742
|
}
|
|
5704
5743
|
|
|
5744
|
+
/**
|
|
5745
|
+
* The not-found.tsx that answers: the nearest above the route that said
|
|
5746
|
+
* notFound(), else - for a url no route owns - the one at the root, else none.
|
|
5747
|
+
*/
|
|
5748
|
+
function nearestNotFound(request: Request): string | null {
|
|
5749
|
+
const route = notFoundRouteOf(request)
|
|
5750
|
+
const chain = route === undefined ? [] : notFoundChain(route)
|
|
5751
|
+
|
|
5752
|
+
return chain[chain.length - 1] ?? ${JSON.stringify(rootNotFound)}
|
|
5753
|
+
}
|
|
5754
|
+
|
|
5755
|
+
const NOT_FOUND_LAYOUTS: Record<string, string[]> = ${JSON.stringify(notFoundLayouts)}
|
|
5756
|
+
|
|
5705
5757
|
async function renderNotFound(request: Request): Promise<Response> {
|
|
5706
|
-
|
|
5758
|
+
const target = nearestNotFound(request)
|
|
5759
|
+
${notFoundFiles.length
|
|
5707
5760
|
? `
|
|
5708
5761
|
// A navigation, not a document: answer with the not-found tree as a
|
|
5709
5762
|
// payload, at the depth the client already holds, so the router renders
|
|
5710
5763
|
// it in place - the layout stays, the url changes, nothing reloads. The
|
|
5711
5764
|
// status is still 404; a payload is a payload whatever it says.
|
|
5712
|
-
if (request.headers.get('X-RSC')) {
|
|
5765
|
+
if (target && request.headers.get('X-RSC')) {
|
|
5713
5766
|
try {
|
|
5714
|
-
const
|
|
5767
|
+
const layouts = NOT_FOUND_LAYOUTS[target] ?? []
|
|
5768
|
+
const chain = layouts
|
|
5715
5769
|
const from = sharedDepth(request.headers.get('X-RSC-Segments'), chain)
|
|
5716
5770
|
const { rscPayload } = await handleRscPayload(
|
|
5717
|
-
|
|
5771
|
+
target,
|
|
5718
5772
|
{},
|
|
5719
|
-
|
|
5773
|
+
layouts.map((component) => ({ component, props: {} })),
|
|
5720
5774
|
[],
|
|
5721
5775
|
{},
|
|
5722
5776
|
from,
|
|
@@ -5739,10 +5793,12 @@ async function renderNotFound(request: Request): Promise<Response> {
|
|
|
5739
5793
|
}
|
|
5740
5794
|
|
|
5741
5795
|
try {
|
|
5796
|
+
if (!target) throw new Error('no not-found.tsx')
|
|
5797
|
+
|
|
5742
5798
|
const { htmlStream } = await handleRscHtmlStream(
|
|
5743
|
-
|
|
5799
|
+
target,
|
|
5744
5800
|
{},
|
|
5745
|
-
|
|
5801
|
+
(NOT_FOUND_LAYOUTS[target] ?? []).map((component) => ({ component, props: {} })),
|
|
5746
5802
|
[],
|
|
5747
5803
|
{},
|
|
5748
5804
|
{},
|