@rangojs/router 0.0.0-experimental.124 → 0.0.0-experimental.126
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/README.md +6 -4
- package/dist/bin/rango.js +3 -4
- package/dist/vite/index.js +315 -68
- package/package.json +19 -18
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/hooks/SKILL.md +2 -2
- package/skills/route/SKILL.md +6 -0
- package/skills/server-actions/SKILL.md +25 -1
- package/skills/testing/SKILL.md +17 -17
- package/skills/testing/cache-prerender.md +29 -3
- package/skills/testing/flight.md +13 -10
- package/skills/testing/render-handler.md +3 -0
- package/skills/testing/server-tree.md +1 -1
- package/skills/testing/setup.md +1 -1
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +10 -0
- package/src/browser/event-controller.ts +1 -83
- package/src/browser/navigation-store-handle.ts +3 -4
- package/src/browser/navigation-store.ts +0 -39
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/partial-update.ts +23 -84
- package/src/browser/prefetch/cache.ts +6 -45
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +2 -23
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +2 -1
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -45
- package/src/browser/react/location-state-shared.ts +0 -13
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-router.ts +2 -1
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/rsc-router.tsx +10 -3
- package/src/browser/server-action-bridge.ts +51 -3
- package/src/browser/types.ts +23 -5
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/index.ts +8 -9
- package/src/build/route-trie.ts +46 -11
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/router-processing.ts +0 -8
- package/src/cache/cache-policy.ts +0 -54
- package/src/cache/cache-runtime.ts +48 -24
- package/src/cache/cache-scope.ts +0 -27
- package/src/cache/cache-tag.ts +0 -37
- package/src/cache/cf/cf-cache-store.ts +72 -45
- package/src/cache/cf/index.ts +0 -24
- package/src/cache/document-cache.ts +10 -36
- package/src/cache/handle-snapshot.ts +0 -40
- package/src/cache/index.ts +0 -27
- package/src/cache/memory-segment-store.ts +0 -52
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/types.ts +0 -98
- package/src/client.rsc.tsx +4 -22
- package/src/client.tsx +19 -32
- package/src/context-var.ts +12 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/handle.ts +2 -12
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +6 -0
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +0 -16
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +0 -4
- package/src/index.rsc.ts +27 -2
- package/src/index.ts +7 -0
- package/src/internal-debug.ts +2 -4
- package/src/loader.rsc.ts +4 -15
- package/src/loader.ts +3 -9
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +23 -30
- package/src/prerender.ts +34 -0
- package/src/redirect-origin.ts +100 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +1 -44
- package/src/route-definition/dsl-helpers.ts +7 -19
- package/src/route-definition/helpers-types.ts +3 -3
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-map-builder.ts +0 -16
- package/src/router/content-negotiation.ts +0 -13
- package/src/router/error-handling.ts +12 -16
- package/src/router/find-match.ts +4 -31
- package/src/router/intercept-resolution.ts +10 -1
- package/src/router/lazy-includes.ts +1 -57
- package/src/router/loader-resolution.ts +25 -23
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +1 -25
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +0 -43
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +96 -179
- package/src/router/match-middleware/cache-store.ts +0 -31
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +1 -52
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +77 -60
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +5 -56
- package/src/router/prerender-match.ts +56 -51
- package/src/router/request-classification.ts +1 -38
- package/src/router/revalidation.ts +14 -62
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +10 -0
- package/src/router/segment-resolution/fresh.ts +25 -57
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +35 -23
- package/src/router/segment-resolution/revalidation.ts +188 -283
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +0 -22
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +66 -45
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +8 -11
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +20 -4
- package/src/rsc/helpers.ts +71 -3
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/origin-guard.ts +9 -15
- package/src/rsc/progressive-enhancement.ts +10 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-route-handler.ts +23 -18
- package/src/rsc/rsc-rendering.ts +2 -7
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +34 -29
- package/src/rsc/types.ts +6 -3
- package/src/search-params.ts +0 -16
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +79 -88
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +29 -92
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +2 -27
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +1 -24
- package/src/testing/dispatch.ts +43 -6
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/flight-matchers.ts +0 -13
- package/src/testing/flight-normalize.ts +3 -30
- package/src/testing/flight.ts +46 -48
- package/src/testing/generated-routes.ts +1 -41
- package/src/testing/index.ts +1 -21
- package/src/testing/internal/context.ts +3 -45
- package/src/testing/internal/seed-vars.ts +0 -26
- package/src/testing/render-handler.ts +31 -61
- package/src/testing/render-route.tsx +75 -103
- package/src/testing/run-loader.ts +0 -96
- package/src/testing/run-middleware.ts +0 -26
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/error-types.ts +25 -89
- package/src/types/global-namespace.ts +4 -14
- package/src/types/handler-context.ts +28 -9
- package/src/types/index.ts +0 -10
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +0 -13
- package/src/urls/include-helper.ts +0 -4
- package/src/urls/index.ts +0 -6
- package/src/urls/path-helper-types.ts +2 -2
- package/src/urls/path-helper.ts +0 -54
- package/src/urls/urls-function.ts +0 -13
- package/src/use-loader.tsx +0 -186
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/discover-routers.ts +28 -18
- package/src/vite/discovery/prerender-collection.ts +2 -4
- package/src/vite/discovery/state.ts +5 -0
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/plugin-types.ts +35 -9
- package/src/vite/plugins/cjs-to-esm.ts +0 -11
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +0 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +0 -38
- package/src/vite/plugins/expose-ids/handler-transform.ts +0 -15
- package/src/vite/plugins/expose-ids/loader-transform.ts +0 -15
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +10 -0
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/version-injector.ts +0 -20
- package/src/vite/plugins/version-plugin.ts +1 -49
- package/src/vite/plugins/virtual-entries.ts +0 -15
- package/src/vite/rango.ts +2 -108
- package/src/vite/router-discovery.ts +9 -1
- package/src/vite/utils/ast-handler-extract.ts +0 -16
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +0 -4
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -35
- package/src/vite/utils/shared-utils.ts +3 -35
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -12,15 +12,18 @@
|
|
|
12
12
|
* - `**.example.com` - any depth subdomain
|
|
13
13
|
* - `admin.*` - admin subdomain of any apex
|
|
14
14
|
* - `example.com/admin` - specific domain with path prefix
|
|
15
|
+
*
|
|
16
|
+
* Apex vs subdomain is classified purely by dot-part COUNT (apex == exactly 2
|
|
17
|
+
* parts) — there is no Public Suffix List. A registrable domain under a
|
|
18
|
+
* multi-label public suffix (example.co.uk, shop.com.au) has 3+ parts and is
|
|
19
|
+
* therefore treated as a SUBDOMAIN, not an apex: `.`/`*` will NOT match it and
|
|
20
|
+
* `*.` WILL. If registrable-domain accuracy matters for a host-router consumer,
|
|
21
|
+
* supply an explicit apex/host hint rather than relying on the part count.
|
|
15
22
|
*/
|
|
16
23
|
|
|
17
24
|
import { InvalidPatternError } from "./errors.js";
|
|
18
25
|
|
|
19
|
-
/**
|
|
20
|
-
* Normalize a pattern by removing trailing slashes from paths
|
|
21
|
-
*/
|
|
22
26
|
export function normalizePattern(pattern: string): string {
|
|
23
|
-
// If pattern has a path component, remove trailing slash
|
|
24
27
|
const slashIndex = pattern.indexOf("/");
|
|
25
28
|
if (slashIndex !== -1) {
|
|
26
29
|
const domain = pattern.slice(0, slashIndex);
|
|
@@ -30,9 +33,6 @@ export function normalizePattern(pattern: string): string {
|
|
|
30
33
|
return pattern;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
/**
|
|
34
|
-
* Parse hostname and path from request URL
|
|
35
|
-
*/
|
|
36
36
|
export function parseRequest(request: Request): {
|
|
37
37
|
hostname: string;
|
|
38
38
|
pathname: string;
|
|
@@ -46,26 +46,14 @@ export function parseRequest(request: Request): {
|
|
|
46
46
|
return { hostname, pathname, parts };
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/**
|
|
50
|
-
* Count subdomain levels (0 for apex, 1+ for subdomains)
|
|
51
|
-
*/
|
|
52
49
|
function getSubdomainLevel(parts: string[]): number {
|
|
53
|
-
// Apex domain has 2 parts (example.com)
|
|
54
|
-
// Single subdomain has 3 parts (www.example.com)
|
|
55
|
-
// Multi-level has 4+ parts (a.b.example.com)
|
|
56
50
|
return Math.max(0, parts.length - 2);
|
|
57
51
|
}
|
|
58
52
|
|
|
59
|
-
/**
|
|
60
|
-
* Check if hostname is an apex domain (no subdomains)
|
|
61
|
-
*/
|
|
62
53
|
function isApexDomain(parts: string[]): boolean {
|
|
63
54
|
return parts.length === 2;
|
|
64
55
|
}
|
|
65
56
|
|
|
66
|
-
/**
|
|
67
|
-
* Match a single pattern against hostname and path
|
|
68
|
-
*/
|
|
69
57
|
export function matchPattern(
|
|
70
58
|
pattern: string,
|
|
71
59
|
hostname: string,
|
|
@@ -74,19 +62,16 @@ export function matchPattern(
|
|
|
74
62
|
): boolean {
|
|
75
63
|
const normalized = normalizePattern(pattern);
|
|
76
64
|
|
|
77
|
-
// Check if pattern has path component
|
|
78
65
|
const slashIndex = normalized.indexOf("/");
|
|
79
66
|
const hasPath = slashIndex !== -1;
|
|
80
67
|
const domainPattern = hasPath ? normalized.slice(0, slashIndex) : normalized;
|
|
81
68
|
const pathPattern = hasPath ? normalized.slice(slashIndex) : null;
|
|
82
69
|
|
|
83
|
-
// First match domain
|
|
84
70
|
const domainMatch = matchDomainPattern(domainPattern, hostname, parts);
|
|
85
71
|
if (!domainMatch) {
|
|
86
72
|
return false;
|
|
87
73
|
}
|
|
88
74
|
|
|
89
|
-
// Then match path (prefix match)
|
|
90
75
|
if (pathPattern) {
|
|
91
76
|
return pathname === pathPattern || pathname.startsWith(pathPattern + "/");
|
|
92
77
|
}
|
|
@@ -94,81 +79,62 @@ export function matchPattern(
|
|
|
94
79
|
return true;
|
|
95
80
|
}
|
|
96
81
|
|
|
97
|
-
/**
|
|
98
|
-
* Match domain pattern against hostname
|
|
99
|
-
*/
|
|
100
82
|
function matchDomainPattern(
|
|
101
83
|
pattern: string,
|
|
102
84
|
hostname: string,
|
|
103
85
|
parts: string[],
|
|
104
86
|
): boolean {
|
|
105
|
-
// Exact match
|
|
106
87
|
if (pattern === hostname) {
|
|
107
88
|
return true;
|
|
108
89
|
}
|
|
109
90
|
|
|
110
|
-
// `.` or `*` - any apex domain
|
|
111
91
|
if (pattern === "." || pattern === "*") {
|
|
112
92
|
return isApexDomain(parts);
|
|
113
93
|
}
|
|
114
94
|
|
|
115
|
-
// `**` - any domain (apex + all subdomains)
|
|
116
95
|
if (pattern === "**") {
|
|
117
96
|
return true;
|
|
118
97
|
}
|
|
119
98
|
|
|
120
|
-
// `*.` - any single-level subdomain
|
|
121
99
|
if (pattern === "*.") {
|
|
122
100
|
return getSubdomainLevel(parts) === 1;
|
|
123
101
|
}
|
|
124
102
|
|
|
125
|
-
// `**.` - any multi-level subdomain (2+ levels)
|
|
126
103
|
if (pattern === "**.") {
|
|
127
104
|
return getSubdomainLevel(parts) >= 2;
|
|
128
105
|
}
|
|
129
106
|
|
|
130
|
-
// `*.tld` - any apex domain with specific TLD (e.g., *.com)
|
|
131
107
|
if (pattern.startsWith("*.") && !pattern.includes(".", 2)) {
|
|
132
108
|
const tld = pattern.slice(2);
|
|
133
109
|
return isApexDomain(parts) && hostname.endsWith("." + tld);
|
|
134
110
|
}
|
|
135
111
|
|
|
136
|
-
// `*.example.com` - single subdomain of specific domain
|
|
137
112
|
if (pattern.startsWith("*.")) {
|
|
138
113
|
const baseDomain = pattern.slice(2);
|
|
139
114
|
if (hostname.endsWith("." + baseDomain)) {
|
|
140
|
-
// Count parts: if pattern is *.example.com (3 parts),
|
|
141
|
-
// hostname should have exactly 4 parts (www.example.com)
|
|
142
115
|
const patternParts = baseDomain.split(".");
|
|
143
116
|
return parts.length === patternParts.length + 1;
|
|
144
117
|
}
|
|
145
118
|
return false;
|
|
146
119
|
}
|
|
147
120
|
|
|
148
|
-
// `**.example.com` - any depth subdomain of specific domain
|
|
149
121
|
if (pattern.startsWith("**.")) {
|
|
150
122
|
const baseDomain = pattern.slice(3);
|
|
151
123
|
if (hostname.endsWith("." + baseDomain)) {
|
|
152
124
|
const patternParts = baseDomain.split(".");
|
|
153
|
-
// Must have more parts than the base domain (i.e., has subdomains)
|
|
154
125
|
return parts.length > patternParts.length;
|
|
155
126
|
}
|
|
156
127
|
return false;
|
|
157
128
|
}
|
|
158
129
|
|
|
159
|
-
// `subdomain.*` - specific subdomain of any apex domain
|
|
160
|
-
// e.g., admin.* matches admin.example.com, admin.google.com
|
|
161
130
|
if (pattern.endsWith(".*")) {
|
|
162
131
|
const subdomain = pattern.slice(0, -2);
|
|
163
|
-
// Must be single-level subdomain (3 parts total)
|
|
164
132
|
if (parts.length === 3 && parts[0] === subdomain) {
|
|
165
133
|
return true;
|
|
166
134
|
}
|
|
167
135
|
return false;
|
|
168
136
|
}
|
|
169
137
|
|
|
170
|
-
// `subdomain.**` - specific subdomain of any domain (including multi-level)
|
|
171
|
-
// e.g., admin.** matches admin.example.com, admin.sub.example.com
|
|
172
138
|
if (pattern.endsWith(".**")) {
|
|
173
139
|
const subdomain = pattern.slice(0, -3);
|
|
174
140
|
if (parts.length >= 3 && parts[0] === subdomain) {
|
|
@@ -177,11 +143,8 @@ function matchDomainPattern(
|
|
|
177
143
|
return false;
|
|
178
144
|
}
|
|
179
145
|
|
|
180
|
-
// `subdomain.` - specific subdomain of any apex domain (no wildcard)
|
|
181
|
-
// e.g., admin. matches admin.example.com, admin.google.com
|
|
182
146
|
if (pattern.endsWith(".") && !pattern.includes("*")) {
|
|
183
147
|
const subdomain = pattern.slice(0, -1);
|
|
184
|
-
// Must be exactly 3 parts (subdomain.domain.tld)
|
|
185
148
|
if (parts.length === 3 && parts[0] === subdomain) {
|
|
186
149
|
return true;
|
|
187
150
|
}
|
|
@@ -191,9 +154,6 @@ function matchDomainPattern(
|
|
|
191
154
|
return false;
|
|
192
155
|
}
|
|
193
156
|
|
|
194
|
-
/**
|
|
195
|
-
* Validate pattern format
|
|
196
|
-
*/
|
|
197
157
|
export function validatePattern(pattern: string): void {
|
|
198
158
|
if (!pattern || typeof pattern !== "string") {
|
|
199
159
|
throw new InvalidPatternError(
|
|
@@ -203,12 +163,9 @@ export function validatePattern(pattern: string): void {
|
|
|
203
163
|
);
|
|
204
164
|
}
|
|
205
165
|
|
|
206
|
-
// Check for invalid characters (spaces, etc.)
|
|
207
166
|
if (/\s/.test(pattern)) {
|
|
208
167
|
throw new InvalidPatternError(pattern, "contains whitespace", {
|
|
209
168
|
cause: { pattern },
|
|
210
169
|
});
|
|
211
170
|
}
|
|
212
|
-
|
|
213
|
-
// Additional validation can be added here
|
|
214
171
|
}
|
package/src/host/router.ts
CHANGED
|
@@ -32,27 +32,16 @@ import {
|
|
|
32
32
|
InvalidHandlerError,
|
|
33
33
|
} from "./errors.js";
|
|
34
34
|
|
|
35
|
-
/**
|
|
36
|
-
* Registry entry for a host router instance.
|
|
37
|
-
* Stores references to the live routes array and fallback, so the discovery
|
|
38
|
-
* plugin can iterate handlers registered after createHostRouter() returns.
|
|
39
|
-
*/
|
|
40
35
|
export interface HostRouterRegistryEntry {
|
|
41
36
|
routes: RouteEntry[];
|
|
42
37
|
fallback: RouteEntry | null;
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
/**
|
|
46
|
-
* Global registry for host routers (parallel to RouterRegistry for RSC routers).
|
|
47
|
-
* Populated by createHostRouter() so the build-time discovery plugin can find
|
|
48
|
-
* host routers and resolve their lazy handlers to trigger sub-app createRouter() calls.
|
|
49
|
-
*/
|
|
50
40
|
export const HostRouterRegistry: Map<string, HostRouterRegistryEntry> =
|
|
51
41
|
new Map();
|
|
52
42
|
|
|
53
43
|
let hostRouterAutoId = 0;
|
|
54
44
|
|
|
55
|
-
/** Whether a value is thenable (a Promise or Promise-like). */
|
|
56
45
|
function isThenable(value: unknown): value is PromiseLike<unknown> {
|
|
57
46
|
return (
|
|
58
47
|
value !== null &&
|
|
@@ -61,12 +50,6 @@ function isThenable(value: unknown): value is PromiseLike<unknown> {
|
|
|
61
50
|
);
|
|
62
51
|
}
|
|
63
52
|
|
|
64
|
-
/**
|
|
65
|
-
* Whether a resolved value looks like a module namespace from a lazy import -
|
|
66
|
-
* an object with a `default` export that is a function (a Handler) or a host
|
|
67
|
-
* router (an object with `match`). Used to detect a `.map(() => import(...))`
|
|
68
|
-
* misuse: an inline handler should return a Response, not a module.
|
|
69
|
-
*/
|
|
70
53
|
function looksLikeLazyModule(value: unknown): boolean {
|
|
71
54
|
if (value === null || typeof value !== "object" || !("default" in value)) {
|
|
72
55
|
return false;
|
|
@@ -80,9 +63,6 @@ function looksLikeLazyModule(value: unknown): boolean {
|
|
|
80
63
|
);
|
|
81
64
|
}
|
|
82
65
|
|
|
83
|
-
/**
|
|
84
|
-
* Create a host router
|
|
85
|
-
*/
|
|
86
66
|
export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
87
67
|
const routes: RouteEntry[] = [];
|
|
88
68
|
const globalMiddleware: Middleware[] = [];
|
|
@@ -96,9 +76,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
96
76
|
}
|
|
97
77
|
}
|
|
98
78
|
|
|
99
|
-
/**
|
|
100
|
-
* Create a route builder for chaining
|
|
101
|
-
*/
|
|
102
79
|
function createRouteBuilder(
|
|
103
80
|
patterns: string[],
|
|
104
81
|
isFallback = false,
|
|
@@ -147,9 +124,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
147
124
|
};
|
|
148
125
|
}
|
|
149
126
|
|
|
150
|
-
/**
|
|
151
|
-
* Find matching route for hostname and path
|
|
152
|
-
*/
|
|
153
127
|
function findMatchingRoute(
|
|
154
128
|
hostname: string,
|
|
155
129
|
pathname: string,
|
|
@@ -168,9 +142,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
168
142
|
return null;
|
|
169
143
|
}
|
|
170
144
|
|
|
171
|
-
/**
|
|
172
|
-
* Execute middleware chain
|
|
173
|
-
*/
|
|
174
145
|
async function executeMiddleware(
|
|
175
146
|
middleware: Middleware[],
|
|
176
147
|
request: Request,
|
|
@@ -189,8 +160,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
189
160
|
return finalHandler();
|
|
190
161
|
}
|
|
191
162
|
|
|
192
|
-
// Guard against double next() calls — a second call would
|
|
193
|
-
// re-enter the downstream chain and run handlers/side-effects twice.
|
|
194
163
|
let nextCalled = false;
|
|
195
164
|
const guardedNext = (): Promise<Response> => {
|
|
196
165
|
if (nextCalled) {
|
|
@@ -208,15 +177,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
208
177
|
return next();
|
|
209
178
|
}
|
|
210
179
|
|
|
211
|
-
/**
|
|
212
|
-
* Execute a route entry, branching on its declared kind:
|
|
213
|
-
* - "lazy": await the loader, then delegate to the default export
|
|
214
|
-
* (a nested HostRouter via `.match`, or a request Handler directly).
|
|
215
|
-
* - "handler": call the inline handler with the request. A `.map()` handler
|
|
216
|
-
* that resolves to a module namespace (`{ default }`) is almost certainly
|
|
217
|
-
* a misused lazy import, so it is rejected with a clear message rather
|
|
218
|
-
* than silently returning a module object as the response.
|
|
219
|
-
*/
|
|
220
180
|
async function executeHandler(
|
|
221
181
|
entry: RouteEntry,
|
|
222
182
|
request: Request,
|
|
@@ -236,8 +196,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
236
196
|
|
|
237
197
|
const result = (handler as Handler)(request, input);
|
|
238
198
|
|
|
239
|
-
// Inline handlers may be async; await to obtain the Response and to run the
|
|
240
|
-
// misuse guard below.
|
|
241
199
|
if (isThenable(result)) {
|
|
242
200
|
const awaited = await result;
|
|
243
201
|
if (looksLikeLazyModule(awaited)) {
|
|
@@ -251,10 +209,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
251
209
|
return result;
|
|
252
210
|
}
|
|
253
211
|
|
|
254
|
-
/**
|
|
255
|
-
* Resolve a `.lazy()` mount: invoke the zero-arg loader, then dispatch to the
|
|
256
|
-
* module's default export.
|
|
257
|
-
*/
|
|
258
212
|
async function executeLazyMount(
|
|
259
213
|
loader: LazyHandler,
|
|
260
214
|
request: Request,
|
|
@@ -266,7 +220,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
266
220
|
const defaultExport = (module as { default: Handler | HostRouter })
|
|
267
221
|
.default;
|
|
268
222
|
|
|
269
|
-
// Default export is a nested host router
|
|
270
223
|
if (
|
|
271
224
|
typeof defaultExport === "object" &&
|
|
272
225
|
defaultExport !== null &&
|
|
@@ -275,7 +228,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
275
228
|
return (defaultExport as HostRouter).match(request, input);
|
|
276
229
|
}
|
|
277
230
|
|
|
278
|
-
// Otherwise treat the default export as a request handler
|
|
279
231
|
return (defaultExport as Handler)(request, input);
|
|
280
232
|
}
|
|
281
233
|
|
|
@@ -288,14 +240,10 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
288
240
|
});
|
|
289
241
|
}
|
|
290
242
|
|
|
291
|
-
/**
|
|
292
|
-
* Router instance
|
|
293
|
-
*/
|
|
294
243
|
const router: HostRouter = {
|
|
295
244
|
host(patterns: HostPattern): HostRouteBuilder {
|
|
296
245
|
const patternsArray = Array.isArray(patterns) ? patterns : [patterns];
|
|
297
246
|
|
|
298
|
-
// Validate and normalize patterns
|
|
299
247
|
const normalized = patternsArray.map((p) => {
|
|
300
248
|
validatePattern(p);
|
|
301
249
|
return normalizePattern(p);
|
|
@@ -314,9 +262,8 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
314
262
|
return createRouteBuilder([], true);
|
|
315
263
|
},
|
|
316
264
|
|
|
317
|
-
test(hostname: string): HostMatchResult | null {
|
|
265
|
+
test(hostname: string, pathname = "/"): HostMatchResult | null {
|
|
318
266
|
const parts = hostname.split(".");
|
|
319
|
-
const pathname = "/";
|
|
320
267
|
|
|
321
268
|
for (const route of routes) {
|
|
322
269
|
for (const pattern of route.patterns) {
|
|
@@ -342,14 +289,11 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
342
289
|
let effectiveHostname: string;
|
|
343
290
|
|
|
344
291
|
try {
|
|
345
|
-
// Handle cookie override (may throw HostRouterError)
|
|
346
292
|
effectiveHostname = handleCookieOverride(request, hostOverride, input);
|
|
347
293
|
} catch (error) {
|
|
348
|
-
// If it's a HostRouterError from cookie override
|
|
349
294
|
if (error instanceof HostRouterError) {
|
|
350
295
|
log(`Cookie override error: ${error.message}`);
|
|
351
296
|
|
|
352
|
-
// If fallback exists, use it
|
|
353
297
|
if (fallbackRoute) {
|
|
354
298
|
const fallbackInput = { ...input, error };
|
|
355
299
|
const allMiddleware = [
|
|
@@ -365,7 +309,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
365
309
|
);
|
|
366
310
|
}
|
|
367
311
|
|
|
368
|
-
// Otherwise return error response with cookie deletion
|
|
369
312
|
if (hostOverride) {
|
|
370
313
|
return createCookieErrorResponse(
|
|
371
314
|
hostOverride.cookieName,
|
|
@@ -374,7 +317,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
374
317
|
}
|
|
375
318
|
}
|
|
376
319
|
|
|
377
|
-
// Re-throw non-HostRouterErrors
|
|
378
320
|
throw error;
|
|
379
321
|
}
|
|
380
322
|
|
|
@@ -384,7 +326,6 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
384
326
|
log(`Cookie override: ${effectiveHostname}`);
|
|
385
327
|
}
|
|
386
328
|
|
|
387
|
-
// Find matching route
|
|
388
329
|
const matchedRoute = findMatchingRoute(effectiveHostname, pathname);
|
|
389
330
|
|
|
390
331
|
if (!matchedRoute) {
|
|
@@ -397,19 +338,14 @@ export function createHostRouter(options: HostRouterOptions = {}): HostRouter {
|
|
|
397
338
|
});
|
|
398
339
|
}
|
|
399
340
|
|
|
400
|
-
// Combine global and route-specific middleware
|
|
401
341
|
const allMiddleware = [...globalMiddleware, ...matchedRoute.middleware];
|
|
402
342
|
|
|
403
|
-
// Execute middleware chain and handler
|
|
404
343
|
return executeMiddleware(allMiddleware, request, input, () =>
|
|
405
344
|
executeHandler(matchedRoute, request, input),
|
|
406
345
|
);
|
|
407
346
|
},
|
|
408
347
|
};
|
|
409
348
|
|
|
410
|
-
// Register in the global HostRouterRegistry for build-time discovery.
|
|
411
|
-
// The routes array and fallbackRoute ref are live - they reflect routes
|
|
412
|
-
// added via .host().map()/.lazy() after this point.
|
|
413
349
|
const registryId = `host-router-${hostRouterAutoId++}`;
|
|
414
350
|
HostRouterRegistry.set(registryId, {
|
|
415
351
|
get routes() {
|
package/src/host/testing.ts
CHANGED
|
@@ -14,18 +14,6 @@ export interface CreateTestRequestOptions {
|
|
|
14
14
|
headers?: Record<string, string>;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
/**
|
|
18
|
-
* Create a test request with specific host and cookies
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```ts
|
|
22
|
-
* const request = createTestRequest({
|
|
23
|
-
* host: 'admin.example.com',
|
|
24
|
-
* path: '/dashboard',
|
|
25
|
-
* cookies: { 'x-requested-host': 'api.example.com' }
|
|
26
|
-
* });
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
17
|
export function createTestRequest(options: CreateTestRequestOptions): Request {
|
|
30
18
|
const {
|
|
31
19
|
host,
|
|
@@ -38,7 +26,6 @@ export function createTestRequest(options: CreateTestRequestOptions): Request {
|
|
|
38
26
|
const url = `http://${host}${path}`;
|
|
39
27
|
const requestHeaders = new Headers(headers);
|
|
40
28
|
|
|
41
|
-
// Add cookies if provided
|
|
42
29
|
if (Object.keys(cookies).length > 0) {
|
|
43
30
|
const cookieString = Object.entries(cookies)
|
|
44
31
|
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
|
@@ -52,9 +39,6 @@ export function createTestRequest(options: CreateTestRequestOptions): Request {
|
|
|
52
39
|
});
|
|
53
40
|
}
|
|
54
41
|
|
|
55
|
-
// Try each pattern (a single pattern or any in an array) against the already
|
|
56
|
-
// parsed host + path. Shared by testPattern and matchesHost so the
|
|
57
|
-
// normalize-and-loop lives once.
|
|
58
42
|
function matchPatterns(
|
|
59
43
|
pattern: string | string[],
|
|
60
44
|
hostname: string,
|
package/src/host/types.ts
CHANGED
|
@@ -110,9 +110,13 @@ export interface HostRouter {
|
|
|
110
110
|
fallback(): HostRouteBuilder;
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
* Test which handler would match a hostname
|
|
113
|
+
* Test which handler would match a hostname (and optional pathname).
|
|
114
|
+
*
|
|
115
|
+
* `pathname` defaults to `"/"`. Pass it to probe path-prefixed patterns
|
|
116
|
+
* such as `host(["example.com/admin"])`, which only match when the request
|
|
117
|
+
* path is under the prefix.
|
|
114
118
|
*/
|
|
115
|
-
test(hostname: string): HostMatchResult | null;
|
|
119
|
+
test(hostname: string, pathname?: string): HostMatchResult | null;
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
/**
|
package/src/href-client.ts
CHANGED
|
@@ -298,13 +298,9 @@ declare global {
|
|
|
298
298
|
*/
|
|
299
299
|
export function href<T extends ValidPaths>(path: T, mount?: string): string {
|
|
300
300
|
if (mount && mount !== "/") {
|
|
301
|
-
// Strip trailing slash from mount to avoid double-slash when joining
|
|
302
301
|
const normalizedMount = mount.endsWith("/") ? mount.slice(0, -1) : mount;
|
|
303
302
|
return normalizedMount + path;
|
|
304
303
|
}
|
|
305
|
-
// ValidPaths is built from template literals so T does extend string at
|
|
306
|
-
// runtime, but the inference can fail past a certain route-union complexity
|
|
307
|
-
// and TypeScript reports T as not assignable to string.
|
|
308
304
|
return path as string;
|
|
309
305
|
}
|
|
310
306
|
|
package/src/index.rsc.ts
CHANGED
|
@@ -75,6 +75,13 @@ export type {
|
|
|
75
75
|
ResolveStreamingContext,
|
|
76
76
|
} from "./router.js";
|
|
77
77
|
|
|
78
|
+
// Origin-check callback types (referenced by the RangoOptions.originCheck JSDoc)
|
|
79
|
+
export type {
|
|
80
|
+
OriginCheckConfig,
|
|
81
|
+
OriginCheckContext,
|
|
82
|
+
OriginCheckPhase,
|
|
83
|
+
} from "./rsc/origin-guard.js";
|
|
84
|
+
|
|
78
85
|
// Server-side createLoader and redirect
|
|
79
86
|
export {
|
|
80
87
|
createLoader,
|
|
@@ -107,6 +114,13 @@ export type {
|
|
|
107
114
|
|
|
108
115
|
// Handle API
|
|
109
116
|
export { createHandle, isHandle, type Handle } from "./handle.js";
|
|
117
|
+
export {
|
|
118
|
+
DEFAULT_DEFER_TIMEOUT_MS,
|
|
119
|
+
type DeferOptions,
|
|
120
|
+
type DeferredHandleEntry,
|
|
121
|
+
type HandlePush,
|
|
122
|
+
type HandlePushFn,
|
|
123
|
+
} from "./defer.js";
|
|
110
124
|
|
|
111
125
|
// Context variable API (typed ctx.set/ctx.get tokens)
|
|
112
126
|
export { createVar, type ContextVar } from "./context-var.js";
|
|
@@ -124,10 +138,15 @@ export {
|
|
|
124
138
|
type BuildContext,
|
|
125
139
|
type StaticBuildContext,
|
|
126
140
|
type GetParamsContext,
|
|
141
|
+
type PrerenderPassthroughResult,
|
|
127
142
|
} from "./prerender.js";
|
|
128
143
|
|
|
129
144
|
// Static handler API
|
|
130
|
-
export {
|
|
145
|
+
export {
|
|
146
|
+
Static,
|
|
147
|
+
type StaticHandlerDefinition,
|
|
148
|
+
type StaticHandlerOptions,
|
|
149
|
+
} from "./static-handler.js";
|
|
131
150
|
|
|
132
151
|
// Django-style URL patterns (RSC/server context)
|
|
133
152
|
export {
|
|
@@ -202,7 +221,13 @@ export { updateTag, revalidateTag } from "./cache/tag-invalidation.js";
|
|
|
202
221
|
export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
|
|
203
222
|
|
|
204
223
|
// Middleware context types
|
|
205
|
-
export type {
|
|
224
|
+
export type {
|
|
225
|
+
MiddlewareContext,
|
|
226
|
+
CookieOptions,
|
|
227
|
+
// The function type of a middleware. Public so the documented "extract the
|
|
228
|
+
// middleware and unit-test it with runMiddleware" pattern has a nameable type.
|
|
229
|
+
MiddlewareFn,
|
|
230
|
+
} from "./router/middleware.js";
|
|
206
231
|
|
|
207
232
|
// Reverse type utilities for type-safe URL generation (Django-style URL reversal)
|
|
208
233
|
export type {
|
package/src/index.ts
CHANGED
|
@@ -140,6 +140,13 @@ export function redirect(): never {
|
|
|
140
140
|
|
|
141
141
|
// Handle API (universal - works on both server and client)
|
|
142
142
|
export { createHandle, isHandle, type Handle } from "./handle.js";
|
|
143
|
+
export {
|
|
144
|
+
DEFAULT_DEFER_TIMEOUT_MS,
|
|
145
|
+
type DeferOptions,
|
|
146
|
+
type DeferredHandleEntry,
|
|
147
|
+
type HandlePush,
|
|
148
|
+
type HandlePushFn,
|
|
149
|
+
} from "./defer.js";
|
|
143
150
|
|
|
144
151
|
// Context variable API (typed ctx.set/ctx.get tokens)
|
|
145
152
|
export { createVar, type ContextVar } from "./context-var.js";
|
package/src/internal-debug.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// in all runtimes including Cloudflare Workers where process.env is unavailable.
|
|
4
|
-
// Falls back to process.env for non-Vite contexts (tests, direct Node usage).
|
|
1
|
+
// Vite define for compile-time injection; falls back to process.env (tests, Node).
|
|
2
|
+
// Works in all runtimes including Cloudflare Workers where process.env is unavailable.
|
|
5
3
|
export const INTERNAL_RANGO_DEBUG: boolean =
|
|
6
4
|
typeof __RANGO_DEBUG__ !== "undefined"
|
|
7
5
|
? __RANGO_DEBUG__
|
package/src/loader.rsc.ts
CHANGED
|
@@ -26,8 +26,7 @@ import { isUnderTestRunner } from "./runtime-env.js";
|
|
|
26
26
|
|
|
27
27
|
export { getFetchableLoader };
|
|
28
28
|
|
|
29
|
-
//
|
|
30
|
-
// (no Vite plugin to inject one). Process-stable; never reached in a real build.
|
|
29
|
+
// Runtime-fallback counter for bare unit tests (no Vite plugin); process-stable.
|
|
31
30
|
let runtimeLoaderIdCounter = 0;
|
|
32
31
|
|
|
33
32
|
// Overload 1: With function only (not fetchable)
|
|
@@ -54,17 +53,10 @@ export function createLoader<T>(
|
|
|
54
53
|
// Hidden parameter injected by Vite exposeInternalIds plugin
|
|
55
54
|
__injectedId?: string,
|
|
56
55
|
): LoaderDefinition<Awaited<T>, Record<string, string | undefined>> {
|
|
57
|
-
// The $$id will be set on the returned object by Vite plugin
|
|
58
|
-
// For fetchable loaders, __injectedId is also passed as a parameter
|
|
59
56
|
let loaderId = __injectedId || "";
|
|
60
57
|
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
// (it recovers the fn from the registry by $$id). Otherwise (dev or a real
|
|
64
|
-
// build) it means an UNSUPPORTED shape (e.g. a namespace import
|
|
65
|
-
// `rango.createLoader(...)`) the plugin skipped — fail loud. The rich
|
|
66
|
-
// diagnostic stays behind the NODE_ENV check so production folds it away and
|
|
67
|
-
// ships the small throw. isUnderTestRunner() is runtime-safe. Mirrors createHandle.
|
|
58
|
+
// Under test runner, fall back to synthetic id (recovers fn from registry by $$id).
|
|
59
|
+
// Otherwise (dev or prod), missing id means unsupported shape — fail loud.
|
|
68
60
|
if (!loaderId) {
|
|
69
61
|
if (isUnderTestRunner()) {
|
|
70
62
|
loaderId = `__rango_runtime_loader_${runtimeLoaderIdCounter++}`;
|
|
@@ -90,12 +82,9 @@ export function createLoader<T>(
|
|
|
90
82
|
};
|
|
91
83
|
}
|
|
92
84
|
|
|
93
|
-
// Fetchable loader - store fn in registry and return a serializable object
|
|
85
|
+
// Fetchable loader - store fn in registry and return a serializable object.
|
|
94
86
|
const middleware: MiddlewareFn[] =
|
|
95
87
|
fetchable === true ? [] : fetchable?.middleware || [];
|
|
96
|
-
|
|
97
|
-
// Register the function in the internal registry by $$id (server-side only)
|
|
98
|
-
// The loader fetch handler looks it up by $$id when load() is called from the client.
|
|
99
88
|
if (fn && loaderId) {
|
|
100
89
|
registerFetchableLoader(loaderId, fn, middleware, true);
|
|
101
90
|
}
|
package/src/loader.ts
CHANGED
|
@@ -38,8 +38,7 @@ export function createLoader<T>(
|
|
|
38
38
|
options: FetchableLoaderOptions,
|
|
39
39
|
): LoaderDefinition<Awaited<T>, Record<string, string | undefined>>;
|
|
40
40
|
|
|
41
|
-
// Implementation - client stub
|
|
42
|
-
// The $$id parameter is injected by Vite plugin, not user-provided
|
|
41
|
+
// Implementation - client stub ($$id injected by Vite plugin, not user-provided)
|
|
43
42
|
export function createLoader<T>(
|
|
44
43
|
_fn: LoaderFn<T, Record<string, string | undefined>, any>,
|
|
45
44
|
_fetchable?: true | FetchableLoaderOptions,
|
|
@@ -47,13 +46,8 @@ export function createLoader<T>(
|
|
|
47
46
|
): LoaderDefinition<Awaited<T>, Record<string, string | undefined>> {
|
|
48
47
|
const loaderId = __injectedId || "";
|
|
49
48
|
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
// fallback for whole-router construction). Otherwise (dev or a real build) a
|
|
53
|
-
// missing id means an UNSUPPORTED shape the plugin skipped — fail loud rather
|
|
54
|
-
// than ship `$$id: ""` (which would make a client useLoader read the wrong
|
|
55
|
-
// key). The rich diagnostic stays behind the NODE_ENV check so production folds
|
|
56
|
-
// it away and ships the small throw. isUnderTestRunner() is runtime-safe.
|
|
49
|
+
// Under test runner, no id needed (loaderId stays ""; loader.rsc.ts provides fallback).
|
|
50
|
+
// Otherwise, missing id means unsupported shape — fail loud to avoid wrong key.
|
|
57
51
|
if (!loaderId && !isUnderTestRunner()) {
|
|
58
52
|
if (process.env.NODE_ENV !== "production") {
|
|
59
53
|
throw missingInjectedIdError("Loader", "createLoader");
|
|
@@ -9,12 +9,7 @@ interface NetworkErrorThrowerProps {
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Client component that throws a NetworkError during render.
|
|
12
|
-
*
|
|
13
|
-
* during navigation or server actions.
|
|
14
|
-
*
|
|
15
|
-
* This must be a separate component because:
|
|
16
|
-
* 1. Errors must be thrown during React's render phase to be caught by error boundaries
|
|
17
|
-
* 2. The error occurs in async code (fetch), so we need to propagate it to React's render
|
|
12
|
+
* Errors thrown during render are caught by error boundaries; async errors are not.
|
|
18
13
|
*/
|
|
19
14
|
export function NetworkErrorThrower({
|
|
20
15
|
error,
|
package/src/outlet-provider.tsx
CHANGED
|
@@ -5,11 +5,7 @@ import { OutletContext, type OutletContextValue } from "./outlet-context.js";
|
|
|
5
5
|
import type { ResolvedSegment } from "./types.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Stores a reference to parent context so useLoader can walk up the chain
|
|
11
|
-
* to find loader data from parent layouts. If this segment defines a loading
|
|
12
|
-
* component, Outlet will wrap content with Suspense using that as fallback.
|
|
8
|
+
* Outlet content provider — stores parent context for useLoader chain walking.
|
|
13
9
|
*/
|
|
14
10
|
export function OutletProvider({
|
|
15
11
|
content,
|