@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -9
- package/dist/bin/rango.js +157 -63
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +1584 -639
- package/package.json +71 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +235 -28
- package/skills/host-router/SKILL.md +122 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +29 -5
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +170 -23
- package/skills/middleware/SKILL.md +16 -10
- package/skills/migrate-nextjs/SKILL.md +38 -16
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +11 -7
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +250 -25
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +114 -47
- package/skills/route/SKILL.md +42 -5
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +78 -42
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +316 -26
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +37 -143
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +30 -59
- package/src/browser/navigation-client.ts +96 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +32 -82
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +60 -127
- package/src/browser/prefetch/cache.ts +82 -72
- package/src/browser/prefetch/fetch.ts +108 -33
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +41 -48
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +11 -11
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +20 -5
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +70 -34
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +168 -44
- package/src/browser/types.ts +36 -21
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +89 -10
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +122 -22
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2255 -238
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +25 -61
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +17 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +63 -9
- package/src/index.ts +64 -9
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -40
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +244 -281
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +40 -17
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -15
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +4 -41
- package/src/router/intercept-resolution.ts +14 -19
- package/src/router/lazy-includes.ts +9 -46
- package/src/router/loader-resolution.ts +91 -46
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +57 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +150 -271
- package/src/router/match-middleware/cache-store.ts +3 -33
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +31 -80
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +5 -112
- package/src/router/middleware.ts +118 -133
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +62 -67
- package/src/router/prerender-match.ts +99 -63
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +50 -56
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +68 -35
- package/src/router/router-options.ts +55 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +44 -63
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +203 -285
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +87 -48
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +80 -41
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +83 -78
- package/src/rsc/helpers.ts +93 -5
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +12 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +76 -62
- package/src/rsc/rsc-rendering.ts +41 -60
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +62 -67
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +10 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +199 -142
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +150 -51
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +165 -87
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +97 -22
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +18 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +19 -5
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +95 -82
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +26 -34
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +39 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +185 -10
- package/src/vite/plugins/cjs-to-esm.ts +3 -18
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +12 -11
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +4 -75
- package/src/vite/plugins/expose-id-utils.ts +3 -54
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +57 -67
- package/src/vite/plugins/performance-tracks.ts +9 -16
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +26 -49
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +2 -32
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +35 -17
- package/src/vite/rango.ts +148 -115
- package/src/vite/router-discovery.ts +220 -68
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -34
- package/src/vite/utils/shared-utils.ts +95 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
package/src/rsc/rsc-rendering.ts
CHANGED
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
import {
|
|
10
10
|
requireRequestContext,
|
|
11
11
|
setRequestContextParams,
|
|
12
|
-
getLocationState,
|
|
13
12
|
} from "../server/request-context.js";
|
|
14
|
-
import { resolveLocationStateEntries } from "../browser/react/location-state-shared.js";
|
|
15
13
|
import { appendMetric } from "../router/metrics.js";
|
|
16
|
-
import { getSSRSetup } from "./ssr-setup.js";
|
|
14
|
+
import { getSSRSetup, isRscRequest } from "./ssr-setup.js";
|
|
17
15
|
import type { RscPayload } from "./types.js";
|
|
16
|
+
import type { MatchResult } from "../types.js";
|
|
18
17
|
import {
|
|
19
18
|
createResponseWithMergedHeaders,
|
|
20
19
|
createSimpleRedirectResponse,
|
|
20
|
+
attachLocationStateIfPresent,
|
|
21
21
|
} from "./helpers.js";
|
|
22
22
|
import type { HandlerContext } from "./handler-context.js";
|
|
23
23
|
|
|
@@ -35,6 +35,29 @@ export async function handleRscRendering<TEnv>(
|
|
|
35
35
|
let payload: RscPayload;
|
|
36
36
|
let hasInterceptSlots = false;
|
|
37
37
|
|
|
38
|
+
// Shared by the partial-fallback and full-render paths. The partial-success
|
|
39
|
+
// payload below is intentionally different (omits rootLayout/theme, adds slots).
|
|
40
|
+
const buildFullPayload = (m: MatchResult): RscPayload => ({
|
|
41
|
+
metadata: {
|
|
42
|
+
pathname: url.pathname,
|
|
43
|
+
routerId: ctx.router.id,
|
|
44
|
+
basename: ctx.router.basename,
|
|
45
|
+
segments: m.segments,
|
|
46
|
+
matched: m.matched,
|
|
47
|
+
diff: m.diff,
|
|
48
|
+
resolvedIds: m.resolvedIds,
|
|
49
|
+
params: m.params,
|
|
50
|
+
isPartial: false,
|
|
51
|
+
rootLayout: ctx.router.rootLayout,
|
|
52
|
+
handles: handleStore.stream(),
|
|
53
|
+
version: ctx.version,
|
|
54
|
+
prefetchCacheTTL: ctx.router.prefetchCacheTTL,
|
|
55
|
+
stateCookieName: ctx.router.resolvedStateCookieName,
|
|
56
|
+
themeConfig: ctx.router.themeConfig,
|
|
57
|
+
initialTheme: reqCtx.theme,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
38
61
|
if (isPartial) {
|
|
39
62
|
// Partial render (navigation)
|
|
40
63
|
const result = await ctx.router.matchPartial(request, { env });
|
|
@@ -51,25 +74,7 @@ export async function handleRscRendering<TEnv>(
|
|
|
51
74
|
return createSimpleRedirectResponse(match.redirect);
|
|
52
75
|
}
|
|
53
76
|
|
|
54
|
-
payload =
|
|
55
|
-
metadata: {
|
|
56
|
-
pathname: url.pathname,
|
|
57
|
-
routerId: ctx.router.id,
|
|
58
|
-
basename: ctx.router.basename,
|
|
59
|
-
segments: match.segments,
|
|
60
|
-
matched: match.matched,
|
|
61
|
-
diff: match.diff,
|
|
62
|
-
resolvedIds: match.resolvedIds,
|
|
63
|
-
params: match.params,
|
|
64
|
-
isPartial: false,
|
|
65
|
-
rootLayout: ctx.router.rootLayout,
|
|
66
|
-
handles: handleStore.stream(),
|
|
67
|
-
version: ctx.version,
|
|
68
|
-
prefetchCacheTTL: ctx.router.prefetchCacheTTL,
|
|
69
|
-
themeConfig: ctx.router.themeConfig,
|
|
70
|
-
initialTheme: reqCtx.theme,
|
|
71
|
-
},
|
|
72
|
-
};
|
|
77
|
+
payload = buildFullPayload(match);
|
|
73
78
|
} else {
|
|
74
79
|
setRequestContextParams(result.params, result.routeName);
|
|
75
80
|
|
|
@@ -78,6 +83,11 @@ export async function handleRscRendering<TEnv>(
|
|
|
78
83
|
payload = {
|
|
79
84
|
metadata: {
|
|
80
85
|
pathname: url.pathname,
|
|
86
|
+
// routerId is serialized on every payload (including within-session
|
|
87
|
+
// ones) so the frontend can read the current app/router identity. It
|
|
88
|
+
// always equals the current app's id: a cross-app navigation is
|
|
89
|
+
// intercepted server-side (X-RSC-Reload) and never delivers a
|
|
90
|
+
// different-router payload to the client.
|
|
81
91
|
routerId: ctx.router.id,
|
|
82
92
|
segments: result.segments,
|
|
83
93
|
matched: result.matched,
|
|
@@ -89,6 +99,7 @@ export async function handleRscRendering<TEnv>(
|
|
|
89
99
|
handles: handleStore.stream(),
|
|
90
100
|
version: ctx.version,
|
|
91
101
|
prefetchCacheTTL: ctx.router.prefetchCacheTTL,
|
|
102
|
+
stateCookieName: ctx.router.resolvedStateCookieName,
|
|
92
103
|
},
|
|
93
104
|
};
|
|
94
105
|
}
|
|
@@ -135,28 +146,7 @@ export async function handleRscRendering<TEnv>(
|
|
|
135
146
|
{ headers: { "Content-Type": "application/json" } },
|
|
136
147
|
);
|
|
137
148
|
} else {
|
|
138
|
-
payload =
|
|
139
|
-
// Initial SSR can reconstruct the tree from segments + rootLayout,
|
|
140
|
-
// so we omit root to avoid sending the same structure twice.
|
|
141
|
-
|
|
142
|
-
metadata: {
|
|
143
|
-
pathname: url.pathname,
|
|
144
|
-
routerId: ctx.router.id,
|
|
145
|
-
basename: ctx.router.basename,
|
|
146
|
-
segments: match.segments,
|
|
147
|
-
matched: match.matched,
|
|
148
|
-
diff: match.diff,
|
|
149
|
-
resolvedIds: match.resolvedIds,
|
|
150
|
-
params: match.params,
|
|
151
|
-
isPartial: false,
|
|
152
|
-
rootLayout: ctx.router.rootLayout,
|
|
153
|
-
handles: handleStore.stream(),
|
|
154
|
-
version: ctx.version,
|
|
155
|
-
prefetchCacheTTL: ctx.router.prefetchCacheTTL,
|
|
156
|
-
themeConfig: ctx.router.themeConfig,
|
|
157
|
-
initialTheme: reqCtx.theme,
|
|
158
|
-
},
|
|
159
|
-
};
|
|
149
|
+
payload = buildFullPayload(match);
|
|
160
150
|
}
|
|
161
151
|
}
|
|
162
152
|
|
|
@@ -164,11 +154,7 @@ export async function handleRscRendering<TEnv>(
|
|
|
164
154
|
// SSR (full page) requests ignore location state since there's no history.state
|
|
165
155
|
// to write to on a fresh page load.
|
|
166
156
|
if (isPartial && payload.metadata) {
|
|
167
|
-
|
|
168
|
-
if (locationState) {
|
|
169
|
-
payload.metadata.locationState =
|
|
170
|
-
resolveLocationStateEntries(locationState);
|
|
171
|
-
}
|
|
157
|
+
attachLocationStateIfPresent(payload);
|
|
172
158
|
}
|
|
173
159
|
|
|
174
160
|
const metricsStore = reqCtx._metricsStore;
|
|
@@ -190,22 +176,17 @@ export async function handleRscRendering<TEnv>(
|
|
|
190
176
|
rscSerializeDur,
|
|
191
177
|
);
|
|
192
178
|
|
|
193
|
-
|
|
194
|
-
// Partial requests (_rsc_partial) are always RSC -- they come from client-side
|
|
195
|
-
// navigation or prefetch fetch(). We cannot rely on Accept alone since some
|
|
196
|
-
// browsers may send Accept: text/html for non-HTML requests.
|
|
197
|
-
const isRscRequest =
|
|
198
|
-
isPartial ||
|
|
199
|
-
(!request.headers.get("accept")?.includes("text/html") &&
|
|
200
|
-
!url.searchParams.has("__html")) ||
|
|
201
|
-
url.searchParams.has("__rsc");
|
|
202
|
-
|
|
203
|
-
if (isRscRequest) {
|
|
179
|
+
if (isRscRequest(request, url, isPartial)) {
|
|
204
180
|
const renderDur = performance.now() - renderStart;
|
|
205
181
|
appendMetric(metricsStore, "render:total", renderStart, renderDur);
|
|
206
182
|
const rscHeaders: Record<string, string> = {
|
|
207
183
|
"content-type": "text/x-component;charset=utf-8",
|
|
208
184
|
vary: "accept, X-Rango-State, X-RSC-Router-Client-Path",
|
|
185
|
+
// Router identity, so the client can verify pre-decode (before importing
|
|
186
|
+
// chunks) that this content payload belongs to its app and refuse a
|
|
187
|
+
// foreign one (cache/proxy/bug). Control-only reload/redirect responses
|
|
188
|
+
// are deliberately NOT stamped. See browser/response-adapter.ts.
|
|
189
|
+
"X-RSC-Router-Id": ctx.router.id,
|
|
209
190
|
};
|
|
210
191
|
// Tell the client's prefetch cache to scope this response to its source
|
|
211
192
|
// URL (instead of the default source-agnostic wildcard). Intercept
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
createResponseWithMergedHeaders,
|
|
9
9
|
carryOverRedirectHeaders,
|
|
10
10
|
} from "./helpers.js";
|
|
11
|
+
import { isRedirectResponse } from "../response-utils.js";
|
|
11
12
|
|
|
12
13
|
// W3 -----------------------------------------------------------------------
|
|
13
14
|
|
|
@@ -18,16 +19,14 @@ import {
|
|
|
18
19
|
*/
|
|
19
20
|
export function extractRedirectResponse(value: unknown): Response | null {
|
|
20
21
|
if (!(value instanceof Response)) return null;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
return null;
|
|
22
|
+
if (!isRedirectResponse(value)) return null;
|
|
23
|
+
const location = value.headers.get("Location")!;
|
|
24
|
+
const redirect = createResponseWithMergedHeaders(null, {
|
|
25
|
+
status: value.status,
|
|
26
|
+
headers: { Location: location },
|
|
27
|
+
});
|
|
28
|
+
carryOverRedirectHeaders(value, redirect);
|
|
29
|
+
return redirect;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
/**
|
|
@@ -40,3 +39,17 @@ export function warnNonRedirectPeResponse(): void {
|
|
|
40
39
|
`ignored — the page will re-render at the current URL instead.`,
|
|
41
40
|
);
|
|
42
41
|
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Warn when a non-redirect Response is returned (not thrown) from an action
|
|
45
|
+
* on the JS (fetch) path. A raw Response cannot be serialized into Flight, so
|
|
46
|
+
* it is discarded — mirroring the PE path. Use `throw redirect('/path')` for
|
|
47
|
+
* redirects.
|
|
48
|
+
*/
|
|
49
|
+
export function warnNonRedirectActionResponse(actionId: string): void {
|
|
50
|
+
console.warn(
|
|
51
|
+
`[@rangojs/router] Server action "${actionId}" returned a Response ` +
|
|
52
|
+
`that is not a redirect. Non-redirect Responses cannot be serialized ` +
|
|
53
|
+
`and are ignored. Use \`throw redirect('/path')\` for redirects.`,
|
|
54
|
+
);
|
|
55
|
+
}
|
package/src/rsc/server-action.ts
CHANGED
|
@@ -18,31 +18,19 @@
|
|
|
18
18
|
import {
|
|
19
19
|
requireRequestContext,
|
|
20
20
|
setRequestContextParams,
|
|
21
|
-
getLocationState,
|
|
22
21
|
} from "../server/request-context.js";
|
|
23
|
-
import { resolveLocationStateEntries } from "../browser/react/location-state-shared.js";
|
|
24
22
|
import { appendMetric } from "../router/metrics.js";
|
|
25
23
|
import type { RscPayload } from "./types.js";
|
|
26
24
|
import {
|
|
27
25
|
hasBodyContent,
|
|
28
26
|
createResponseWithMergedHeaders,
|
|
29
27
|
createSimpleRedirectResponse,
|
|
30
|
-
|
|
28
|
+
interceptRedirectForPartial,
|
|
29
|
+
attachLocationStateIfPresent,
|
|
31
30
|
} from "./helpers.js";
|
|
31
|
+
import { warnNonRedirectActionResponse } from "./runtime-warnings.js";
|
|
32
32
|
import type { HandlerContext } from "./handler-context.js";
|
|
33
33
|
|
|
34
|
-
/**
|
|
35
|
-
* Attach location state set during the action to a payload's metadata.
|
|
36
|
-
* No-op if no location state was set.
|
|
37
|
-
*/
|
|
38
|
-
function attachLocationState(payload: RscPayload): void {
|
|
39
|
-
const locationState = getLocationState();
|
|
40
|
-
if (locationState) {
|
|
41
|
-
payload.metadata!.locationState =
|
|
42
|
-
resolveLocationStateEntries(locationState);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
34
|
/**
|
|
47
35
|
* Data flowing from action execution to the revalidation phase.
|
|
48
36
|
* When the action completes without redirect/error-boundary, the handler
|
|
@@ -97,7 +85,10 @@ export async function executeServerAction<TEnv>(
|
|
|
97
85
|
args = await ctx.decodeReply(body, { temporaryReferences });
|
|
98
86
|
}
|
|
99
87
|
} catch (error) {
|
|
100
|
-
|
|
88
|
+
// Keep the original error as `cause` for server-side logging, but do not
|
|
89
|
+
// interpolate it into the message: that string can surface to the client
|
|
90
|
+
// and may leak decode internals.
|
|
91
|
+
throw new Error("Failed to decode action arguments", {
|
|
101
92
|
cause: error,
|
|
102
93
|
});
|
|
103
94
|
}
|
|
@@ -109,51 +100,35 @@ export async function executeServerAction<TEnv>(
|
|
|
109
100
|
|
|
110
101
|
try {
|
|
111
102
|
loadedAction = await ctx.loadServerAction(actionId);
|
|
112
|
-
|
|
103
|
+
let data = await loadedAction!.apply(null, args);
|
|
113
104
|
|
|
114
|
-
// Intercept redirect
|
|
115
|
-
//
|
|
116
|
-
// and the revalidation step would run unnecessarily.
|
|
105
|
+
// Intercept redirect Responses: serializing one as the action returnValue
|
|
106
|
+
// would fail, and revalidation would run needlessly.
|
|
117
107
|
if (data instanceof Response) {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
redirect = createSimpleRedirectResponse(redirectUrl);
|
|
130
|
-
}
|
|
131
|
-
carryOverRedirectHeaders(data, redirect);
|
|
132
|
-
return redirect;
|
|
108
|
+
const intercepted = interceptRedirectForPartial(
|
|
109
|
+
data,
|
|
110
|
+
ctx.createRedirectFlightResponse,
|
|
111
|
+
);
|
|
112
|
+
if (intercepted) return intercepted;
|
|
113
|
+
|
|
114
|
+
// Non-redirect Response returned (not thrown): a raw Response cannot be
|
|
115
|
+
// serialized into Flight. Discard it and re-render — mirroring the PE
|
|
116
|
+
// path (progressive-enhancement.ts) so JS and no-JS behave identically.
|
|
117
|
+
if (process.env.NODE_ENV !== "production") {
|
|
118
|
+
warnNonRedirectActionResponse(actionId);
|
|
133
119
|
}
|
|
120
|
+
data = undefined;
|
|
134
121
|
}
|
|
135
122
|
|
|
136
123
|
returnValue = { ok: true, data };
|
|
137
124
|
} catch (error) {
|
|
138
125
|
// Handle thrown redirect (e.g., throw redirect('/path'))
|
|
139
126
|
if (error instanceof Response) {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
let redirect: Response;
|
|
146
|
-
if (locationState) {
|
|
147
|
-
redirect = ctx.createRedirectFlightResponse(
|
|
148
|
-
redirectUrl,
|
|
149
|
-
resolveLocationStateEntries(locationState),
|
|
150
|
-
);
|
|
151
|
-
} else {
|
|
152
|
-
redirect = createSimpleRedirectResponse(redirectUrl);
|
|
153
|
-
}
|
|
154
|
-
carryOverRedirectHeaders(error, redirect);
|
|
155
|
-
return redirect;
|
|
156
|
-
}
|
|
127
|
+
const intercepted = interceptRedirectForPartial(
|
|
128
|
+
error,
|
|
129
|
+
ctx.createRedirectFlightResponse,
|
|
130
|
+
);
|
|
131
|
+
if (intercepted) return intercepted;
|
|
157
132
|
|
|
158
133
|
// Non-redirect Response thrown from action — this will be treated
|
|
159
134
|
// as a regular error and routed to the error boundary. Warn in dev
|
|
@@ -208,6 +183,8 @@ export async function executeServerAction<TEnv>(
|
|
|
208
183
|
const payload: RscPayload = {
|
|
209
184
|
metadata: {
|
|
210
185
|
pathname: url.pathname,
|
|
186
|
+
// routerId exposed for the frontend (current app identity); see
|
|
187
|
+
// rsc-rendering.ts partial branch.
|
|
211
188
|
routerId: ctx.router.id,
|
|
212
189
|
segments: errorResult.segments,
|
|
213
190
|
isPartial: true,
|
|
@@ -235,24 +212,32 @@ export async function executeServerAction<TEnv>(
|
|
|
235
212
|
|
|
236
213
|
return createResponseWithMergedHeaders(rscStream, {
|
|
237
214
|
status: actionStatus,
|
|
238
|
-
headers: {
|
|
215
|
+
headers: {
|
|
216
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
217
|
+
// Router identity for the client's pre-decode integrity check (the
|
|
218
|
+
// action apply path has no post-decode guard). See response-adapter.
|
|
219
|
+
"X-RSC-Router-Id": ctx.router.id,
|
|
220
|
+
},
|
|
239
221
|
});
|
|
240
222
|
}
|
|
241
223
|
}
|
|
242
224
|
|
|
243
225
|
// Build continuation for the revalidation phase
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
226
|
+
const actionMeta = loadedAction as
|
|
227
|
+
| { $id?: string; $$id?: string }
|
|
228
|
+
| undefined;
|
|
229
|
+
const resolvedActionId = actionMeta?.$id ?? actionMeta?.$$id ?? actionId;
|
|
248
230
|
|
|
249
231
|
return {
|
|
250
232
|
returnValue,
|
|
251
233
|
actionStatus,
|
|
252
234
|
temporaryReferences,
|
|
253
235
|
actionContext: {
|
|
236
|
+
// Defensive copy of the already-parsed url (avoids re-parsing
|
|
237
|
+
// request.url). actionUrl is persisted into the continuation and later
|
|
238
|
+
// flows into matchPartial, so it must not alias the handler's live url.
|
|
254
239
|
actionId: resolvedActionId,
|
|
255
|
-
actionUrl: new URL(
|
|
240
|
+
actionUrl: new URL(url),
|
|
256
241
|
actionResult: returnValue.data,
|
|
257
242
|
formData: actionFormData,
|
|
258
243
|
},
|
|
@@ -291,8 +276,8 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
291
276
|
);
|
|
292
277
|
|
|
293
278
|
if (!matchResult) {
|
|
294
|
-
// matchPartial returns null when the route is a redirect or
|
|
295
|
-
//
|
|
279
|
+
// matchPartial returns null when the route is a redirect or no previous-URL
|
|
280
|
+
// context could be resolved. Check for redirect first.
|
|
296
281
|
const fullMatch = await ctx.router.match(request, { env });
|
|
297
282
|
setRequestContextParams(fullMatch.params, fullMatch.routeName);
|
|
298
283
|
|
|
@@ -303,14 +288,17 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
303
288
|
return createSimpleRedirectResponse(fullMatch.redirect);
|
|
304
289
|
}
|
|
305
290
|
|
|
306
|
-
// Non-redirect: this branch is only reachable when
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
//
|
|
291
|
+
// Non-redirect: this branch is only reachable when no previous URL could
|
|
292
|
+
// be resolved (neither X-RSC-Router-Client-Path nor a usable Referer), or
|
|
293
|
+
// the previous URL was unparseable (defensive). The client requires
|
|
294
|
+
// isPartial for action responses, so producing a full payload here would
|
|
295
|
+
// be rejected. Return 500 instead.
|
|
310
296
|
throw new Error(
|
|
311
297
|
`[RSC] matchPartial returned null for a non-redirect route ` +
|
|
312
298
|
`during action revalidation (${url.pathname}). This indicates ` +
|
|
313
|
-
`a malformed action request
|
|
299
|
+
`a malformed action request: no previous-URL context could be ` +
|
|
300
|
+
`resolved (neither X-RSC-Router-Client-Path nor a usable Referer), ` +
|
|
301
|
+
`or the previous URL was unparseable.`,
|
|
314
302
|
);
|
|
315
303
|
}
|
|
316
304
|
|
|
@@ -320,6 +308,8 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
320
308
|
const payload: RscPayload = {
|
|
321
309
|
metadata: {
|
|
322
310
|
pathname: url.pathname,
|
|
311
|
+
// routerId exposed for the frontend (current app identity); see
|
|
312
|
+
// rsc-rendering.ts partial branch.
|
|
323
313
|
routerId: ctx.router.id,
|
|
324
314
|
segments: matchResult.segments,
|
|
325
315
|
isPartial: true,
|
|
@@ -334,7 +324,7 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
334
324
|
returnValue,
|
|
335
325
|
};
|
|
336
326
|
|
|
337
|
-
|
|
327
|
+
attachLocationStateIfPresent(payload);
|
|
338
328
|
|
|
339
329
|
const renderStart = performance.now();
|
|
340
330
|
const rscStream = ctx.renderToReadableStream<RscPayload>(payload, {
|
|
@@ -355,6 +345,11 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
355
345
|
|
|
356
346
|
return createResponseWithMergedHeaders(rscStream, {
|
|
357
347
|
status: actionStatus,
|
|
358
|
-
headers: {
|
|
348
|
+
headers: {
|
|
349
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
350
|
+
// Router identity for the client's pre-decode integrity check (the action
|
|
351
|
+
// apply path has no post-decode guard). See response-adapter.
|
|
352
|
+
"X-RSC-Router-Id": ctx.router.id,
|
|
353
|
+
},
|
|
359
354
|
});
|
|
360
355
|
}
|
package/src/rsc/ssr-setup.ts
CHANGED
|
@@ -126,3 +126,19 @@ export function mayNeedSSR(request: Request, url: URL): boolean {
|
|
|
126
126
|
|
|
127
127
|
return true;
|
|
128
128
|
}
|
|
129
|
+
|
|
130
|
+
// Final render-time decision: is the response an RSC stream (vs HTML)? Distinct
|
|
131
|
+
// from mayNeedSSR, which is a conservative pre-classifier (it treats a missing
|
|
132
|
+
// Accept header as needing SSR; this treats it as RSC).
|
|
133
|
+
export function isRscRequest(
|
|
134
|
+
request: Request,
|
|
135
|
+
url: URL,
|
|
136
|
+
isPartial: boolean,
|
|
137
|
+
): boolean {
|
|
138
|
+
return (
|
|
139
|
+
isPartial ||
|
|
140
|
+
(!request.headers.get("accept")?.includes("text/html") &&
|
|
141
|
+
!url.searchParams.has("__html")) ||
|
|
142
|
+
url.searchParams.has("__rsc")
|
|
143
|
+
);
|
|
144
|
+
}
|
package/src/rsc/types.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ResolvedSegment, SlotState } from "../types.js";
|
|
9
9
|
import type { HandleData } from "../server/handle-store.js";
|
|
10
|
-
import type {
|
|
10
|
+
import type { RangoInternal } from "../router/router-interfaces.js";
|
|
11
11
|
import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -43,6 +43,8 @@ export interface RscPayload {
|
|
|
43
43
|
version?: string;
|
|
44
44
|
/** TTL in milliseconds for the client-side in-memory prefetch cache */
|
|
45
45
|
prefetchCacheTTL?: number;
|
|
46
|
+
/** Server-resolved rango state cookie name; the client reads it verbatim. */
|
|
47
|
+
stateCookieName?: string;
|
|
46
48
|
/** Theme configuration for FOUC prevention */
|
|
47
49
|
themeConfig?: ResolvedThemeConfig | null;
|
|
48
50
|
/** Initial theme from cookie (for SSR hydration) */
|
|
@@ -51,13 +53,16 @@ export interface RscPayload {
|
|
|
51
53
|
basename?: string;
|
|
52
54
|
/** Whether connection warmup is enabled */
|
|
53
55
|
warmupEnabled?: boolean;
|
|
54
|
-
/**
|
|
55
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Server-side redirect with optional state (for partial requests).
|
|
58
|
+
* `external: true` (from redirect(url, { external: true })) tells the client
|
|
59
|
+
* to hard-navigate to an off-host target instead of validating same-origin.
|
|
60
|
+
*/
|
|
61
|
+
redirect?: { url: string; external?: boolean };
|
|
56
62
|
/** Server-set location state to include in history.pushState */
|
|
57
63
|
locationState?: Record<string, unknown>;
|
|
58
64
|
};
|
|
59
65
|
returnValue?: { ok: boolean; data: unknown };
|
|
60
|
-
formState?: unknown;
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
/**
|
|
@@ -185,7 +190,7 @@ export interface CreateRSCHandlerOptions<
|
|
|
185
190
|
/**
|
|
186
191
|
* The RSC router instance
|
|
187
192
|
*/
|
|
188
|
-
router:
|
|
193
|
+
router: RangoInternal<TEnv, TRoutes>;
|
|
189
194
|
|
|
190
195
|
/**
|
|
191
196
|
* RSC dependencies from @vitejs/plugin-rsc/rsc.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Runtime-safe detection of a test runner (Vitest), used to decide whether a
|
|
2
|
+
// create*() call with no plugin-injected $$id may fall back to a synthetic id (a
|
|
3
|
+
// bare test) or must fail loud (dev / a real build).
|
|
4
|
+
//
|
|
5
|
+
// `process` is absent in some target runtimes (the browser, certain edge/worker
|
|
6
|
+
// RSC environments), so probe it through `globalThis` with optional chaining —
|
|
7
|
+
// NEVER a bare `process.env.VITEST`, which would ReferenceError before the
|
|
8
|
+
// intended error is thrown. Unlike `process.env.NODE_ENV` (folded by the app's
|
|
9
|
+
// build `define`), `VITEST` is not folded, so this stays a small runtime check;
|
|
10
|
+
// it lives only on the create*() error path (id missing), which never runs in a
|
|
11
|
+
// correct production build.
|
|
12
|
+
//
|
|
13
|
+
// Vitest sets `VITEST` in every test process — the node project and the
|
|
14
|
+
// react-server forks alike (the RSC project forces NODE_ENV=production, so NODE_ENV
|
|
15
|
+
// cannot distinguish it from a real build; `VITEST` can). A real build never sets it.
|
|
16
|
+
export function isUnderTestRunner(): boolean {
|
|
17
|
+
return !!globalThis.process?.env?.VITEST;
|
|
18
|
+
}
|
package/src/search-params.ts
CHANGED
|
@@ -7,10 +7,6 @@
|
|
|
7
7
|
* URLSearchParams instance.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
// ============================================================================
|
|
11
|
-
// Schema Types
|
|
12
|
-
// ============================================================================
|
|
13
|
-
|
|
14
10
|
/** Supported scalar types for search params (append ? for optional). */
|
|
15
11
|
export type SearchSchemaValue =
|
|
16
12
|
| "string"
|
|
@@ -23,10 +19,6 @@ export type SearchSchemaValue =
|
|
|
23
19
|
/** A search schema maps param names to their type descriptors. */
|
|
24
20
|
export type SearchSchema = Record<string, SearchSchemaValue>;
|
|
25
21
|
|
|
26
|
-
// ============================================================================
|
|
27
|
-
// Type-Level Schema Resolution
|
|
28
|
-
// ============================================================================
|
|
29
|
-
|
|
30
22
|
/** Strip trailing `?` from a schema value to get the base type. */
|
|
31
23
|
type BaseType<T extends string> = T extends `${infer B}?` ? B : T;
|
|
32
24
|
|
|
@@ -81,11 +73,11 @@ export type ResolveSearchSchema<T extends SearchSchema> = Simplify<
|
|
|
81
73
|
// ============================================================================
|
|
82
74
|
|
|
83
75
|
/** Resolve the global route map from RegisteredRoutes or GeneratedRouteMap. */
|
|
84
|
-
type GlobalRouteMap = keyof
|
|
85
|
-
? keyof
|
|
76
|
+
type GlobalRouteMap = keyof Rango.RegisteredRoutes extends never
|
|
77
|
+
? keyof Rango.GeneratedRouteMap extends never
|
|
86
78
|
? Record<string, string>
|
|
87
|
-
:
|
|
88
|
-
:
|
|
79
|
+
: Rango.GeneratedRouteMap
|
|
80
|
+
: Rango.RegisteredRoutes;
|
|
89
81
|
|
|
90
82
|
/**
|
|
91
83
|
* Extract the resolved search params type for a named route.
|
|
@@ -163,10 +155,6 @@ type ExtractParamsFromPattern<T extends string> =
|
|
|
163
155
|
: { [K in Param]: string }
|
|
164
156
|
: {};
|
|
165
157
|
|
|
166
|
-
// ============================================================================
|
|
167
|
-
// Runtime Parser
|
|
168
|
-
// ============================================================================
|
|
169
|
-
|
|
170
158
|
/**
|
|
171
159
|
* Parse URLSearchParams into a typed object using the given schema.
|
|
172
160
|
*
|
|
@@ -210,10 +198,6 @@ export function parseSearchParams<T extends SearchSchema>(
|
|
|
210
198
|
return result as ResolveSearchSchema<T>;
|
|
211
199
|
}
|
|
212
200
|
|
|
213
|
-
// ============================================================================
|
|
214
|
-
// Runtime Serializer
|
|
215
|
-
// ============================================================================
|
|
216
|
-
|
|
217
201
|
/**
|
|
218
202
|
* Serialize a typed search params object to a query string (without leading `?`).
|
|
219
203
|
* Skips `undefined` and `null` values.
|
|
@@ -26,7 +26,10 @@ const IS_BROWSER = typeof window !== "undefined";
|
|
|
26
26
|
|
|
27
27
|
interface LoaderCacheEntry {
|
|
28
28
|
sources: any[];
|
|
29
|
-
|
|
29
|
+
// buildLoaderPromise always returns a Promise, so the cached value is never a
|
|
30
|
+
// bare array. The public getMemoizedLoaderPromise return type stays broader
|
|
31
|
+
// (Promise<any[]> | any[]) to mirror its siblings.
|
|
32
|
+
promise: Promise<any[]>;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
const objectLoaderCache = IS_BROWSER
|
|
@@ -56,7 +59,16 @@ function hasSameReferences(a: any[], b: any[]): boolean {
|
|
|
56
59
|
return true;
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Build a fresh aggregate Promise.all over the loaders' resolved data refs.
|
|
64
|
+
* Unlike getMemoizedLoaderPromise this never caches, so each call yields a new
|
|
65
|
+
* Promise — correct for sites that await the result immediately (a shared,
|
|
66
|
+
* already-resolved promise would leak React's `.status` across server requests
|
|
67
|
+
* and skip the Suspense fallback).
|
|
68
|
+
*
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
export function buildLoaderPromise(loaders: ResolvedSegment[]): Promise<any[]> {
|
|
60
72
|
if (loaders.length === 0) {
|
|
61
73
|
return Promise.resolve([]);
|
|
62
74
|
}
|