@rangojs/router 0.0.0-experimental.10 → 0.0.0-experimental.100
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 +1037 -4
- package/dist/bin/rango.js +1619 -157
- package/dist/vite/index.js +5762 -2301
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +71 -63
- package/skills/breadcrumbs/SKILL.md +252 -0
- package/skills/cache-guide/SKILL.md +294 -0
- package/skills/caching/SKILL.md +93 -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/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +367 -71
- package/skills/host-router/SKILL.md +218 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +176 -8
- package/skills/layout/SKILL.md +124 -3
- package/skills/links/SKILL.md +304 -25
- package/skills/loader/SKILL.md +474 -47
- package/skills/middleware/SKILL.md +207 -37
- package/skills/migrate-nextjs/SKILL.md +562 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +15 -11
- package/skills/parallel/SKILL.md +272 -1
- package/skills/prerender/SKILL.md +467 -65
- package/skills/rango/SKILL.md +89 -21
- package/skills/response-routes/SKILL.md +152 -91
- package/skills/route/SKILL.md +305 -14
- package/skills/router-setup/SKILL.md +210 -32
- package/skills/server-actions/SKILL.md +739 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +333 -86
- package/skills/use-cache/SKILL.md +324 -0
- package/skills/view-transitions/SKILL.md +212 -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/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +136 -68
- 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 +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +374 -561
- package/src/browser/navigation-client.ts +228 -70
- package/src/browser/navigation-store.ts +97 -55
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +376 -315
- package/src/browser/prefetch/cache.ts +314 -0
- package/src/browser/prefetch/fetch.ts +282 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +191 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +152 -0
- package/src/browser/react/Link.tsx +255 -71
- package/src/browser/react/NavigationProvider.tsx +152 -24
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +55 -0
- package/src/browser/react/index.ts +15 -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 +30 -120
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +44 -65
- package/src/browser/react/use-params.ts +78 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-reverse.ts +99 -0
- package/src/browser/react/use-router.ts +83 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +85 -99
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +246 -64
- package/src/browser/scroll-restoration.ts +127 -52
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +510 -603
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +158 -48
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +84 -23
- package/src/build/generate-route-types.ts +39 -828
- package/src/build/index.ts +4 -5
- package/src/build/route-trie.ts +85 -32
- 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 +418 -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 +618 -0
- package/src/build/route-types/scan-filter.ts +85 -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 +167 -307
- package/src/cache/cf/cf-cache-store.ts +573 -21
- 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 +153 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +6 -1
- package/src/client.tsx +118 -302
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +156 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +77 -7
- package/src/handle.ts +55 -10
- 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 +138 -21
- package/src/index.ts +206 -51
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +25 -143
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +4 -2
- package/src/prerender/store.ts +159 -13
- package/src/prerender.ts +397 -29
- package/src/response-utils.ts +28 -0
- package/src/reverse.ts +231 -121
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +1134 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +483 -0
- package/src/route-definition/index.ts +55 -0
- package/src/route-definition/redirect.ts +101 -0
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-definition.ts +1 -1431
- package/src/route-map-builder.ts +162 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +66 -9
- package/src/router/content-negotiation.ts +215 -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 +418 -86
- package/src/router/intercept-resolution.ts +35 -20
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +359 -128
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +98 -32
- package/src/router/match-api.ts +196 -261
- package/src/router/match-context.ts +4 -2
- package/src/router/match-handlers.ts +441 -0
- package/src/router/match-middleware/background-revalidation.ts +108 -93
- package/src/router/match-middleware/cache-lookup.ts +415 -86
- package/src/router/match-middleware/cache-store.ts +91 -29
- package/src/router/match-middleware/intercept-resolution.ts +48 -21
- package/src/router/match-middleware/segment-resolution.ts +73 -9
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +154 -35
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +209 -0
- package/src/router/middleware.ts +373 -371
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +292 -52
- package/src/router/prerender-match.ts +502 -0
- package/src/router/preview-match.ts +98 -0
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +152 -39
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +41 -21
- package/src/router/router-interfaces.ts +484 -0
- package/src/router/router-options.ts +618 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +756 -0
- package/src/router/segment-resolution/helpers.ts +268 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1407 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -1315
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/substitute-pattern-params.ts +56 -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 +111 -39
- package/src/router/types.ts +17 -9
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +642 -2011
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +864 -1114
- package/src/rsc/helpers.ts +181 -19
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +229 -0
- package/src/rsc/manifest-init.ts +90 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +395 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +360 -0
- package/src/rsc/rsc-rendering.ts +256 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +360 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +52 -11
- package/src/search-params.ts +230 -0
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +187 -38
- package/src/server/context.ts +333 -59
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +113 -15
- package/src/server/loader-registry.ts +24 -64
- package/src/server/request-context.ts +603 -109
- package/src/server.ts +35 -155
- package/src/ssr/index.tsx +107 -30
- package/src/static-handler.ts +126 -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 +764 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +209 -0
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +120 -0
- package/src/types/segments.ts +167 -0
- package/src/types.ts +1 -1757
- package/src/urls/include-helper.ts +207 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +372 -0
- package/src/urls/path-helper.ts +364 -0
- package/src/urls/pattern-types.ts +107 -0
- package/src/urls/response-types.ts +108 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -1282
- package/src/use-loader.tsx +161 -81
- package/src/vite/debug.ts +184 -0
- package/src/vite/discovery/bundle-postprocess.ts +181 -0
- package/src/vite/discovery/discover-routers.ts +376 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +486 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +73 -0
- package/src/vite/discovery/state.ts +117 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +15 -2063
- package/src/vite/plugin-types.ts +103 -0
- package/src/vite/plugins/cjs-to-esm.ts +98 -0
- package/src/vite/plugins/client-ref-dedup.ts +131 -0
- package/src/vite/plugins/client-ref-hashing.ts +117 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
- package/src/vite/plugins/expose-id-utils.ts +299 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +816 -0
- package/src/vite/plugins/performance-tracks.ts +96 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +336 -0
- package/src/vite/plugins/version-injector.ts +109 -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 +497 -0
- package/src/vite/router-discovery.ts +1423 -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/utils/package-resolution.ts +161 -0
- package/src/vite/utils/prerender-utils.ts +222 -0
- package/src/vite/utils/shared-utils.ts +170 -0
- package/CLAUDE.md +0 -43
- 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/urls.gen.ts +0 -8
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- package/src/vite/expose-prerender-handler-id.ts +0 -429
- package/src/vite/package-resolution.ts +0 -125
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
package/src/reverse.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { ExtractParams } from "./types.js";
|
|
2
|
+
import type { SearchSchema, ResolveSearchSchema } from "./search-params.js";
|
|
3
|
+
import { serializeSearchParams } from "./search-params.js";
|
|
4
|
+
import { substitutePatternParams } from "./router/substitute-pattern-params.js";
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* Sanitize prefix string by removing leading slash
|
|
@@ -8,98 +11,55 @@ export type SanitizePrefix<T extends string> = T extends `/${infer P}` ? P : T;
|
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* Helper type to merge multiple route definitions into a single accumulated type.
|
|
11
|
-
* Note: When using createRouter, types accumulate automatically through the
|
|
12
|
-
* builder chain, so this type is typically not needed.
|
|
13
14
|
*
|
|
14
15
|
* @example
|
|
15
16
|
* ```typescript
|
|
16
|
-
*
|
|
17
|
-
* type AppRoutes = MergeRoutes<[
|
|
18
|
-
* typeof homeRoutes,
|
|
19
|
-
* PrefixRoutePatterns<typeof blogRoutes, "/blog">,
|
|
20
|
-
* ]>;
|
|
21
|
-
*
|
|
22
|
-
* // Preferred: Let router accumulate types automatically
|
|
23
|
-
* const router = createRouter<AppEnv>()
|
|
24
|
-
* .routes(homeRoutes).map(...)
|
|
25
|
-
* .routes("/blog", blogRoutes).map(...);
|
|
26
|
-
* type AppRoutes = typeof router.routeMap;
|
|
17
|
+
* type AppRoutes = MergeRoutes<[typeof siteRoutes, typeof apiRoutes]>;
|
|
27
18
|
* ```
|
|
28
19
|
*/
|
|
29
20
|
export type MergeRoutes<T extends unknown[]> = T extends [
|
|
30
21
|
infer First,
|
|
31
|
-
...infer Rest
|
|
22
|
+
...infer Rest,
|
|
32
23
|
]
|
|
33
24
|
? First & MergeRoutes<Rest>
|
|
34
25
|
: {};
|
|
35
26
|
|
|
36
|
-
/**
|
|
37
|
-
* Add key prefix to all entries in a route map
|
|
38
|
-
* { "cart": "/cart" } with prefix "shop" -> { "shop.cart": "/shop/cart" }
|
|
39
|
-
*/
|
|
40
|
-
export type PrefixRouteKeys<
|
|
41
|
-
T,
|
|
42
|
-
Prefix extends string
|
|
43
|
-
> = Prefix extends ""
|
|
44
|
-
? T
|
|
45
|
-
: { [K in keyof T as `${Prefix}.${K & string}`]: T[K] };
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Add path prefix to all patterns in a route map
|
|
49
|
-
* { "cart": "/cart" } with prefix "/shop" -> { "cart": "/shop/cart" }
|
|
50
|
-
*/
|
|
51
|
-
export type PrefixRoutePatterns<
|
|
52
|
-
T,
|
|
53
|
-
PathPrefix extends string
|
|
54
|
-
> = {
|
|
55
|
-
[K in keyof T]: PathPrefix extends "" | "/"
|
|
56
|
-
? T[K]
|
|
57
|
-
: T[K] extends "/"
|
|
58
|
-
? PathPrefix
|
|
59
|
-
: T[K] extends string
|
|
60
|
-
? `${PathPrefix}${T[K]}`
|
|
61
|
-
: T[K];
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Combined: prefix both keys and patterns
|
|
66
|
-
* Used for module augmentation registration
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* ```typescript
|
|
70
|
-
* // Given shopRoutes = { "index": "/", "cart": "/cart", "products.detail": "/product/:slug" }
|
|
71
|
-
* // PrefixedRoutes<typeof shopRoutes, "shop"> produces:
|
|
72
|
-
* // { "shop.index": "/shop", "shop.cart": "/shop/cart", "shop.products.detail": "/shop/product/:slug" }
|
|
73
|
-
* ```
|
|
74
|
-
*/
|
|
75
|
-
export type PrefixedRoutes<
|
|
76
|
-
T,
|
|
77
|
-
KeyPrefix extends string,
|
|
78
|
-
PathPrefix extends string = KeyPrefix extends "" ? "" : `/${KeyPrefix}`
|
|
79
|
-
> = PrefixRouteKeys<PrefixRoutePatterns<T, PathPrefix>, KeyPrefix>;
|
|
80
|
-
|
|
81
27
|
/**
|
|
82
28
|
* Helper to safely extract route patterns from a routes object
|
|
83
29
|
* Handles string values, { path, response } objects, and interface types (like RegisteredRoutes)
|
|
84
30
|
*/
|
|
85
|
-
type RoutePatternFor<
|
|
86
|
-
TRoutes
|
|
87
|
-
|
|
88
|
-
|
|
31
|
+
type RoutePatternFor<
|
|
32
|
+
TRoutes,
|
|
33
|
+
TName extends keyof TRoutes,
|
|
34
|
+
> = TRoutes[TName] extends string
|
|
35
|
+
? TRoutes[TName]
|
|
36
|
+
: TRoutes[TName] extends { readonly path: infer P extends string }
|
|
37
|
+
? P
|
|
38
|
+
: string;
|
|
89
39
|
|
|
90
40
|
/**
|
|
91
41
|
* Extract params type for a route
|
|
92
42
|
*/
|
|
93
|
-
export type ParamsFor<
|
|
94
|
-
TRoutes,
|
|
95
|
-
|
|
96
|
-
> = ExtractParams<RoutePatternFor<TRoutes, TName>>;
|
|
43
|
+
export type ParamsFor<TRoutes, TName extends keyof TRoutes> = ExtractParams<
|
|
44
|
+
RoutePatternFor<TRoutes, TName>
|
|
45
|
+
>;
|
|
97
46
|
|
|
98
47
|
/**
|
|
99
48
|
* Check if an object type has any keys
|
|
100
49
|
*/
|
|
101
50
|
type IsEmptyObject<T> = keyof T extends never ? true : false;
|
|
102
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Extract search schema from a route entry.
|
|
54
|
+
* Returns {} if no search schema is defined.
|
|
55
|
+
*/
|
|
56
|
+
type ExtractSearchSchema<
|
|
57
|
+
TRoutes,
|
|
58
|
+
TName extends keyof TRoutes,
|
|
59
|
+
> = TRoutes[TName] extends { readonly search: infer S extends SearchSchema }
|
|
60
|
+
? S
|
|
61
|
+
: {};
|
|
62
|
+
|
|
103
63
|
/**
|
|
104
64
|
* Type-safe reverse function signature (Django-style URL reversal)
|
|
105
65
|
*
|
|
@@ -117,7 +77,11 @@ export type ReverseFunction<TRoutes> = {
|
|
|
117
77
|
* Route without params - validates route name exists
|
|
118
78
|
*/
|
|
119
79
|
<TName extends keyof TRoutes & string>(
|
|
120
|
-
name: IsEmptyObject<
|
|
80
|
+
name: IsEmptyObject<
|
|
81
|
+
ExtractParams<RoutePatternFor<TRoutes, TName>>
|
|
82
|
+
> extends true
|
|
83
|
+
? TName
|
|
84
|
+
: never,
|
|
121
85
|
): string;
|
|
122
86
|
|
|
123
87
|
/**
|
|
@@ -125,69 +89,193 @@ export type ReverseFunction<TRoutes> = {
|
|
|
125
89
|
*/
|
|
126
90
|
<TName extends keyof TRoutes & string>(
|
|
127
91
|
name: TName,
|
|
128
|
-
params: ExtractParams<RoutePatternFor<TRoutes, TName
|
|
92
|
+
params: ExtractParams<RoutePatternFor<TRoutes, TName>>,
|
|
93
|
+
): string;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Route with params and search - validates route name, params, and search
|
|
97
|
+
*/
|
|
98
|
+
<TName extends keyof TRoutes & string>(
|
|
99
|
+
name: TName,
|
|
100
|
+
params: ExtractParams<RoutePatternFor<TRoutes, TName>>,
|
|
101
|
+
search: ResolveSearchSchema<ExtractSearchSchema<TRoutes, TName>>,
|
|
102
|
+
): string;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Dot-prefixed route without params - strictly local resolution
|
|
106
|
+
*/
|
|
107
|
+
<TName extends keyof TRoutes & string>(
|
|
108
|
+
name: IsEmptyObject<
|
|
109
|
+
ExtractParams<RoutePatternFor<TRoutes, TName>>
|
|
110
|
+
> extends true
|
|
111
|
+
? `.${TName}`
|
|
112
|
+
: never,
|
|
113
|
+
): string;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Dot-prefixed route with params - strictly local resolution
|
|
117
|
+
*/
|
|
118
|
+
<TName extends keyof TRoutes & string>(
|
|
119
|
+
name: `.${TName}`,
|
|
120
|
+
params: ExtractParams<RoutePatternFor<TRoutes, TName>>,
|
|
121
|
+
): string;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Dot-prefixed route with params and search - strictly local resolution
|
|
125
|
+
*/
|
|
126
|
+
<TName extends keyof TRoutes & string>(
|
|
127
|
+
name: `.${TName}`,
|
|
128
|
+
params: ExtractParams<RoutePatternFor<TRoutes, TName>>,
|
|
129
|
+
search: ResolveSearchSchema<ExtractSearchSchema<TRoutes, TName>>,
|
|
129
130
|
): string;
|
|
130
131
|
};
|
|
131
132
|
|
|
132
133
|
/**
|
|
133
|
-
* Type-safe scoped reverse function
|
|
134
|
+
* Type-safe scoped reverse function with separate local and global namespaces.
|
|
134
135
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* Path-based URLs (`/...`) are an escape hatch with no validation.
|
|
136
|
+
* - `.name` — local resolution within the current include() scope
|
|
137
|
+
* - `name` — global resolution against the named-routes definition
|
|
138
138
|
*
|
|
139
139
|
* @example
|
|
140
140
|
* ```typescript
|
|
141
|
-
*
|
|
142
|
-
* reverse("
|
|
143
|
-
* reverse("
|
|
144
|
-
*
|
|
145
|
-
* //
|
|
146
|
-
* reverse("
|
|
147
|
-
* reverse("/typo/in/path") // ⚠ Won't catch errors
|
|
141
|
+
* reverse(".article", { slug: "hello" }) // ✓ Local route (resolves with mount prefix)
|
|
142
|
+
* reverse(".index") // ✓ Local route (no params)
|
|
143
|
+
* reverse("magazine.index") // ✓ Global route (fully qualified)
|
|
144
|
+
* reverse("blog.post", { slug: "hello" }) // ✓ Global route + params
|
|
145
|
+
* reverse(".typo") // ✗ Compile error (not in local routes)
|
|
146
|
+
* reverse("typo") // ✗ Compile error (not in global routes)
|
|
148
147
|
* ```
|
|
149
148
|
*/
|
|
150
|
-
export type ScopedReverseFunction<
|
|
149
|
+
export type ScopedReverseFunction<
|
|
150
|
+
TLocalRoutes,
|
|
151
|
+
TGlobalRoutes = TLocalRoutes,
|
|
152
|
+
> = {
|
|
151
153
|
/**
|
|
152
|
-
*
|
|
153
|
-
* @recommended Use this for type-safe URL generation
|
|
154
|
+
* Global route without params
|
|
154
155
|
*/
|
|
155
|
-
<TName extends keyof
|
|
156
|
-
name: IsEmptyObject<
|
|
156
|
+
<TName extends keyof TGlobalRoutes & string>(
|
|
157
|
+
name: IsEmptyObject<
|
|
158
|
+
ExtractParams<RoutePatternFor<TGlobalRoutes, TName>>
|
|
159
|
+
> extends true
|
|
160
|
+
? TName
|
|
161
|
+
: never,
|
|
157
162
|
): string;
|
|
158
163
|
|
|
159
164
|
/**
|
|
160
|
-
*
|
|
161
|
-
* @recommended Use this for type-safe URL generation with parameters
|
|
165
|
+
* Global route with params
|
|
162
166
|
*/
|
|
163
|
-
<TName extends keyof
|
|
167
|
+
<TName extends keyof TGlobalRoutes & string>(
|
|
168
|
+
name: TName,
|
|
169
|
+
params: ExtractParams<RoutePatternFor<TGlobalRoutes, TName>>,
|
|
170
|
+
): string;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Global route with params and search
|
|
174
|
+
*/
|
|
175
|
+
<TName extends keyof TGlobalRoutes & string>(
|
|
164
176
|
name: TName,
|
|
165
|
-
params: ExtractParams<RoutePatternFor<
|
|
177
|
+
params: ExtractParams<RoutePatternFor<TGlobalRoutes, TName>>,
|
|
178
|
+
search: ResolveSearchSchema<ExtractSearchSchema<TGlobalRoutes, TName>>,
|
|
166
179
|
): string;
|
|
167
180
|
|
|
168
181
|
/**
|
|
169
|
-
*
|
|
170
|
-
* Use for cross-module navigation: "shop.cart", "blog.post"
|
|
182
|
+
* Dot-prefixed local route without params
|
|
171
183
|
*/
|
|
172
|
-
|
|
184
|
+
<TName extends keyof TLocalRoutes & string>(
|
|
185
|
+
name: IsEmptyObject<
|
|
186
|
+
ExtractParams<RoutePatternFor<TLocalRoutes, TName>>
|
|
187
|
+
> extends true
|
|
188
|
+
? `.${TName}`
|
|
189
|
+
: never,
|
|
190
|
+
): string;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Dot-prefixed local route with params
|
|
194
|
+
*/
|
|
195
|
+
<TName extends keyof TLocalRoutes & string>(
|
|
196
|
+
name: `.${TName}`,
|
|
197
|
+
params: ExtractParams<RoutePatternFor<TLocalRoutes, TName>>,
|
|
198
|
+
): string;
|
|
173
199
|
|
|
174
200
|
/**
|
|
175
|
-
*
|
|
176
|
-
* Prefer route names for type safety. Only use paths when necessary.
|
|
201
|
+
* Dot-prefixed local route with params and search
|
|
177
202
|
*/
|
|
178
|
-
|
|
203
|
+
<TName extends keyof TLocalRoutes & string>(
|
|
204
|
+
name: `.${TName}`,
|
|
205
|
+
params: ExtractParams<RoutePatternFor<TLocalRoutes, TName>>,
|
|
206
|
+
search: ResolveSearchSchema<ExtractSearchSchema<TLocalRoutes, TName>>,
|
|
207
|
+
): string;
|
|
179
208
|
};
|
|
180
209
|
|
|
181
210
|
/**
|
|
182
211
|
* Extract local routes type from UrlPatterns
|
|
183
212
|
* Used with scopedReverse() to get the routes type from patterns
|
|
184
213
|
*/
|
|
185
|
-
export type ExtractLocalRoutes<TPatterns> =
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
214
|
+
export type ExtractLocalRoutes<TPatterns> = TPatterns extends {
|
|
215
|
+
readonly _routes?: infer TRoutes;
|
|
216
|
+
}
|
|
217
|
+
? TRoutes
|
|
218
|
+
: TPatterns extends Record<string, string>
|
|
219
|
+
? TPatterns
|
|
220
|
+
: Record<string, string>;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Params accepted by `useReverse(routes)`. The route's own params are
|
|
224
|
+
* required, and additional string keys are permitted so callers can
|
|
225
|
+
* override values that would otherwise be auto-filled from the matched
|
|
226
|
+
* route's `useParams()` (e.g. an enclosing `:tenantId` mount segment).
|
|
227
|
+
*/
|
|
228
|
+
export type LocalReverseParams<TPattern extends string> =
|
|
229
|
+
ExtractParams<TPattern> & {
|
|
230
|
+
readonly [extra: string]: string | undefined;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Type-safe local reverse function with dot-prefixed names only.
|
|
235
|
+
*
|
|
236
|
+
* Returned by `useReverse(routes)` on the client. The route map is the
|
|
237
|
+
* exposure boundary (a generated `routes` from a `urls()` module) and the
|
|
238
|
+
* scope is implicit from that import — there is no global namespace, so
|
|
239
|
+
* names must be dot-prefixed to mirror `ctx.reverse(".name")`.
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```typescript
|
|
243
|
+
* const reverse = useReverse(blogRoutes);
|
|
244
|
+
* reverse(".index"); // ✓ no params
|
|
245
|
+
* reverse(".post", { postId: "hello" }); // ✓ with params
|
|
246
|
+
* reverse(".search", {}, { q: "hi" }); // ✓ with search schema
|
|
247
|
+
* reverse(".typo"); // ✗ compile error
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
export type LocalReverseFunction<TLocalRoutes> = {
|
|
251
|
+
/**
|
|
252
|
+
* Dot-prefixed local route without params
|
|
253
|
+
*/
|
|
254
|
+
<TName extends keyof TLocalRoutes & string>(
|
|
255
|
+
name: IsEmptyObject<
|
|
256
|
+
ExtractParams<RoutePatternFor<TLocalRoutes, TName>>
|
|
257
|
+
> extends true
|
|
258
|
+
? `.${TName}`
|
|
259
|
+
: never,
|
|
260
|
+
): string;
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Dot-prefixed local route with params
|
|
264
|
+
*/
|
|
265
|
+
<TName extends keyof TLocalRoutes & string>(
|
|
266
|
+
name: `.${TName}`,
|
|
267
|
+
params: LocalReverseParams<RoutePatternFor<TLocalRoutes, TName>>,
|
|
268
|
+
): string;
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Dot-prefixed local route with params and search
|
|
272
|
+
*/
|
|
273
|
+
<TName extends keyof TLocalRoutes & string>(
|
|
274
|
+
name: `.${TName}`,
|
|
275
|
+
params: LocalReverseParams<RoutePatternFor<TLocalRoutes, TName>>,
|
|
276
|
+
search: ResolveSearchSchema<ExtractSearchSchema<TLocalRoutes, TName>>,
|
|
277
|
+
): string;
|
|
278
|
+
};
|
|
191
279
|
|
|
192
280
|
/**
|
|
193
281
|
* Extract the response data type for a named route from a UrlPatterns instance.
|
|
@@ -198,24 +286,23 @@ export type { RouteResponse } from "./urls.js";
|
|
|
198
286
|
/**
|
|
199
287
|
* Get a locally-typed reverse function from ctx.reverse for composable modules.
|
|
200
288
|
*
|
|
201
|
-
* This is a type-only cast - ctx.reverse already resolves
|
|
202
|
-
*
|
|
203
|
-
*
|
|
289
|
+
* This is a type-only cast - ctx.reverse already resolves names at runtime.
|
|
290
|
+
* Provides type safety: `.name` validates against local routes,
|
|
291
|
+
* `name` validates against global named-routes.
|
|
204
292
|
*
|
|
205
293
|
* @param reverse - The ctx.reverse function from HandlerContext
|
|
206
|
-
* @returns The same reverse function,
|
|
294
|
+
* @returns The same reverse function, typed with local + global routes
|
|
207
295
|
*
|
|
208
296
|
* @example
|
|
209
297
|
* ```typescript
|
|
210
298
|
* // urls/blog.tsx
|
|
211
299
|
* export const blogPatterns = urls(({ path }) => [
|
|
212
300
|
* path("/", (ctx) => {
|
|
213
|
-
* // Get locally-typed reverse for this module's routes
|
|
214
301
|
* const reverse = scopedReverse<typeof blogPatterns>(ctx.reverse);
|
|
215
302
|
*
|
|
216
|
-
* reverse("index"); // ✓
|
|
217
|
-
* reverse("post", { slug: "x" }); // ✓
|
|
218
|
-
* reverse("shop.cart");
|
|
303
|
+
* reverse(".index"); // ✓ Local route
|
|
304
|
+
* reverse(".post", { slug: "x" }); // ✓ Local with params
|
|
305
|
+
* reverse("shop.cart"); // ✓ Global route
|
|
219
306
|
*
|
|
220
307
|
* return <BlogIndex />;
|
|
221
308
|
* }, { name: "index" }),
|
|
@@ -225,7 +312,7 @@ export type { RouteResponse } from "./urls.js";
|
|
|
225
312
|
* ```
|
|
226
313
|
*/
|
|
227
314
|
export function scopedReverse<TPatterns>(
|
|
228
|
-
reverse: (
|
|
315
|
+
reverse: (...args: any[]) => string,
|
|
229
316
|
): ScopedReverseFunction<ExtractLocalRoutes<TPatterns>> {
|
|
230
317
|
return reverse as ScopedReverseFunction<ExtractLocalRoutes<TPatterns>>;
|
|
231
318
|
}
|
|
@@ -244,24 +331,47 @@ export function scopedReverse<TPatterns>(
|
|
|
244
331
|
* reverse("detail", { slug: "my-product" }); // "/shop/product/my-product"
|
|
245
332
|
* ```
|
|
246
333
|
*/
|
|
334
|
+
type RouteMapEntry = string | { path: string; search?: Record<string, string> };
|
|
335
|
+
|
|
336
|
+
function resolveRoutePattern(
|
|
337
|
+
entry: RouteMapEntry | undefined,
|
|
338
|
+
): string | undefined {
|
|
339
|
+
if (!entry) return undefined;
|
|
340
|
+
return typeof entry === "string" ? entry : entry.path;
|
|
341
|
+
}
|
|
342
|
+
|
|
247
343
|
export function createReverse<TRoutes extends Record<string, string>>(
|
|
248
|
-
routeMap: TRoutes
|
|
344
|
+
routeMap: TRoutes,
|
|
249
345
|
): ReverseFunction<TRoutes & Record<string, string>> {
|
|
250
|
-
return ((
|
|
251
|
-
|
|
346
|
+
return ((
|
|
347
|
+
name: string,
|
|
348
|
+
params?: Record<string, string>,
|
|
349
|
+
search?: Record<string, unknown>,
|
|
350
|
+
) => {
|
|
351
|
+
const pattern = resolveRoutePattern(
|
|
352
|
+
routeMap[name] as unknown as RouteMapEntry,
|
|
353
|
+
);
|
|
252
354
|
if (!pattern) {
|
|
355
|
+
// During build-time discovery, lazy includes haven't resolved yet.
|
|
356
|
+
// Return a placeholder instead of crashing the build.
|
|
357
|
+
if ((globalThis as any).__rscRouterDiscoveryActive) {
|
|
358
|
+
return `/__unresolved_reverse/${name}`;
|
|
359
|
+
}
|
|
253
360
|
throw new Error(`Unknown route: ${name}`);
|
|
254
361
|
}
|
|
255
362
|
|
|
256
|
-
|
|
363
|
+
let result = params
|
|
364
|
+
? substitutePatternParams(pattern, params, name)
|
|
365
|
+
: pattern;
|
|
257
366
|
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
if (
|
|
262
|
-
|
|
367
|
+
// Append search params as query string
|
|
368
|
+
if (search) {
|
|
369
|
+
const qs = serializeSearchParams(search);
|
|
370
|
+
if (qs) {
|
|
371
|
+
result += `?${qs}`;
|
|
263
372
|
}
|
|
264
|
-
|
|
265
|
-
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return result;
|
|
266
376
|
}) as ReverseFunction<TRoutes>;
|
|
267
377
|
}
|
|
@@ -60,7 +60,8 @@ function NetworkErrorFallback({
|
|
|
60
60
|
marginBottom: "1.5rem",
|
|
61
61
|
}}
|
|
62
62
|
>
|
|
63
|
-
{error.message ||
|
|
63
|
+
{error.message ||
|
|
64
|
+
"Unable to connect to the server. Please check your internet connection."}
|
|
64
65
|
</p>
|
|
65
66
|
<div style={{ display: "flex", gap: "1rem", justifyContent: "center" }}>
|
|
66
67
|
<button
|
|
@@ -104,7 +105,12 @@ function NetworkErrorFallback({
|
|
|
104
105
|
* Default fallback UI for root error boundary
|
|
105
106
|
* This is shown when an unhandled error bubbles up to the root
|
|
106
107
|
*/
|
|
107
|
-
function RootErrorFallback({
|
|
108
|
+
function RootErrorFallback({
|
|
109
|
+
error,
|
|
110
|
+
reset,
|
|
111
|
+
}: ClientErrorBoundaryFallbackProps): ReactNode {
|
|
112
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
113
|
+
|
|
108
114
|
return (
|
|
109
115
|
<div
|
|
110
116
|
style={{
|
|
@@ -131,38 +137,40 @@ function RootErrorFallback({ error, reset }: ClientErrorBoundaryFallbackProps):
|
|
|
131
137
|
>
|
|
132
138
|
An unexpected error occurred while processing your request.
|
|
133
139
|
</p>
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
background: "#fef2f2",
|
|
137
|
-
border: "1px solid #fecaca",
|
|
138
|
-
borderRadius: "0.5rem",
|
|
139
|
-
padding: "1rem",
|
|
140
|
-
marginBottom: "1rem",
|
|
141
|
-
}}
|
|
142
|
-
>
|
|
143
|
-
<p
|
|
140
|
+
{isDev && (
|
|
141
|
+
<div
|
|
144
142
|
style={{
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
143
|
+
background: "#fef2f2",
|
|
144
|
+
border: "1px solid #fecaca",
|
|
145
|
+
borderRadius: "0.5rem",
|
|
146
|
+
padding: "1rem",
|
|
147
|
+
marginBottom: "1rem",
|
|
148
148
|
}}
|
|
149
149
|
>
|
|
150
|
-
|
|
151
|
-
</p>
|
|
152
|
-
{error.stack && (
|
|
153
|
-
<pre
|
|
150
|
+
<p
|
|
154
151
|
style={{
|
|
155
|
-
|
|
156
|
-
color: "#
|
|
157
|
-
|
|
158
|
-
whiteSpace: "pre-wrap",
|
|
159
|
-
wordBreak: "break-word",
|
|
152
|
+
fontWeight: 600,
|
|
153
|
+
color: "#991b1b",
|
|
154
|
+
marginBottom: "0.5rem",
|
|
160
155
|
}}
|
|
161
156
|
>
|
|
162
|
-
{error.
|
|
163
|
-
</
|
|
164
|
-
|
|
165
|
-
|
|
157
|
+
{error.name}: {error.message}
|
|
158
|
+
</p>
|
|
159
|
+
{error.stack && (
|
|
160
|
+
<pre
|
|
161
|
+
style={{
|
|
162
|
+
fontSize: "0.75rem",
|
|
163
|
+
color: "#6b7280",
|
|
164
|
+
overflow: "auto",
|
|
165
|
+
whiteSpace: "pre-wrap",
|
|
166
|
+
wordBreak: "break-word",
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
{error.stack}
|
|
170
|
+
</pre>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
166
174
|
<div style={{ display: "flex", gap: "1rem" }}>
|
|
167
175
|
<button
|
|
168
176
|
type="button"
|
|
@@ -231,7 +239,11 @@ export class RootErrorBoundary extends Component<
|
|
|
231
239
|
}
|
|
232
240
|
|
|
233
241
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
|
|
234
|
-
console.error(
|
|
242
|
+
console.error(
|
|
243
|
+
"[RootErrorBoundary] Unhandled error caught:",
|
|
244
|
+
error,
|
|
245
|
+
errorInfo,
|
|
246
|
+
);
|
|
235
247
|
}
|
|
236
248
|
|
|
237
249
|
componentDidUpdate(prevProps: { children: ReactNode }): void {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
3
|
import { Suspense, use, useId } from "react";
|
|
4
4
|
import { invariant } from "./errors";
|
|
5
|
-
import { OutletProvider } from "./
|
|
5
|
+
import { OutletProvider } from "./outlet-provider.js";
|
|
6
6
|
import type { ResolvedSegment } from "./types.js";
|
|
7
7
|
import { isLoaderDataResult } from "./types.js";
|
|
8
8
|
|
|
@@ -31,7 +31,10 @@ export function RouteContentWrapper({
|
|
|
31
31
|
return content as ReactNode;
|
|
32
32
|
}
|
|
33
33
|
return (
|
|
34
|
-
<Suspense
|
|
34
|
+
<Suspense
|
|
35
|
+
fallback={fallback ?? null}
|
|
36
|
+
key={segmentId ? "route-content-suspense-" + segmentId : undefined}
|
|
37
|
+
>
|
|
35
38
|
<Suspender content={content} key={segmentId} />
|
|
36
39
|
</Suspense>
|
|
37
40
|
);
|
|
@@ -50,11 +53,11 @@ export function RouteContentWrapperCallback<T>({
|
|
|
50
53
|
invariant(children, "RouteContentWrapperCallback requires children");
|
|
51
54
|
invariant(
|
|
52
55
|
typeof children === "function",
|
|
53
|
-
"RouteContentWrapperCallback requires children to be a function"
|
|
56
|
+
"RouteContentWrapperCallback requires children to be a function",
|
|
54
57
|
);
|
|
55
58
|
invariant(
|
|
56
59
|
resolve !== undefined,
|
|
57
|
-
"RouteContentWrapperCallback requires resolve"
|
|
60
|
+
"RouteContentWrapperCallback requires resolve",
|
|
58
61
|
);
|
|
59
62
|
return (
|
|
60
63
|
<Suspense
|