@rangojs/router 0.26.1 → 0.26.2
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/bin/rango.js +1 -1
- package/dist/types/router/loader-resolution.d.ts +4 -1
- package/dist/types/router/types.d.ts +3 -1
- package/dist/vite/index.js +1 -1
- package/package.json +1 -1
- package/skills/loader/SKILL.md +9 -0
- package/skills/ppr/SKILL.md +4 -3
- package/skills/router-setup/SKILL.md +1 -1
- package/src/router/loader-resolution.ts +12 -2
- package/src/router/segment-resolution/fresh.ts +2 -0
- package/src/router/types.ts +2 -0
- package/src/router.ts +2 -0
package/dist/bin/rango.js
CHANGED
|
@@ -1644,7 +1644,7 @@ var init_package = __esm({
|
|
|
1644
1644
|
"package.json"() {
|
|
1645
1645
|
package_default = {
|
|
1646
1646
|
name: "@rangojs/router",
|
|
1647
|
-
version: "0.26.
|
|
1647
|
+
version: "0.26.2",
|
|
1648
1648
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
1649
1649
|
keywords: [
|
|
1650
1650
|
"react",
|
|
@@ -42,13 +42,16 @@ export type LoaderErrorCallback = (error: unknown, context: {
|
|
|
42
42
|
* This has a simplified signature for internal use - the caller (typically
|
|
43
43
|
* wrapLoaderPromise in router.ts) is responsible for bridging to the full
|
|
44
44
|
* OnErrorCallback with complete request context (request, url, env, etc.).
|
|
45
|
+
* @param useDefaultErrorBoundary - An `ssr: false` loader on a document or
|
|
46
|
+
* capture render: an error with no declared boundary gets
|
|
47
|
+
* `DefaultErrorFallback` and a 500 (it would otherwise reject the tree).
|
|
45
48
|
*/
|
|
46
49
|
export declare function wrapLoaderWithErrorHandling<T>(promise: Promise<T>, entry: EntryData, segmentId: string, pathname: string, findNearestErrorBoundary: (entry: EntryData | null) => ReactNode | ErrorBoundaryHandler | null, createErrorInfo: (error: unknown, segmentId: string, segmentType: ErrorInfo["segmentType"]) => ErrorInfo, onError?: LoaderErrorCallback, notFoundDeps?: {
|
|
47
50
|
findNearestNotFoundBoundary: (entry: EntryData | null) => ReactNode | NotFoundBoundaryHandler | null;
|
|
48
51
|
notFoundComponent?: ReactNode | ((props: {
|
|
49
52
|
pathname: string;
|
|
50
53
|
}) => ReactNode);
|
|
51
|
-
}): Promise<LoaderDataResult<T>>;
|
|
54
|
+
}, useDefaultErrorBoundary?: boolean): Promise<LoaderDataResult<T>>;
|
|
52
55
|
/**
|
|
53
56
|
* Set up the use() method on handler context to access loaders and handles.
|
|
54
57
|
*
|
|
@@ -25,7 +25,9 @@ export interface SegmentResolutionDeps<TEnv = any> {
|
|
|
25
25
|
env?: TEnv;
|
|
26
26
|
isPartial?: boolean;
|
|
27
27
|
requestStartTime?: number;
|
|
28
|
-
}
|
|
28
|
+
},
|
|
29
|
+
/** An `ssr: false` loader on a document or capture render. */
|
|
30
|
+
useDefaultErrorBoundary?: boolean) => Promise<LoaderDataResult<T>>;
|
|
29
31
|
trackHandler: <T>(promise: Promise<T>, errorContext?: {
|
|
30
32
|
segmentId?: string;
|
|
31
33
|
segmentType?: string;
|
package/dist/vite/index.js
CHANGED
|
@@ -7077,7 +7077,7 @@ function resolveVersionsFrom(table, routerId2) {
|
|
|
7077
7077
|
// package.json
|
|
7078
7078
|
var package_default = {
|
|
7079
7079
|
name: "@rangojs/router",
|
|
7080
|
-
version: "0.26.
|
|
7080
|
+
version: "0.26.2",
|
|
7081
7081
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
7082
7082
|
keywords: [
|
|
7083
7083
|
"react",
|
package/package.json
CHANGED
package/skills/loader/SKILL.md
CHANGED
|
@@ -995,6 +995,15 @@ runs, so a `useLoader` in a layout above every Suspense boundary is safe. On a
|
|
|
995
995
|
during the shell capture refuses the capture (warned once per key): that URL
|
|
996
996
|
gets no shell, and its renders deliver the signal as above.
|
|
997
997
|
|
|
998
|
+
A flagged loader that throws (anything but `notFound()` or `redirect()`) is
|
|
999
|
+
resolved the same way: its route's `errorBoundary()` renders at the owning
|
|
1000
|
+
segment, else `createRouter({ defaultErrorBoundary })`, else the router's
|
|
1001
|
+
default error boundary (the "Internal Server Error" page) with a 500, as a
|
|
1002
|
+
handler that throws does; a declared `errorBoundary()` or
|
|
1003
|
+
`defaultErrorBoundary` answers 200 here. Layouts above it stay. On a `ppr` shell HIT whose
|
|
1004
|
+
run throws, the baked content shows first and the boundary replaces it at
|
|
1005
|
+
hydration, after React reports a hydration mismatch.
|
|
1006
|
+
|
|
998
1007
|
Session/auth gates belong in middleware (they are request-shaped, not
|
|
999
1008
|
data-shaped, and middleware CAN emit a real pre-stream 302). Data-dependent
|
|
1000
1009
|
"this slug moved / does not exist" belongs in the loader.
|
package/skills/ppr/SKILL.md
CHANGED
|
@@ -926,9 +926,10 @@ by a navigation alone carries no pins, and its replay runs these loaders fresh.
|
|
|
926
926
|
fill the holes — and the recorded baked paths are overlaid onto the fresh
|
|
927
927
|
result, so the payload matches the prelude while the nested promises stream
|
|
928
928
|
fresh. A fresh rejection skips the overlay and goes to the loader's error
|
|
929
|
-
boundary
|
|
930
|
-
|
|
931
|
-
|
|
929
|
+
boundary (the route's `errorBoundary()`, else the router's default one): on
|
|
930
|
+
a document HIT the payload then carries the error where the HTML has the
|
|
931
|
+
baked value, and React renders that part again on the client (one
|
|
932
|
+
hydration mismatch, the one known residual of a HIT). Give it its own cache,
|
|
932
933
|
`loader(Def, { ssr: false }, () => [cache({ ttl })])`, so that run reads
|
|
933
934
|
through the loader cache.
|
|
934
935
|
|
|
@@ -130,7 +130,7 @@ interface RangoOptions<TEnv> {
|
|
|
130
130
|
id?: string; // Router id; namespaces build output and route maps. Auto-generated.
|
|
131
131
|
|
|
132
132
|
// --- Fallbacks and errors ---
|
|
133
|
-
defaultErrorBoundary?: ReactNode | ErrorBoundaryHandler; // When no errorBoundary() matches
|
|
133
|
+
defaultErrorBoundary?: ReactNode | ErrorBoundaryHandler; // When no errorBoundary() matches; unset: the built-in "Internal Server Error" page
|
|
134
134
|
defaultNotFoundBoundary?: ReactNode | NotFoundBoundaryHandler; // When no notFoundBoundary() matches
|
|
135
135
|
notFound?: ReactNode | ((props: { pathname: string }) => ReactNode); // 404 page
|
|
136
136
|
onError?: OnErrorCallback<TEnv>; // Logging/reporting hook; cannot change the response
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
} from "../server/handle-store.js";
|
|
56
56
|
import { getFetchableLoader } from "../server/fetchable-loader-store.js";
|
|
57
57
|
import { _getRequestContext } from "../server/request-context.js";
|
|
58
|
+
import { DefaultErrorFallback } from "../default-error-boundary.js";
|
|
58
59
|
import { defineRewrittenFrom } from "../server/rewrite-trail.js";
|
|
59
60
|
import {
|
|
60
61
|
firstUnitCandidate,
|
|
@@ -154,6 +155,9 @@ function pushHandleValue(
|
|
|
154
155
|
* This has a simplified signature for internal use - the caller (typically
|
|
155
156
|
* wrapLoaderPromise in router.ts) is responsible for bridging to the full
|
|
156
157
|
* OnErrorCallback with complete request context (request, url, env, etc.).
|
|
158
|
+
* @param useDefaultErrorBoundary - An `ssr: false` loader on a document or
|
|
159
|
+
* capture render: an error with no declared boundary gets
|
|
160
|
+
* `DefaultErrorFallback` and a 500 (it would otherwise reject the tree).
|
|
157
161
|
*/
|
|
158
162
|
export function wrapLoaderWithErrorHandling<T>(
|
|
159
163
|
promise: Promise<T>,
|
|
@@ -177,13 +181,15 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
177
181
|
| ReactNode
|
|
178
182
|
| ((props: { pathname: string }) => ReactNode);
|
|
179
183
|
},
|
|
184
|
+
useDefaultErrorBoundary?: boolean,
|
|
180
185
|
): Promise<LoaderDataResult<T>> {
|
|
181
186
|
// Extract the trailing token from segmentId (format: "<shortCode>D<i>.<loaderId>").
|
|
182
187
|
// The token is the loader's $$id (hash#export in prod, pathfrag#export in dev),
|
|
183
188
|
// not a clean display name.
|
|
184
189
|
const loaderName = segmentId.split(".").pop() || "unknown";
|
|
185
190
|
// Captured now: the rejection can settle outside the request's ALS frame.
|
|
186
|
-
const
|
|
191
|
+
const requestContext = _getRequestContext();
|
|
192
|
+
const renderErrors = requestContext?._renderErrors;
|
|
187
193
|
|
|
188
194
|
return Promise.resolve(promise)
|
|
189
195
|
.then(
|
|
@@ -286,7 +292,9 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
286
292
|
renderErrors?.push(error);
|
|
287
293
|
|
|
288
294
|
// Find nearest error boundary
|
|
289
|
-
const
|
|
295
|
+
const declared = findNearestErrorBoundary(entry);
|
|
296
|
+
const fallback =
|
|
297
|
+
declared ?? (useDefaultErrorBoundary ? DefaultErrorFallback : null);
|
|
290
298
|
|
|
291
299
|
// Create error info
|
|
292
300
|
const errorInfo = createErrorInfo(error, segmentId, "loader");
|
|
@@ -341,6 +349,8 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
341
349
|
segmentId,
|
|
342
350
|
message: errorInfo.message,
|
|
343
351
|
});
|
|
352
|
+
// The default boundary answers 500, as in catchSegmentError.
|
|
353
|
+
if (!declared) requestContext?._setStatus(500);
|
|
344
354
|
|
|
345
355
|
return {
|
|
346
356
|
__loaderResult: true,
|
|
@@ -184,6 +184,7 @@ export async function resolveLoaders<TEnv>(
|
|
|
184
184
|
segmentId,
|
|
185
185
|
ctx.pathname,
|
|
186
186
|
errorContext,
|
|
187
|
+
loaderEntry.awaitBeforeFlush,
|
|
187
188
|
),
|
|
188
189
|
belongsToRoute,
|
|
189
190
|
};
|
|
@@ -235,6 +236,7 @@ export async function resolveLoaders<TEnv>(
|
|
|
235
236
|
segmentId,
|
|
236
237
|
ctx.pathname,
|
|
237
238
|
errorContext,
|
|
239
|
+
loaderEntry.awaitBeforeFlush,
|
|
238
240
|
);
|
|
239
241
|
return { wrapped, segmentId };
|
|
240
242
|
});
|
package/src/router/types.ts
CHANGED
|
@@ -49,6 +49,8 @@ export interface SegmentResolutionDeps<TEnv = any> {
|
|
|
49
49
|
isPartial?: boolean;
|
|
50
50
|
requestStartTime?: number;
|
|
51
51
|
},
|
|
52
|
+
/** An `ssr: false` loader on a document or capture render. */
|
|
53
|
+
useDefaultErrorBoundary?: boolean,
|
|
52
54
|
) => Promise<LoaderDataResult<T>>;
|
|
53
55
|
trackHandler: <T>(
|
|
54
56
|
promise: Promise<T>,
|
package/src/router.ts
CHANGED
|
@@ -531,6 +531,7 @@ export function createRouter<TEnv = any>(
|
|
|
531
531
|
isPartial?: boolean;
|
|
532
532
|
requestStartTime?: number;
|
|
533
533
|
},
|
|
534
|
+
useDefaultErrorBoundary?: boolean,
|
|
534
535
|
): Promise<LoaderDataResult<T>> {
|
|
535
536
|
const loaderStart = telemetrySink ? performance.now() : 0;
|
|
536
537
|
const loaderRequestId = telemetrySink
|
|
@@ -600,6 +601,7 @@ export function createRouter<TEnv = any>(
|
|
|
600
601
|
findNearestNotFoundBoundary,
|
|
601
602
|
notFoundComponent: notFound,
|
|
602
603
|
},
|
|
604
|
+
useDefaultErrorBoundary,
|
|
603
605
|
);
|
|
604
606
|
|
|
605
607
|
// Emit loader.end after the promise settles (fire-and-forget)
|