@rangojs/router 0.0.0-experimental.3 → 0.0.0-experimental.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -0
- package/README.md +883 -4
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +4655 -747
- package/package.json +78 -50
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +54 -25
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +23 -21
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +390 -63
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +133 -10
- package/skills/layout/SKILL.md +102 -5
- package/skills/links/SKILL.md +239 -0
- package/skills/loader/SKILL.md +366 -29
- package/skills/middleware/SKILL.md +173 -36
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +80 -3
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +86 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +227 -14
- package/skills/router-setup/SKILL.md +225 -32
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +12 -11
- package/skills/typesafety/SKILL.md +401 -75
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +10 -4
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +87 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +20 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +201 -553
- package/src/browser/navigation-client.ts +124 -71
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +267 -317
- package/src/browser/prefetch/cache.ts +146 -0
- package/src/browser/prefetch/fetch.ts +135 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +173 -73
- package/src/browser/react/NavigationProvider.tsx +138 -27
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +37 -0
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +49 -65
- package/src/browser/react/use-href.tsx +20 -188
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +27 -78
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +111 -26
- package/src/browser/scroll-restoration.ts +92 -16
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +83 -0
- package/src/browser/server-action-bridge.ts +504 -584
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +92 -57
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +438 -0
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +35 -0
- package/src/build/route-trie.ts +265 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +411 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +469 -0
- package/src/build/route-types/scan-filter.ts +78 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +338 -0
- package/src/cache/cache-scope.ts +120 -303
- package/src/cache/cf/cf-cache-store.ts +119 -7
- package/src/cache/cf/index.ts +8 -2
- package/src/cache/document-cache.ts +101 -72
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +0 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +98 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +10 -15
- package/src/client.tsx +114 -135
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +86 -0
- package/src/debug.ts +17 -7
- package/src/errors.ts +108 -2
- package/src/handle.ts +34 -19
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +165 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +53 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +352 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +146 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +135 -49
- package/src/index.rsc.ts +182 -17
- package/src/index.ts +238 -24
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +27 -142
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +9 -11
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -1388
- package/src/route-map-builder.ts +241 -112
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +70 -9
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +158 -0
- package/src/router/handler-context.ts +371 -81
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +215 -122
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +155 -32
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +80 -93
- package/src/router/match-middleware/cache-lookup.ts +382 -9
- package/src/router/match-middleware/cache-store.ts +51 -22
- package/src/router/match-middleware/intercept-resolution.ts +55 -17
- package/src/router/match-middleware/segment-resolution.ts +24 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +34 -29
- package/src/router/metrics.ts +235 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +324 -367
- package/src/router/pattern-matching.ts +321 -30
- package/src/router/prerender-match.ts +400 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/router-context.ts +36 -21
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1241 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +289 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +77 -3
- package/src/router.ts +688 -3656
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +786 -760
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +5 -25
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +235 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +40 -14
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +57 -61
- package/src/server/context.ts +202 -51
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +422 -70
- package/src/server.ts +36 -120
- package/src/ssr/index.tsx +157 -26
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +102 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -1577
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -726
- package/src/use-loader.tsx +85 -77
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +110 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +11 -782
- package/src/vite/plugin-types.ts +131 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +254 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +29 -15
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +510 -0
- package/src/vite/router-discovery.ts +785 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
- package/CLAUDE.md +0 -3
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/href.ts +0 -255
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -357
- package/src/vite/expose-location-state-id.ts +0 -177
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/src/client.tsx
CHANGED
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
RouteContentWrapper,
|
|
21
21
|
LoaderBoundary,
|
|
22
22
|
} from "./route-content-wrapper.js";
|
|
23
|
+
import { OutletProvider } from "./outlet-provider.js";
|
|
24
|
+
import { MountContextProvider } from "./browser/react/mount-context.js";
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* Outlet component - renders child content in layouts
|
|
@@ -79,12 +81,15 @@ export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
|
|
|
79
81
|
: Promise.resolve(segment.component)
|
|
80
82
|
}
|
|
81
83
|
fallback={segment.loading}
|
|
84
|
+
segmentId={segment.id}
|
|
82
85
|
/>
|
|
83
86
|
);
|
|
84
87
|
} else {
|
|
85
88
|
content = segment.component ?? null;
|
|
86
89
|
}
|
|
87
90
|
|
|
91
|
+
let result: ReactNode;
|
|
92
|
+
|
|
88
93
|
// If segment has a layout, wrap appropriately
|
|
89
94
|
if (segment.layout) {
|
|
90
95
|
// Check if this segment has loaders that need streaming
|
|
@@ -104,25 +109,23 @@ export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
|
|
|
104
109
|
</LoaderBoundary>
|
|
105
110
|
);
|
|
106
111
|
|
|
107
|
-
|
|
112
|
+
result = (
|
|
108
113
|
<OutletProvider content={loaderAwareContent} segment={segment}>
|
|
109
114
|
{segment.layout}
|
|
110
115
|
</OutletProvider>
|
|
111
116
|
);
|
|
117
|
+
} else {
|
|
118
|
+
// No loaders - wrap in OutletProvider so layout can use <Outlet />
|
|
119
|
+
result = (
|
|
120
|
+
<OutletProvider content={content} segment={segment}>
|
|
121
|
+
{segment.layout}
|
|
122
|
+
</OutletProvider>
|
|
123
|
+
);
|
|
112
124
|
}
|
|
113
|
-
|
|
114
|
-
// No loaders - wrap
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
{segment.layout}
|
|
118
|
-
</OutletProvider>
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// No layout but has loaders - wrap content with LoaderBoundary for useLoader context
|
|
123
|
-
// This is common for intercept routes that use useLoader without a custom layout
|
|
124
|
-
if (segment.loaderDataPromise && segment.loaderIds) {
|
|
125
|
-
return (
|
|
125
|
+
} else if (segment.loaderDataPromise && segment.loaderIds) {
|
|
126
|
+
// No layout but has loaders - wrap content with LoaderBoundary for useLoader context
|
|
127
|
+
// This is common for intercept routes that use useLoader without a custom layout
|
|
128
|
+
result = (
|
|
126
129
|
<LoaderBoundary
|
|
127
130
|
loaderDataPromise={segment.loaderDataPromise}
|
|
128
131
|
loaderIds={segment.loaderIds}
|
|
@@ -134,9 +137,20 @@ export function Outlet({ name }: { name?: `@${string}` } = {}): ReactNode {
|
|
|
134
137
|
{content}
|
|
135
138
|
</LoaderBoundary>
|
|
136
139
|
);
|
|
140
|
+
} else {
|
|
141
|
+
result = content;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Wrap with MountContextProvider for include() scoped parallel/intercept slots
|
|
145
|
+
if (segment.mountPath) {
|
|
146
|
+
return (
|
|
147
|
+
<MountContextProvider value={segment.mountPath}>
|
|
148
|
+
{result}
|
|
149
|
+
</MountContextProvider>
|
|
150
|
+
);
|
|
137
151
|
}
|
|
138
152
|
|
|
139
|
-
return
|
|
153
|
+
return result;
|
|
140
154
|
}
|
|
141
155
|
|
|
142
156
|
// Default: render child content
|
|
@@ -193,12 +207,15 @@ export function ParallelOutlet({ name }: { name: `@${string}` }): ReactNode {
|
|
|
193
207
|
: Promise.resolve(segment.component)
|
|
194
208
|
}
|
|
195
209
|
fallback={segment.loading}
|
|
210
|
+
segmentId={segment.id}
|
|
196
211
|
/>
|
|
197
212
|
);
|
|
198
213
|
} else {
|
|
199
214
|
content = segment.component ?? null;
|
|
200
215
|
}
|
|
201
216
|
|
|
217
|
+
let result: ReactNode;
|
|
218
|
+
|
|
202
219
|
// If segment has a layout, wrap appropriately
|
|
203
220
|
if (segment.layout) {
|
|
204
221
|
// Check if this segment has loaders that need streaming
|
|
@@ -217,25 +234,23 @@ export function ParallelOutlet({ name }: { name: `@${string}` }): ReactNode {
|
|
|
217
234
|
</LoaderBoundary>
|
|
218
235
|
);
|
|
219
236
|
|
|
220
|
-
|
|
237
|
+
result = (
|
|
221
238
|
<OutletProvider content={loaderAwareContent} segment={segment}>
|
|
222
239
|
{segment.layout}
|
|
223
240
|
</OutletProvider>
|
|
224
241
|
);
|
|
242
|
+
} else {
|
|
243
|
+
// No loaders - wrap in OutletProvider so layout can use <Outlet />
|
|
244
|
+
result = (
|
|
245
|
+
<OutletProvider content={content} segment={segment}>
|
|
246
|
+
{segment.layout}
|
|
247
|
+
</OutletProvider>
|
|
248
|
+
);
|
|
225
249
|
}
|
|
226
|
-
|
|
227
|
-
// No loaders - wrap
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
{segment.layout}
|
|
231
|
-
</OutletProvider>
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// No layout but has loaders - wrap content with LoaderBoundary for useLoader context
|
|
236
|
-
// This is common for intercept routes that use useLoader without a custom layout
|
|
237
|
-
if (segment.loaderDataPromise && segment.loaderIds) {
|
|
238
|
-
return (
|
|
250
|
+
} else if (segment.loaderDataPromise && segment.loaderIds) {
|
|
251
|
+
// No layout but has loaders - wrap content with LoaderBoundary for useLoader context
|
|
252
|
+
// This is common for intercept routes that use useLoader without a custom layout
|
|
253
|
+
result = (
|
|
239
254
|
<LoaderBoundary
|
|
240
255
|
loaderDataPromise={segment.loaderDataPromise}
|
|
241
256
|
loaderIds={segment.loaderIds}
|
|
@@ -247,51 +262,28 @@ export function ParallelOutlet({ name }: { name: `@${string}` }): ReactNode {
|
|
|
247
262
|
{content}
|
|
248
263
|
</LoaderBoundary>
|
|
249
264
|
);
|
|
265
|
+
} else {
|
|
266
|
+
result = content;
|
|
250
267
|
}
|
|
251
268
|
|
|
252
|
-
|
|
253
|
-
|
|
269
|
+
// Wrap with MountContextProvider for include() scoped parallel/intercept slots
|
|
270
|
+
if (segment.mountPath) {
|
|
271
|
+
return (
|
|
272
|
+
<MountContextProvider value={segment.mountPath}>
|
|
273
|
+
{result}
|
|
274
|
+
</MountContextProvider>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
254
277
|
|
|
255
|
-
|
|
256
|
-
* Provider for outlet content - used internally by renderSegments
|
|
257
|
-
*
|
|
258
|
-
* Stores a reference to parent context so useLoader can walk up the chain
|
|
259
|
-
* to find loader data from parent layouts. If this segment defines a loading
|
|
260
|
-
* component, Outlet will wrap content with Suspense using that as fallback.
|
|
261
|
-
*/
|
|
262
|
-
export function OutletProvider({
|
|
263
|
-
content,
|
|
264
|
-
parallel,
|
|
265
|
-
segment,
|
|
266
|
-
loaderData,
|
|
267
|
-
children,
|
|
268
|
-
}: {
|
|
269
|
-
content: ReactNode;
|
|
270
|
-
parallel?: ResolvedSegment[];
|
|
271
|
-
segment?: ResolvedSegment;
|
|
272
|
-
loaderData?: Record<string, any>;
|
|
273
|
-
children: ReactNode;
|
|
274
|
-
}): ReactNode {
|
|
275
|
-
// Get parent context to enable walking up the chain for loader lookups
|
|
276
|
-
const parentContext = useContext(OutletContext);
|
|
277
|
-
|
|
278
|
-
const value = useMemo(
|
|
279
|
-
() => ({
|
|
280
|
-
content,
|
|
281
|
-
parallel,
|
|
282
|
-
segment,
|
|
283
|
-
loaderData,
|
|
284
|
-
parent: parentContext,
|
|
285
|
-
loading: segment?.loading,
|
|
286
|
-
}),
|
|
287
|
-
[content, parallel, segment, loaderData, parentContext]
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
return (
|
|
291
|
-
<OutletContext.Provider value={value}>{children}</OutletContext.Provider>
|
|
292
|
-
);
|
|
278
|
+
return result;
|
|
293
279
|
}
|
|
294
280
|
|
|
281
|
+
// OutletProvider is defined in outlet-provider.tsx to break a circular
|
|
282
|
+
// dependency between client.tsx and route-content-wrapper.tsx.
|
|
283
|
+
// Imported at the top of this file for local use in Outlet/ParallelOutlet,
|
|
284
|
+
// and re-exported here for backwards compatibility.
|
|
285
|
+
export { OutletProvider };
|
|
286
|
+
|
|
295
287
|
/**
|
|
296
288
|
* Hook to access outlet content programmatically
|
|
297
289
|
*
|
|
@@ -321,52 +313,6 @@ export {
|
|
|
321
313
|
type UseLoaderOptions,
|
|
322
314
|
} from "./use-loader.js";
|
|
323
315
|
|
|
324
|
-
/**
|
|
325
|
-
* Hook to access all loader data in the current context
|
|
326
|
-
*
|
|
327
|
-
* Returns a record of all loader data available in the current outlet context
|
|
328
|
-
* and all parent contexts. Useful for debugging or when you need access to
|
|
329
|
-
* multiple loaders.
|
|
330
|
-
*
|
|
331
|
-
* @returns Record of loader name to data, or empty object if no loaders
|
|
332
|
-
*
|
|
333
|
-
* @example
|
|
334
|
-
* ```tsx
|
|
335
|
-
* "use client";
|
|
336
|
-
* import { useLoaderData } from "rsc-router/client";
|
|
337
|
-
*
|
|
338
|
-
* export function DebugPanel() {
|
|
339
|
-
* const loaderData = useLoaderData();
|
|
340
|
-
* return <pre>{JSON.stringify(loaderData, null, 2)}</pre>;
|
|
341
|
-
* }
|
|
342
|
-
* ```
|
|
343
|
-
*/
|
|
344
|
-
export function useLoaderData(): Record<string, any> {
|
|
345
|
-
const context = useContext(OutletContext);
|
|
346
|
-
|
|
347
|
-
// Collect all loader data from the context chain
|
|
348
|
-
// Child loaders override parent loaders with the same name
|
|
349
|
-
const result: Record<string, any> = {};
|
|
350
|
-
const stack: OutletContextValue[] = [];
|
|
351
|
-
|
|
352
|
-
// Build stack from current to root
|
|
353
|
-
let current: OutletContextValue | null | undefined = context;
|
|
354
|
-
while (current) {
|
|
355
|
-
stack.push(current);
|
|
356
|
-
current = current.parent;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Apply from root to current (so children override parents)
|
|
360
|
-
for (let i = stack.length - 1; i >= 0; i--) {
|
|
361
|
-
const ctx = stack[i];
|
|
362
|
-
if (ctx.loaderData) {
|
|
363
|
-
Object.assign(result, ctx.loaderData);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return result;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
316
|
/**
|
|
371
317
|
* Client-safe createLoader factory
|
|
372
318
|
*
|
|
@@ -396,13 +342,13 @@ export function useLoaderData(): Record<string, any> {
|
|
|
396
342
|
*/
|
|
397
343
|
// Overload 1: With function only (not fetchable)
|
|
398
344
|
export function createLoader<T>(
|
|
399
|
-
fn: LoaderFn<T, Record<string, string | undefined>, any
|
|
345
|
+
fn: LoaderFn<T, Record<string, string | undefined>, any>,
|
|
400
346
|
): LoaderDefinition<Awaited<T>, Record<string, string | undefined>>;
|
|
401
347
|
|
|
402
348
|
// Overload 2: With function and fetchable flag
|
|
403
349
|
export function createLoader<T>(
|
|
404
350
|
fn: LoaderFn<T, Record<string, string | undefined>, any>,
|
|
405
|
-
fetchable: true
|
|
351
|
+
fetchable: true,
|
|
406
352
|
): LoaderDefinition<Awaited<T>, Record<string, string | undefined>>;
|
|
407
353
|
|
|
408
354
|
// Implementation - function is ignored at runtime on client
|
|
@@ -410,7 +356,7 @@ export function createLoader<T>(
|
|
|
410
356
|
export function createLoader(
|
|
411
357
|
_fn: LoaderFn<any, Record<string, string | undefined>, any>,
|
|
412
358
|
_fetchable?: true,
|
|
413
|
-
__injectedId?: string
|
|
359
|
+
__injectedId?: string,
|
|
414
360
|
): LoaderDefinition<any, Record<string, string | undefined>> {
|
|
415
361
|
return {
|
|
416
362
|
__brand: "loader",
|
|
@@ -532,11 +478,16 @@ export class ErrorBoundary extends Component<
|
|
|
532
478
|
// ============================================================================
|
|
533
479
|
|
|
534
480
|
// Navigation hooks
|
|
535
|
-
export {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
} from "./browser/react/use-
|
|
481
|
+
export { useNavigation } from "./browser/react/use-navigation.js";
|
|
482
|
+
export { useRouter } from "./browser/react/use-router.js";
|
|
483
|
+
export { usePathname } from "./browser/react/use-pathname.js";
|
|
484
|
+
export { useSearchParams } from "./browser/react/use-search-params.js";
|
|
485
|
+
export { useParams } from "./browser/react/use-params.js";
|
|
486
|
+
export type {
|
|
487
|
+
RouterInstance,
|
|
488
|
+
RouterNavigateOptions,
|
|
489
|
+
ReadonlyURLSearchParams,
|
|
490
|
+
} from "./browser/types.js";
|
|
540
491
|
|
|
541
492
|
// Action state tracking hook
|
|
542
493
|
export {
|
|
@@ -600,22 +551,50 @@ export {
|
|
|
600
551
|
useLocationState,
|
|
601
552
|
type LocationStateDefinition,
|
|
602
553
|
type LocationStateEntry,
|
|
554
|
+
type LocationStateOptions,
|
|
603
555
|
} from "./browser/react/location-state.js";
|
|
604
556
|
|
|
605
557
|
// Type-safe href for client-side path validation
|
|
606
|
-
export { href, type ValidPaths, type PatternToPath } from "./href-client.js";
|
|
607
|
-
|
|
608
|
-
// useHref hook for Django-style route name resolution
|
|
609
558
|
export {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
type
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
559
|
+
href,
|
|
560
|
+
type ValidPaths,
|
|
561
|
+
type PatternToPath,
|
|
562
|
+
type PathResponse,
|
|
563
|
+
} from "./href-client.js";
|
|
564
|
+
|
|
565
|
+
// Response envelope types for consuming JSON response routes
|
|
566
|
+
export type { ResponseEnvelope, ResponseError } from "./urls.js";
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Type guard for checking if a response envelope contains an error.
|
|
570
|
+
*
|
|
571
|
+
* @example
|
|
572
|
+
* ```typescript
|
|
573
|
+
* const result: ResponseEnvelope<Product> = await fetch(url).then(r => r.json());
|
|
574
|
+
* if (isResponseError(result)) {
|
|
575
|
+
* console.log(result.error.message, result.error.code);
|
|
576
|
+
* return;
|
|
577
|
+
* }
|
|
578
|
+
* result.data // fully typed as Product
|
|
579
|
+
* ```
|
|
580
|
+
*/
|
|
581
|
+
export function isResponseError<T>(
|
|
582
|
+
result: import("./urls.js").ResponseEnvelope<T>,
|
|
583
|
+
): result is import("./urls.js").ResponseEnvelope<T> & {
|
|
584
|
+
error: import("./urls.js").ResponseError;
|
|
585
|
+
} {
|
|
586
|
+
return result.error !== undefined;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Mount context for include() scoped components
|
|
590
|
+
export { useMount } from "./browser/react/use-mount.js";
|
|
591
|
+
export { MountContext } from "./browser/react/mount-context.js";
|
|
592
|
+
|
|
593
|
+
// Mount-aware href hook - auto-prefixes paths with include() mount
|
|
594
|
+
export { useHref } from "./browser/react/use-href.js";
|
|
595
|
+
|
|
596
|
+
// Type-safe scoped reverse function for scopedReverse<typeof patterns>()
|
|
597
|
+
export type { ScopedReverseFunction } from "./reverse.js";
|
|
619
598
|
|
|
620
599
|
// Loader definition type - for typing loader props in client components
|
|
621
600
|
export type { LoaderDefinition } from "./types.js";
|
package/src/component-utils.ts
CHANGED
|
@@ -33,7 +33,7 @@ const CLIENT_REFERENCE = Symbol.for("react.client.reference");
|
|
|
33
33
|
* ```
|
|
34
34
|
*/
|
|
35
35
|
export function isClientComponent(
|
|
36
|
-
component: ComponentType<unknown> | unknown
|
|
36
|
+
component: ComponentType<unknown> | unknown,
|
|
37
37
|
): boolean {
|
|
38
38
|
if (typeof component !== "function") {
|
|
39
39
|
return false;
|
|
@@ -52,13 +52,13 @@ export function isClientComponent(
|
|
|
52
52
|
*/
|
|
53
53
|
export function assertClientComponent(
|
|
54
54
|
component: ComponentType<unknown> | unknown,
|
|
55
|
-
name: string
|
|
55
|
+
name: string,
|
|
56
56
|
): asserts component is ComponentType<unknown> {
|
|
57
57
|
if (typeof component !== "function") {
|
|
58
58
|
throw new Error(
|
|
59
59
|
`${name} must be a client component function with "use client" directive. ` +
|
|
60
60
|
`Make sure to pass the component itself, not a JSX element: ` +
|
|
61
|
-
`${name}: My${capitalize(name)} (correct) vs ${name}: <My${capitalize(name)} /> (incorrect)
|
|
61
|
+
`${name}: My${capitalize(name)} (correct) vs ${name}: <My${capitalize(name)} /> (incorrect)`,
|
|
62
62
|
);
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -66,7 +66,7 @@ export function assertClientComponent(
|
|
|
66
66
|
throw new Error(
|
|
67
67
|
`${name} must be a client component with "use client" directive at the top of the file. ` +
|
|
68
68
|
`Server components cannot be used as the ${name} because their function reference ` +
|
|
69
|
-
`cannot be serialized in the RSC payload. Add "use client" to your ${name} file
|
|
69
|
+
`cannot be serialized in the RSC payload. Add "use client" to your ${name} file.`,
|
|
70
70
|
);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -11,7 +11,11 @@ import { MetaTags } from "../handles/MetaTags.js";
|
|
|
11
11
|
* Uses suppressHydrationWarning on <html> because the theme script
|
|
12
12
|
* may modify class/style attributes before React hydrates.
|
|
13
13
|
*/
|
|
14
|
-
export function DefaultDocument({
|
|
14
|
+
export function DefaultDocument({
|
|
15
|
+
children,
|
|
16
|
+
}: {
|
|
17
|
+
children: ReactNode;
|
|
18
|
+
}): ReactElement {
|
|
15
19
|
return (
|
|
16
20
|
<html lang="en" suppressHydrationWarning>
|
|
17
21
|
<head>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed context variables for ctx.set() / ctx.get().
|
|
3
|
+
*
|
|
4
|
+
* createVar<T>() produces a typed token that handlers set and layouts/middleware
|
|
5
|
+
* read. The token carries a unique Symbol used as the property key on the
|
|
6
|
+
* per-request variables object — no build-time processing, no IDs.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { createVar } from "@rangojs/router";
|
|
11
|
+
*
|
|
12
|
+
* interface PaginationData { current: number; total: number }
|
|
13
|
+
* export const Pagination = createVar<PaginationData>();
|
|
14
|
+
*
|
|
15
|
+
* // handler
|
|
16
|
+
* ctx.set(Pagination, { current: 1, total: 4 });
|
|
17
|
+
*
|
|
18
|
+
* // layout
|
|
19
|
+
* const pg = ctx.get(Pagination); // PaginationData | undefined
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export interface ContextVar<T> {
|
|
24
|
+
readonly __brand: "context-var";
|
|
25
|
+
readonly key: symbol;
|
|
26
|
+
/** Phantom field to carry the type parameter. Never set at runtime. */
|
|
27
|
+
readonly __type?: T;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a typed context variable token.
|
|
32
|
+
*
|
|
33
|
+
* The returned object is used with ctx.set(token, value) and ctx.get(token)
|
|
34
|
+
* for compile-time-checked data flow between handlers, layouts, and middleware.
|
|
35
|
+
*/
|
|
36
|
+
export function createVar<T>(): ContextVar<T> {
|
|
37
|
+
return { __brand: "context-var" as const, key: Symbol() };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Type guard: is the value a ContextVar token?
|
|
42
|
+
*/
|
|
43
|
+
export function isContextVar(value: unknown): value is ContextVar<unknown> {
|
|
44
|
+
return (
|
|
45
|
+
typeof value === "object" &&
|
|
46
|
+
value !== null &&
|
|
47
|
+
"__brand" in value &&
|
|
48
|
+
(value as { __brand: unknown }).__brand === "context-var"
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Read a variable from the variables store.
|
|
54
|
+
* Accepts either a string key (legacy) or a ContextVar token (typed).
|
|
55
|
+
*/
|
|
56
|
+
export function contextGet(
|
|
57
|
+
variables: any,
|
|
58
|
+
keyOrVar: string | ContextVar<any>,
|
|
59
|
+
): any {
|
|
60
|
+
if (typeof keyOrVar === "string") return variables[keyOrVar];
|
|
61
|
+
return variables[keyOrVar.key];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Keys that must never be used as string variable names */
|
|
65
|
+
const FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Write a variable to the variables store.
|
|
69
|
+
* Accepts either a string key (legacy) or a ContextVar token (typed).
|
|
70
|
+
*/
|
|
71
|
+
export function contextSet(
|
|
72
|
+
variables: any,
|
|
73
|
+
keyOrVar: string | ContextVar<any>,
|
|
74
|
+
value: any,
|
|
75
|
+
): void {
|
|
76
|
+
if (typeof keyOrVar === "string") {
|
|
77
|
+
if (FORBIDDEN_KEYS.has(keyOrVar)) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`ctx.set(): "${keyOrVar}" is a reserved key and cannot be used as a variable name.`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
variables[keyOrVar] = value;
|
|
83
|
+
} else {
|
|
84
|
+
variables[keyOrVar.key] = value;
|
|
85
|
+
}
|
|
86
|
+
}
|
package/src/debug.ts
CHANGED
|
@@ -34,7 +34,7 @@ export interface SerializedManifest {
|
|
|
34
34
|
* Serialize a manifest Map into a JSON-friendly structure
|
|
35
35
|
*/
|
|
36
36
|
export function serializeManifest(
|
|
37
|
-
manifest: Map<string, EntryData
|
|
37
|
+
manifest: Map<string, EntryData>,
|
|
38
38
|
): SerializedManifest {
|
|
39
39
|
const routes: Record<string, SerializedEntry> = {};
|
|
40
40
|
const layouts: Record<string, SerializedEntry> = {};
|
|
@@ -92,7 +92,7 @@ export function serializeManifest(
|
|
|
92
92
|
*/
|
|
93
93
|
export function compareManifests(
|
|
94
94
|
oldManifest: SerializedManifest,
|
|
95
|
-
newManifest: SerializedManifest
|
|
95
|
+
newManifest: SerializedManifest,
|
|
96
96
|
): {
|
|
97
97
|
addedRoutes: string[];
|
|
98
98
|
removedRoutes: string[];
|
|
@@ -113,10 +113,20 @@ export function compareManifests(
|
|
|
113
113
|
} {
|
|
114
114
|
const addedRoutes: string[] = [];
|
|
115
115
|
const removedRoutes: string[] = [];
|
|
116
|
-
const changedRoutes: Array<{
|
|
116
|
+
const changedRoutes: Array<{
|
|
117
|
+
key: string;
|
|
118
|
+
field: string;
|
|
119
|
+
old: any;
|
|
120
|
+
new: any;
|
|
121
|
+
}> = [];
|
|
117
122
|
const addedLayouts: string[] = [];
|
|
118
123
|
const removedLayouts: string[] = [];
|
|
119
|
-
const changedLayouts: Array<{
|
|
124
|
+
const changedLayouts: Array<{
|
|
125
|
+
key: string;
|
|
126
|
+
field: string;
|
|
127
|
+
old: any;
|
|
128
|
+
new: any;
|
|
129
|
+
}> = [];
|
|
120
130
|
|
|
121
131
|
// Compare routes
|
|
122
132
|
const oldRouteKeys = new Set(Object.keys(oldManifest.routes));
|
|
@@ -191,7 +201,7 @@ export function compareManifests(
|
|
|
191
201
|
* Format manifest diff as a human-readable string
|
|
192
202
|
*/
|
|
193
203
|
export function formatManifestDiff(
|
|
194
|
-
diff: ReturnType<typeof compareManifests
|
|
204
|
+
diff: ReturnType<typeof compareManifests>,
|
|
195
205
|
): string {
|
|
196
206
|
const lines: string[] = [];
|
|
197
207
|
|
|
@@ -208,7 +218,7 @@ export function formatManifestDiff(
|
|
|
208
218
|
if (diff.changedRoutes.length > 0) {
|
|
209
219
|
lines.push("Changed routes:");
|
|
210
220
|
diff.changedRoutes.forEach((c) =>
|
|
211
|
-
lines.push(` ~ ${c.key}.${c.field}: ${c.old} -> ${c.new}`)
|
|
221
|
+
lines.push(` ~ ${c.key}.${c.field}: ${c.old} -> ${c.new}`),
|
|
212
222
|
);
|
|
213
223
|
}
|
|
214
224
|
|
|
@@ -225,7 +235,7 @@ export function formatManifestDiff(
|
|
|
225
235
|
if (diff.changedLayouts.length > 0) {
|
|
226
236
|
lines.push("Changed layouts:");
|
|
227
237
|
diff.changedLayouts.forEach((c) =>
|
|
228
|
-
lines.push(` ~ ${c.key}.${c.field}: ${c.old} -> ${c.new}`)
|
|
238
|
+
lines.push(` ~ ${c.key}.${c.field}: ${c.old} -> ${c.new}`),
|
|
229
239
|
);
|
|
230
240
|
}
|
|
231
241
|
|