@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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useContext, useState, useEffect, useRef } from "react";
|
|
4
4
|
import { NavigationStoreContext } from "./context.js";
|
|
5
|
+
import { shallowEqual } from "./shallow-equal.js";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Segments state returned by useSegments hook
|
|
@@ -15,65 +16,6 @@ export interface SegmentsState {
|
|
|
15
16
|
location: URL;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
/**
|
|
19
|
-
* SSR module-level state.
|
|
20
|
-
* Populated by initSegmentsSync before React renders.
|
|
21
|
-
* Used by useState initializer during SSR.
|
|
22
|
-
*/
|
|
23
|
-
let ssrSegmentOrder: string[] = [];
|
|
24
|
-
let ssrPathname: string = "/";
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Filter segment IDs to only include routes and layouts.
|
|
28
|
-
* Excludes parallels (contain .@) and loaders (contain D followed by digit).
|
|
29
|
-
*/
|
|
30
|
-
function filterSegmentOrder(matched: string[]): string[] {
|
|
31
|
-
return matched.filter((id) => {
|
|
32
|
-
if (id.includes(".@")) return false;
|
|
33
|
-
if (/D\d+\./.test(id)) return false;
|
|
34
|
-
return true;
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Initialize segments data synchronously for SSR.
|
|
40
|
-
* Called before rendering to populate state for useState initializer.
|
|
41
|
-
*
|
|
42
|
-
* @param matched - Segment order from RSC metadata
|
|
43
|
-
* @param pathname - Current pathname
|
|
44
|
-
*/
|
|
45
|
-
export function initSegmentsSync(matched?: string[], pathname?: string): void {
|
|
46
|
-
ssrSegmentOrder = filterSegmentOrder(matched ?? []);
|
|
47
|
-
ssrPathname = pathname ?? "/";
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Shallow equality check for selector results
|
|
52
|
-
*/
|
|
53
|
-
function shallowEqual<T>(a: T, b: T): boolean {
|
|
54
|
-
if (Object.is(a, b)) return true;
|
|
55
|
-
if (
|
|
56
|
-
typeof a !== "object" ||
|
|
57
|
-
a === null ||
|
|
58
|
-
typeof b !== "object" ||
|
|
59
|
-
b === null
|
|
60
|
-
) {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
const keysA = Object.keys(a);
|
|
64
|
-
const keysB = Object.keys(b);
|
|
65
|
-
if (keysA.length !== keysB.length) return false;
|
|
66
|
-
for (const key of keysA) {
|
|
67
|
-
if (
|
|
68
|
-
!Object.hasOwn(b, key) ||
|
|
69
|
-
!Object.is((a as any)[key], (b as any)[key])
|
|
70
|
-
) {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
19
|
/**
|
|
78
20
|
* Parse pathname into path segments
|
|
79
21
|
* /shop/products/123 → ["shop", "products", "123"]
|
|
@@ -83,27 +25,18 @@ function parsePathname(pathname: string): string[] {
|
|
|
83
25
|
}
|
|
84
26
|
|
|
85
27
|
/**
|
|
86
|
-
* Build segments state from event controller
|
|
28
|
+
* Build segments state from event controller. `segmentIds` is the
|
|
29
|
+
* route-only list (parallels and loaders stripped) — distinct from the
|
|
30
|
+
* controller's `segmentOrder` which drives handle collection and includes
|
|
31
|
+
* parallel slot ids.
|
|
87
32
|
*/
|
|
88
33
|
function buildSegmentsState(
|
|
89
34
|
location: URL,
|
|
90
|
-
|
|
35
|
+
routeSegmentIds: string[],
|
|
91
36
|
): SegmentsState {
|
|
92
37
|
return {
|
|
93
38
|
path: parsePathname(location.pathname),
|
|
94
|
-
segmentIds:
|
|
95
|
-
location,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Build SSR state from module-level variables
|
|
101
|
-
*/
|
|
102
|
-
function buildSsrState(): SegmentsState {
|
|
103
|
-
const location = new URL(ssrPathname, "http://localhost");
|
|
104
|
-
return {
|
|
105
|
-
path: parsePathname(ssrPathname),
|
|
106
|
-
segmentIds: ssrSegmentOrder,
|
|
39
|
+
segmentIds: routeSegmentIds,
|
|
107
40
|
location,
|
|
108
41
|
};
|
|
109
42
|
}
|
|
@@ -127,62 +60,115 @@ function buildSsrState(): SegmentsState {
|
|
|
127
60
|
export function useSegments(): SegmentsState;
|
|
128
61
|
export function useSegments<T>(selector: (state: SegmentsState) => T): T;
|
|
129
62
|
export function useSegments<T>(
|
|
130
|
-
selector?: (state: SegmentsState) => T
|
|
63
|
+
selector?: (state: SegmentsState) => T,
|
|
131
64
|
): T | SegmentsState {
|
|
132
65
|
const ctx = useContext(NavigationStoreContext);
|
|
133
66
|
|
|
134
|
-
// Build initial state from
|
|
67
|
+
// Build initial state from event controller when context exists.
|
|
68
|
+
// Inlined rather than calling recompute() because the segmentsCache ref
|
|
69
|
+
// is not yet initialized during the useState initializer.
|
|
135
70
|
const [state, setState] = useState<T | SegmentsState>(() => {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
return selector ? selector(
|
|
71
|
+
if (!ctx) {
|
|
72
|
+
const fallbackLocation = new URL("/", "http://localhost");
|
|
73
|
+
const fallbackState = buildSegmentsState(fallbackLocation, []);
|
|
74
|
+
return selector ? selector(fallbackState) : fallbackState;
|
|
140
75
|
}
|
|
141
|
-
|
|
142
|
-
const navState = ctx.eventController.getState();
|
|
76
|
+
const location = ctx.eventController.getLocation();
|
|
143
77
|
const handleState = ctx.eventController.getHandleState();
|
|
144
78
|
const segmentsState = buildSegmentsState(
|
|
145
|
-
|
|
146
|
-
handleState.
|
|
79
|
+
location as URL,
|
|
80
|
+
handleState.routeSegmentIds,
|
|
147
81
|
);
|
|
148
82
|
return selector ? selector(segmentsState) : segmentsState;
|
|
149
83
|
});
|
|
150
84
|
|
|
151
85
|
const prevState = useRef(state);
|
|
86
|
+
const selectorRef = useRef(selector);
|
|
87
|
+
selectorRef.current = selector;
|
|
88
|
+
|
|
89
|
+
// Track selector identity to detect when the selector function changes.
|
|
90
|
+
// Only then do we eagerly recompute during render to avoid staleness.
|
|
91
|
+
// Without this guard, no-selector mode causes infinite re-renders because
|
|
92
|
+
// buildSegmentsState creates fresh arrays that fail Object.is checks.
|
|
93
|
+
const prevSelectorIdentity = useRef(selector);
|
|
94
|
+
|
|
95
|
+
// Cache SegmentsState to stabilize nested references (path, segmentIds
|
|
96
|
+
// arrays) so selectors returning composite values don't cause spurious
|
|
97
|
+
// render-time setState calls.
|
|
98
|
+
const segmentsCache = useRef<{
|
|
99
|
+
location: URL;
|
|
100
|
+
routeSegmentIds: string[];
|
|
101
|
+
state: SegmentsState;
|
|
102
|
+
} | null>(null);
|
|
103
|
+
|
|
104
|
+
// Recompute selected value from current store state and apply selector.
|
|
105
|
+
// Shared by the render-time eager check and the subscription callback.
|
|
106
|
+
function recompute(
|
|
107
|
+
sel: ((state: SegmentsState) => T) | undefined,
|
|
108
|
+
): T | SegmentsState {
|
|
109
|
+
const location = ctx!.eventController.getLocation();
|
|
110
|
+
const handleState = ctx!.eventController.getHandleState();
|
|
111
|
+
|
|
112
|
+
// Reuse cached state when inputs haven't changed by reference,
|
|
113
|
+
// keeping array/object references stable for composite selectors.
|
|
114
|
+
const cache = segmentsCache.current;
|
|
115
|
+
let segmentsState: SegmentsState;
|
|
116
|
+
if (
|
|
117
|
+
cache &&
|
|
118
|
+
cache.location === location &&
|
|
119
|
+
cache.routeSegmentIds === handleState.routeSegmentIds
|
|
120
|
+
) {
|
|
121
|
+
segmentsState = cache.state;
|
|
122
|
+
} else {
|
|
123
|
+
segmentsState = buildSegmentsState(
|
|
124
|
+
location as URL,
|
|
125
|
+
handleState.routeSegmentIds,
|
|
126
|
+
);
|
|
127
|
+
segmentsCache.current = {
|
|
128
|
+
location: location as URL,
|
|
129
|
+
routeSegmentIds: handleState.routeSegmentIds,
|
|
130
|
+
state: segmentsState,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return sel ? sel(segmentsState) : segmentsState;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (ctx && selector !== prevSelectorIdentity.current) {
|
|
137
|
+
prevSelectorIdentity.current = selector;
|
|
138
|
+
const nextSelected = recompute(selector);
|
|
139
|
+
if (!shallowEqual(nextSelected, prevState.current)) {
|
|
140
|
+
prevState.current = nextSelected;
|
|
141
|
+
setState(nextSelected);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
152
144
|
|
|
153
|
-
// Subscribe to
|
|
145
|
+
// Subscribe to store changes. The eager block above handles selector
|
|
146
|
+
// changes and SSR drift, so no initial updateState() call is needed.
|
|
154
147
|
useEffect(() => {
|
|
155
148
|
if (!ctx) {
|
|
156
149
|
return;
|
|
157
150
|
}
|
|
158
151
|
|
|
159
152
|
const updateState = () => {
|
|
160
|
-
const
|
|
161
|
-
const handleState = ctx.eventController.getHandleState();
|
|
162
|
-
const segmentsState = buildSegmentsState(
|
|
163
|
-
navState.location as URL,
|
|
164
|
-
handleState.segmentOrder
|
|
165
|
-
);
|
|
166
|
-
const nextSelected = selector ? selector(segmentsState) : segmentsState;
|
|
167
|
-
|
|
153
|
+
const nextSelected = recompute(selectorRef.current);
|
|
168
154
|
if (!shallowEqual(nextSelected, prevState.current)) {
|
|
169
155
|
prevState.current = nextSelected;
|
|
170
156
|
setState(nextSelected);
|
|
171
157
|
}
|
|
172
158
|
};
|
|
173
159
|
|
|
174
|
-
// Initial update in case SSR state differs from client state
|
|
175
|
-
updateState();
|
|
176
|
-
|
|
177
|
-
// Subscribe to both state sources
|
|
178
160
|
const unsubscribeNav = ctx.eventController.subscribe(updateState);
|
|
179
|
-
const unsubscribeHandles =
|
|
161
|
+
const unsubscribeHandles =
|
|
162
|
+
ctx.eventController.subscribeToHandles(updateState);
|
|
180
163
|
|
|
181
164
|
return () => {
|
|
182
165
|
unsubscribeNav();
|
|
183
166
|
unsubscribeHandles();
|
|
184
167
|
};
|
|
185
|
-
|
|
168
|
+
// Stable subscription: selector changes are handled via selectorRef,
|
|
169
|
+
// state comparison uses prevState ref. No re-subscribe needed.
|
|
170
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
171
|
+
}, []);
|
|
186
172
|
|
|
187
173
|
return state as T | SegmentsState;
|
|
188
174
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { validateRedirectOrigin } from "./validate-redirect-origin.js";
|
|
2
|
+
|
|
3
|
+
type HeaderResult = { url: string } | "blocked" | null;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extract and validate an RSC response header URL (X-RSC-Reload, X-RSC-Redirect).
|
|
7
|
+
* Returns { url } if valid, "blocked" if present but invalid origin, null if absent.
|
|
8
|
+
*/
|
|
9
|
+
export function extractRscHeaderUrl(
|
|
10
|
+
response: Response,
|
|
11
|
+
header: string,
|
|
12
|
+
): HeaderResult {
|
|
13
|
+
const raw = response.headers.get(header);
|
|
14
|
+
if (!raw) return null;
|
|
15
|
+
const url = validateRedirectOrigin(raw, window.location.origin);
|
|
16
|
+
return url ? { url } : "blocked";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Empty 200 response that won't choke Flight parsing.
|
|
21
|
+
* Used when a header URL is blocked by origin validation.
|
|
22
|
+
*/
|
|
23
|
+
export function emptyResponse(): Response {
|
|
24
|
+
return new Response(null, { status: 200 });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Tee a response body for RSC parsing and stream completion tracking.
|
|
29
|
+
* Returns a new Response with one branch; the other is consumed to detect
|
|
30
|
+
* end-of-stream, calling onComplete when done.
|
|
31
|
+
*
|
|
32
|
+
* If the response has no body, onComplete fires synchronously.
|
|
33
|
+
* If signal is provided, an abort cancels the tracking reader.
|
|
34
|
+
*/
|
|
35
|
+
export function teeWithCompletion(
|
|
36
|
+
response: Response,
|
|
37
|
+
onComplete: () => void,
|
|
38
|
+
signal?: AbortSignal,
|
|
39
|
+
): Response {
|
|
40
|
+
if (!response.body) {
|
|
41
|
+
onComplete();
|
|
42
|
+
return response;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const [rscStream, trackingStream] = response.body.tee();
|
|
46
|
+
|
|
47
|
+
(async () => {
|
|
48
|
+
const reader = trackingStream.getReader();
|
|
49
|
+
const onAbort = signal ? reader.cancel.bind(reader) : undefined;
|
|
50
|
+
if (onAbort) signal!.addEventListener("abort", onAbort, { once: true });
|
|
51
|
+
try {
|
|
52
|
+
while (true) {
|
|
53
|
+
const { done } = await reader.read();
|
|
54
|
+
if (done) break;
|
|
55
|
+
}
|
|
56
|
+
} finally {
|
|
57
|
+
if (onAbort) signal!.removeEventListener("abort", onAbort);
|
|
58
|
+
reader.releaseLock();
|
|
59
|
+
onComplete();
|
|
60
|
+
}
|
|
61
|
+
})().catch((error) => {
|
|
62
|
+
if (!signal?.aborted) {
|
|
63
|
+
console.error("[Browser] Error reading tracking stream:", error);
|
|
64
|
+
}
|
|
65
|
+
onComplete();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return new Response(rscStream, {
|
|
69
|
+
headers: response.headers,
|
|
70
|
+
status: response.status,
|
|
71
|
+
statusText: response.statusText,
|
|
72
|
+
});
|
|
73
|
+
}
|