@rangojs/router 0.0.0-experimental.13 → 0.0.0-experimental.13221847
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 +9 -0
- package/README.md +884 -4
- package/dist/bin/rango.js +1531 -212
- package/dist/vite/index.js +3995 -2489
- package/package.json +57 -52
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +85 -23
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +6 -4
- package/skills/hooks/SKILL.md +328 -70
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +131 -8
- package/skills/layout/SKILL.md +100 -3
- package/skills/links/SKILL.md +62 -15
- package/skills/loader/SKILL.md +368 -42
- package/skills/middleware/SKILL.md +171 -34
- package/skills/mime-routes/SKILL.md +14 -10
- package/skills/parallel/SKILL.md +137 -1
- package/skills/prerender/SKILL.md +366 -28
- package/skills/rango/SKILL.md +85 -21
- package/skills/response-routes/SKILL.md +136 -83
- package/skills/route/SKILL.md +195 -21
- package/skills/router-setup/SKILL.md +123 -30
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +240 -102
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +92 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +11 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +266 -558
- package/src/browser/navigation-client.ts +132 -75
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +303 -309
- package/src/browser/prefetch/cache.ts +206 -0
- package/src/browser/prefetch/fetch.ts +144 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +128 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +190 -70
- package/src/browser/react/NavigationProvider.tsx +78 -11
- 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 +6 -1
- 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 +29 -70
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +22 -63
- 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 +188 -57
- package/src/browser/scroll-restoration.ts +117 -44
- package/src/browser/segment-reconciler.ts +221 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +488 -606
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +116 -47
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +63 -21
- package/src/build/generate-route-types.ts +36 -1038
- package/src/build/index.ts +2 -5
- package/src/build/route-trie.ts +38 -12
- 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 +479 -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 +342 -0
- package/src/cache/cache-scope.ts +122 -303
- package/src/cache/cf/cf-cache-store.ts +571 -17
- package/src/cache/cf/index.ts +13 -3
- package/src/cache/document-cache.ts +116 -77
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +1 -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 +3 -1
- package/src/client.tsx +84 -126
- 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 +19 -9
- package/src/errors.ts +77 -7
- package/src/handle.ts +12 -7
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +65 -45
- package/src/index.rsc.ts +104 -40
- package/src/index.ts +122 -67
- package/src/internal-debug.ts +9 -3
- package/src/loader.rsc.ts +18 -93
- package/src/loader.ts +26 -9
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +4 -2
- package/src/prerender/store.ts +121 -17
- package/src/prerender.ts +325 -20
- package/src/reverse.ts +144 -124
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +959 -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 -1450
- package/src/route-map-builder.ts +87 -133
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +41 -6
- 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 +160 -0
- package/src/router/handler-context.ts +324 -116
- package/src/router/intercept-resolution.ts +11 -4
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +179 -133
- package/src/router/logging.ts +112 -6
- package/src/router/manifest.ts +58 -19
- package/src/router/match-api.ts +89 -88
- package/src/router/match-context.ts +4 -2
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +86 -89
- package/src/router/match-middleware/cache-lookup.ts +295 -49
- package/src/router/match-middleware/cache-store.ts +56 -13
- package/src/router/match-middleware/intercept-resolution.ts +45 -22
- package/src/router/match-middleware/segment-resolution.ts +20 -9
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +44 -21
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +327 -369
- package/src/router/pattern-matching.ts +169 -31
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +105 -14
- package/src/router/router-context.ts +40 -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 +677 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1296 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -1354
- package/src/router/segment-wrappers.ts +291 -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 +96 -29
- package/src/router/types.ts +15 -9
- package/src/router.ts +642 -2366
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +639 -1027
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- 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 +237 -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 +38 -11
- package/src/search-params.ts +66 -54
- package/src/segment-system.tsx +165 -17
- package/src/server/context.ts +237 -54
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +438 -71
- package/src/server.ts +26 -164
- package/src/ssr/index.tsx +101 -31
- package/src/static-handler.ts +22 -4
- 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 +773 -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 +109 -0
- package/src/types/segments.ts +150 -0
- package/src/types.ts +1 -1795
- 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 -1323
- 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 +108 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +11 -2259
- package/src/vite/plugin-types.ts +48 -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 -47
- package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +8 -43
- 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 +266 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +445 -0
- package/src/vite/router-discovery.ts +777 -0
- package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +181 -9
- 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 -43
- package/dist/vite/index.named-routes.gen.ts +0 -103
- 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/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-internal-ids.ts +0 -1167
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/src/search-params.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Search parameter schema types and runtime utilities.
|
|
3
3
|
*
|
|
4
4
|
* Provides a lightweight schema system for typed query parameters.
|
|
5
|
-
* When a route defines a `search` schema, ctx.
|
|
6
|
-
*
|
|
5
|
+
* When a route defines a `search` schema, ctx.search provides a typed
|
|
6
|
+
* object with parsed values. ctx.searchParams always remains a standard
|
|
7
|
+
* URLSearchParams instance.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
// ============================================================================
|
|
@@ -54,16 +55,26 @@ type Simplify<T> = { [K in keyof T]: T[K] };
|
|
|
54
55
|
/**
|
|
55
56
|
* Resolve a SearchSchema to its typed object.
|
|
56
57
|
*
|
|
58
|
+
* Both required and optional params resolve to `T | undefined` at the handler
|
|
59
|
+
* level. The required/optional distinction is a consumer-facing contract
|
|
60
|
+
* (e.g., for href() and reverse() autocomplete) — it tells callers which
|
|
61
|
+
* params the route expects, but the handler must still check for undefined
|
|
62
|
+
* since the framework cannot trust the client to send all required params.
|
|
63
|
+
*
|
|
57
64
|
* @example
|
|
58
65
|
* type S = { q: "string"; page: "number?"; sort: "string?" };
|
|
59
66
|
* type R = ResolveSearchSchema<S>;
|
|
60
|
-
* // { q: string; page?: number; sort?: string }
|
|
67
|
+
* // { q: string | undefined; page?: number; sort?: string }
|
|
61
68
|
*/
|
|
62
|
-
export type ResolveSearchSchema<T extends SearchSchema> = Simplify<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
export type ResolveSearchSchema<T extends SearchSchema> = Simplify<
|
|
70
|
+
{
|
|
71
|
+
[K in RequiredKeys<T> & string]:
|
|
72
|
+
| ResolveBaseType<BaseType<T[K]>>
|
|
73
|
+
| undefined;
|
|
74
|
+
} & {
|
|
75
|
+
[K in OptionalKeys<T> & string]?: ResolveBaseType<BaseType<T[K]>>;
|
|
76
|
+
}
|
|
77
|
+
>;
|
|
67
78
|
|
|
68
79
|
// ============================================================================
|
|
69
80
|
// Route-Level Type Extraction
|
|
@@ -87,19 +98,17 @@ type GlobalRouteMap = keyof RSCRouter.RegisteredRoutes extends never
|
|
|
87
98
|
* // { q: string; page?: number }
|
|
88
99
|
* ```
|
|
89
100
|
*/
|
|
90
|
-
export type RouteSearchParams<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
> = [TRouteMap] extends [never]
|
|
101
|
+
export type RouteSearchParams<TName extends string, TRouteMap = never> = [
|
|
102
|
+
TRouteMap,
|
|
103
|
+
] extends [never]
|
|
94
104
|
? ExtractAndResolveSearch<GlobalRouteMap, TName>
|
|
95
105
|
: ExtractAndResolveSearch<TRouteMap, TName>;
|
|
96
106
|
|
|
97
|
-
type ExtractAndResolveSearch<TRouteMap, TName> =
|
|
98
|
-
TName extends
|
|
99
|
-
?
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
: {};
|
|
107
|
+
type ExtractAndResolveSearch<TRouteMap, TName> = TName extends keyof TRouteMap
|
|
108
|
+
? TRouteMap[TName] extends { readonly search: infer S extends SearchSchema }
|
|
109
|
+
? ResolveSearchSchema<S>
|
|
110
|
+
: {}
|
|
111
|
+
: {};
|
|
103
112
|
|
|
104
113
|
/**
|
|
105
114
|
* Extract the route params type for a named route.
|
|
@@ -112,36 +121,46 @@ type ExtractAndResolveSearch<TRouteMap, TName> =
|
|
|
112
121
|
* // { slug: string }
|
|
113
122
|
* ```
|
|
114
123
|
*/
|
|
115
|
-
export type RouteParams<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
> = [TRouteMap] extends [never]
|
|
124
|
+
export type RouteParams<TName extends string, TRouteMap = never> = [
|
|
125
|
+
TRouteMap,
|
|
126
|
+
] extends [never]
|
|
119
127
|
? ExtractRouteParamsFromMap<GlobalRouteMap, TName>
|
|
120
128
|
: ExtractRouteParamsFromMap<TRouteMap, TName>;
|
|
121
129
|
|
|
122
|
-
type ExtractRouteParamsFromMap<TRouteMap, TName> =
|
|
123
|
-
TName extends
|
|
124
|
-
? TRouteMap[TName]
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
+
type ExtractRouteParamsFromMap<TRouteMap, TName> = TName extends keyof TRouteMap
|
|
131
|
+
? TRouteMap[TName] extends string
|
|
132
|
+
? ExtractParamsFromPattern<TRouteMap[TName]>
|
|
133
|
+
: TRouteMap[TName] extends { readonly path: infer P extends string }
|
|
134
|
+
? ExtractParamsFromPattern<P>
|
|
135
|
+
: {}
|
|
136
|
+
: {};
|
|
137
|
+
|
|
138
|
+
/** Parse "a|b|c" into "a" | "b" | "c" */
|
|
139
|
+
type ParseConstraint<T extends string> =
|
|
140
|
+
T extends `${infer First}|${infer Rest}` ? First | ParseConstraint<Rest> : T;
|
|
130
141
|
|
|
131
142
|
/** Minimal inline param extraction (avoids importing from types.ts to prevent circular deps). */
|
|
132
143
|
type ExtractParamsFromPattern<T extends string> =
|
|
133
144
|
T extends `${string}:${infer Param}/${infer Rest}`
|
|
134
|
-
? Param extends `${infer Name}?`
|
|
135
|
-
? {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
145
|
+
? Param extends `${infer Name}(${infer C})?`
|
|
146
|
+
? {
|
|
147
|
+
[K in Name]?: ParseConstraint<C>;
|
|
148
|
+
} & ExtractParamsFromPattern<`/${Rest}`>
|
|
149
|
+
: Param extends `${infer Name}(${infer C})`
|
|
150
|
+
? {
|
|
151
|
+
[K in Name]: ParseConstraint<C>;
|
|
152
|
+
} & ExtractParamsFromPattern<`/${Rest}`>
|
|
153
|
+
: Param extends `${infer Name}?`
|
|
154
|
+
? { [K in Name]?: string } & ExtractParamsFromPattern<`/${Rest}`>
|
|
155
|
+
: { [K in Param]: string } & ExtractParamsFromPattern<`/${Rest}`>
|
|
139
156
|
: T extends `${string}:${infer Param}`
|
|
140
|
-
? Param extends `${infer Name}?`
|
|
141
|
-
? { [K in Name]?:
|
|
142
|
-
: Param extends `${infer Name}(${
|
|
143
|
-
? { [K in Name]:
|
|
144
|
-
:
|
|
157
|
+
? Param extends `${infer Name}(${infer C})?`
|
|
158
|
+
? { [K in Name]?: ParseConstraint<C> }
|
|
159
|
+
: Param extends `${infer Name}(${infer C})`
|
|
160
|
+
? { [K in Name]: ParseConstraint<C> }
|
|
161
|
+
: Param extends `${infer Name}?`
|
|
162
|
+
? { [K in Name]?: string }
|
|
163
|
+
: { [K in Param]: string }
|
|
145
164
|
: {};
|
|
146
165
|
|
|
147
166
|
// ============================================================================
|
|
@@ -155,7 +174,9 @@ type ExtractParamsFromPattern<T extends string> =
|
|
|
155
174
|
* - `"number"` / `"number?"` - coerced via `Number()`; NaN treated as missing
|
|
156
175
|
* - `"boolean"` / `"boolean?"` - `"true"` / `"1"` -> true, `"false"` / `"0"` / `""` -> false
|
|
157
176
|
*
|
|
158
|
-
* Missing
|
|
177
|
+
* Missing params (both required and optional) are omitted from the result
|
|
178
|
+
* (undefined). The required/optional distinction is a consumer-facing contract
|
|
179
|
+
* only — the handler must check for undefined.
|
|
159
180
|
*/
|
|
160
181
|
export function parseSearchParams<T extends SearchSchema>(
|
|
161
182
|
searchParams: URLSearchParams,
|
|
@@ -169,13 +190,7 @@ export function parseSearchParams<T extends SearchSchema>(
|
|
|
169
190
|
const raw = searchParams.get(key);
|
|
170
191
|
|
|
171
192
|
if (raw === null) {
|
|
172
|
-
|
|
173
|
-
// Required param missing: use zero value
|
|
174
|
-
if (baseType === "string") result[key] = "";
|
|
175
|
-
else if (baseType === "number") result[key] = 0;
|
|
176
|
-
else if (baseType === "boolean") result[key] = false;
|
|
177
|
-
}
|
|
178
|
-
// Optional params are omitted (undefined)
|
|
193
|
+
// Missing params are omitted (undefined) regardless of required/optional
|
|
179
194
|
continue;
|
|
180
195
|
}
|
|
181
196
|
|
|
@@ -183,11 +198,10 @@ export function parseSearchParams<T extends SearchSchema>(
|
|
|
183
198
|
result[key] = raw;
|
|
184
199
|
} else if (baseType === "number") {
|
|
185
200
|
const num = Number(raw);
|
|
186
|
-
if (Number.isNaN(num)) {
|
|
187
|
-
if (!isOptional) result[key] = 0;
|
|
188
|
-
} else {
|
|
201
|
+
if (!Number.isNaN(num)) {
|
|
189
202
|
result[key] = num;
|
|
190
203
|
}
|
|
204
|
+
// NaN treated as missing (undefined)
|
|
191
205
|
} else if (baseType === "boolean") {
|
|
192
206
|
result[key] = raw === "true" || raw === "1";
|
|
193
207
|
}
|
|
@@ -204,9 +218,7 @@ export function parseSearchParams<T extends SearchSchema>(
|
|
|
204
218
|
* Serialize a typed search params object to a query string (without leading `?`).
|
|
205
219
|
* Skips `undefined` and `null` values.
|
|
206
220
|
*/
|
|
207
|
-
export function serializeSearchParams(
|
|
208
|
-
params: Record<string, unknown>,
|
|
209
|
-
): string {
|
|
221
|
+
export function serializeSearchParams(params: Record<string, unknown>): string {
|
|
210
222
|
const parts: string[] = [];
|
|
211
223
|
for (const [key, value] of Object.entries(params)) {
|
|
212
224
|
if (value === undefined || value === null) continue;
|
package/src/segment-system.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as React from "react";
|
|
1
2
|
import { createElement, type ReactNode, type ComponentType } from "react";
|
|
2
3
|
import { OutletProvider } from "./client.js";
|
|
3
4
|
import { MountContextProvider } from "./browser/react/mount-context.js";
|
|
@@ -14,6 +15,66 @@ import {
|
|
|
14
15
|
} from "./route-content-wrapper.js";
|
|
15
16
|
import { RootErrorBoundary } from "./root-error-boundary.js";
|
|
16
17
|
|
|
18
|
+
// ViewTransition is only available in React experimental.
|
|
19
|
+
// Access via namespace import to avoid compile-time errors on stable React.
|
|
20
|
+
const ReactViewTransition: any =
|
|
21
|
+
"ViewTransition" in React ? (React as any).ViewTransition : null;
|
|
22
|
+
|
|
23
|
+
function restoreParallelLoaderMarkers(
|
|
24
|
+
segments: ResolvedSegment[],
|
|
25
|
+
): ResolvedSegment[] {
|
|
26
|
+
const parallelLoadingByNamespace = new Map<string, ReactNode>();
|
|
27
|
+
let nextSegments: ResolvedSegment[] | null = null;
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < segments.length; i++) {
|
|
30
|
+
const segment = segments[i];
|
|
31
|
+
|
|
32
|
+
if (segment.type === "parallel") {
|
|
33
|
+
if (
|
|
34
|
+
segment.namespace &&
|
|
35
|
+
segment.loading !== undefined &&
|
|
36
|
+
segment.loading !== null &&
|
|
37
|
+
segment.loading !== false
|
|
38
|
+
) {
|
|
39
|
+
parallelLoadingByNamespace.set(segment.namespace, segment.loading);
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (segment.type !== "loader" || segment.parallelLoading !== undefined) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const parallelLoading = segment.namespace
|
|
49
|
+
? parallelLoadingByNamespace.get(segment.namespace)
|
|
50
|
+
: undefined;
|
|
51
|
+
if (parallelLoading === undefined) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!nextSegments) {
|
|
56
|
+
nextSegments = segments.slice();
|
|
57
|
+
}
|
|
58
|
+
nextSegments[i] = { ...segment, parallelLoading };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return nextSegments ?? segments;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function hasSameReferences(a: unknown[] | undefined, b: unknown[]): boolean {
|
|
65
|
+
if (!a || a.length !== b.length) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < a.length; i++) {
|
|
70
|
+
if (a[i] !== b[i]) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
17
78
|
/**
|
|
18
79
|
* Resolve loader data from raw results, unwrapping LoaderDataResult wrappers
|
|
19
80
|
*/
|
|
@@ -137,6 +198,10 @@ export async function renderSegments(
|
|
|
137
198
|
} = options || {};
|
|
138
199
|
|
|
139
200
|
const temporalLazyRefs: Promise<any>[] = [];
|
|
201
|
+
const normalizedSegments = restoreParallelLoaderMarkers(segments);
|
|
202
|
+
const normalizedInterceptSegments = interceptSegments
|
|
203
|
+
? restoreParallelLoaderMarkers(interceptSegments)
|
|
204
|
+
: undefined;
|
|
140
205
|
|
|
141
206
|
/**
|
|
142
207
|
* Registers promises from lazy/async components for awaiting.
|
|
@@ -161,7 +226,7 @@ export async function renderSegments(
|
|
|
161
226
|
);
|
|
162
227
|
}
|
|
163
228
|
// Separate segments by type, passing intercept segments for explicit injection
|
|
164
|
-
const tree = segmentTreeWalk(
|
|
229
|
+
const tree = segmentTreeWalk(normalizedSegments, normalizedInterceptSegments);
|
|
165
230
|
// Render content segments as siblings
|
|
166
231
|
let content: ReactNode = null;
|
|
167
232
|
for (const node of tree) {
|
|
@@ -210,7 +275,7 @@ export async function renderSegments(
|
|
|
210
275
|
}
|
|
211
276
|
|
|
212
277
|
let nodeContent: ReactNode =
|
|
213
|
-
loading !== null && loading
|
|
278
|
+
loading !== null && loading !== undefined && loading !== false
|
|
214
279
|
? createElement(RouteContentWrapper, {
|
|
215
280
|
key: `suspense-loading-${id}`,
|
|
216
281
|
content:
|
|
@@ -221,6 +286,19 @@ export async function renderSegments(
|
|
|
221
286
|
segmentId: id,
|
|
222
287
|
})
|
|
223
288
|
: registerLazyRef(resolvedComponent);
|
|
289
|
+
|
|
290
|
+
// Wrap with <ViewTransition> if transition config exists (React experimental only).
|
|
291
|
+
// An empty config ({}) creates a bare <ViewTransition> boundary that participates
|
|
292
|
+
// in transitions without adding custom animation classes. Named element-level
|
|
293
|
+
// <ViewTransition> components inside (with name/share props) morph independently
|
|
294
|
+
// from the parent's default cross-fade.
|
|
295
|
+
if (ReactViewTransition && node.segment.transition) {
|
|
296
|
+
nodeContent = createElement(ReactViewTransition, {
|
|
297
|
+
...node.segment.transition,
|
|
298
|
+
children: nodeContent,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
224
302
|
// Common props for OutletProvider
|
|
225
303
|
const outletContent: ReactNode =
|
|
226
304
|
node.segment.type === "layout" ? content : null;
|
|
@@ -265,13 +343,90 @@ export async function renderSegments(
|
|
|
265
343
|
children: nodeContent,
|
|
266
344
|
});
|
|
267
345
|
} else {
|
|
268
|
-
// Has loaders but no loading skeleton
|
|
269
|
-
|
|
346
|
+
// Has loaders but no loading skeleton.
|
|
347
|
+
// Split: parallel-owned loaders stream (their parallel has loading()),
|
|
348
|
+
// layout-owned loaders are awaited (they gate the layout content).
|
|
349
|
+
const layoutLoaders = loaderEntries.filter((l) => !l.parallelLoading);
|
|
350
|
+
const parallelOwnedLoaders = loaderEntries.filter(
|
|
351
|
+
(l) => !!l.parallelLoading,
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
// Await only layout-owned loaders
|
|
355
|
+
const layoutLoaderIds = layoutLoaders.map((l) => l.loaderId!);
|
|
356
|
+
const layoutLoaderDataPromise =
|
|
357
|
+
layoutLoaders.length > 0
|
|
358
|
+
? Promise.all(
|
|
359
|
+
layoutLoaders.map((l) =>
|
|
360
|
+
l.loaderData instanceof Promise
|
|
361
|
+
? l.loaderData
|
|
362
|
+
: Promise.resolve(l.loaderData),
|
|
363
|
+
),
|
|
364
|
+
)
|
|
365
|
+
: Promise.resolve([]);
|
|
366
|
+
const resolvedData = await layoutLoaderDataPromise;
|
|
270
367
|
const { loaderData, errorFallback } = resolveLoaderData(
|
|
271
368
|
resolvedData,
|
|
272
|
-
|
|
369
|
+
layoutLoaderIds,
|
|
273
370
|
);
|
|
274
371
|
|
|
372
|
+
// Parallel-owned loaders: attach to their owning parallel segment
|
|
373
|
+
// as loaderDataPromise so ParallelOutlet wraps in LoaderBoundary
|
|
374
|
+
if (parallelOwnedLoaders.length > 0) {
|
|
375
|
+
const loadersByParallelNamespace = new Map<string, ResolvedSegment[]>();
|
|
376
|
+
|
|
377
|
+
for (const loader of parallelOwnedLoaders) {
|
|
378
|
+
if (!loader.namespace) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const existing = loadersByParallelNamespace.get(loader.namespace);
|
|
382
|
+
if (existing) {
|
|
383
|
+
existing.push(loader);
|
|
384
|
+
} else {
|
|
385
|
+
loadersByParallelNamespace.set(loader.namespace, [loader]);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
for (const p of node.parallel) {
|
|
390
|
+
if (!p.loading || !p.namespace) {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const ownedLoaders = loadersByParallelNamespace.get(p.namespace);
|
|
395
|
+
if (!ownedLoaders || ownedLoaders.length === 0) {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const parallelLoaderIds = ownedLoaders.map((l) => l.loaderId!);
|
|
400
|
+
const parallelLoaderSources = ownedLoaders.map((l) => l.loaderData);
|
|
401
|
+
p.loaderIds = parallelLoaderIds;
|
|
402
|
+
|
|
403
|
+
const shouldReuseParallelPromise =
|
|
404
|
+
p.loaderDataPromise !== undefined &&
|
|
405
|
+
hasSameReferences(p.parallelLoaderSources, parallelLoaderSources);
|
|
406
|
+
|
|
407
|
+
const parallelLoaderDataPromise = shouldReuseParallelPromise
|
|
408
|
+
? p.loaderDataPromise
|
|
409
|
+
: forceAwait || isAction
|
|
410
|
+
? await Promise.all(
|
|
411
|
+
ownedLoaders.map((l) =>
|
|
412
|
+
l.loaderData instanceof Promise
|
|
413
|
+
? l.loaderData
|
|
414
|
+
: Promise.resolve(l.loaderData),
|
|
415
|
+
),
|
|
416
|
+
)
|
|
417
|
+
: Promise.all(
|
|
418
|
+
ownedLoaders.map((l) =>
|
|
419
|
+
l.loaderData instanceof Promise
|
|
420
|
+
? l.loaderData
|
|
421
|
+
: Promise.resolve(l.loaderData),
|
|
422
|
+
),
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
p.loaderDataPromise = parallelLoaderDataPromise;
|
|
426
|
+
p.parallelLoaderSources = parallelLoaderSources;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
275
430
|
content = createElement(OutletProvider, {
|
|
276
431
|
key,
|
|
277
432
|
content: outletContent,
|
|
@@ -407,20 +562,13 @@ function* segmentTreeWalk(
|
|
|
407
562
|
}
|
|
408
563
|
}
|
|
409
564
|
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
//
|
|
413
|
-
//
|
|
414
|
-
nonParallels.sort((a, b) => {
|
|
415
|
-
if (a.id.length !== b.id.length) {
|
|
416
|
-
return a.id.length - b.id.length;
|
|
417
|
-
}
|
|
418
|
-
return a.id.localeCompare(b.id);
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
// Iterate bottom-to-top using reverse() to process leaf segments first
|
|
565
|
+
// Segments arrive in root-to-leaf order from the server (resolveSegment
|
|
566
|
+
// and resolveSegmentWithRevalidation push segments in this order).
|
|
567
|
+
// All consumers (reconcileSegments, cache) preserve this order.
|
|
568
|
+
// No sorting needed — iterate bottom-to-top to process leaf segments first.
|
|
422
569
|
// This processes route/leaf layouts first, then parent layouts
|
|
423
570
|
// Note: We reverse the array to iterate from end to start (bottom-to-top)
|
|
571
|
+
|
|
424
572
|
for (let i = nonParallels.length - 1; i >= 0; i--) {
|
|
425
573
|
const segment = nonParallels[i];
|
|
426
574
|
|