@rangojs/router 0.0.0-experimental.a769fbe7 → 0.0.0-experimental.ac99d918
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 +211 -43
- package/dist/bin/rango.js +279 -102
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3313 -1160
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +62 -11
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +63 -1
- 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 +364 -0
- package/skills/hooks/SKILL.md +250 -30
- package/skills/host-router/SKILL.md +124 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +49 -5
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +223 -9
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +77 -7
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +250 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +66 -5
- package/skills/router-setup/SKILL.md +38 -3
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- 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 +319 -27
- 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 +1 -66
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +81 -147
- 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 +66 -14
- package/src/browser/navigation-client.ts +172 -109
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +79 -93
- package/src/browser/prefetch/cache.ts +180 -62
- package/src/browser/prefetch/fetch.ts +252 -39
- package/src/browser/prefetch/queue.ts +42 -8
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +72 -10
- package/src/browser/react/NavigationProvider.tsx +83 -31
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +49 -7
- 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 +23 -69
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +22 -5
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +111 -24
- package/src/browser/scroll-restoration.ts +29 -19
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -62
- package/src/browser/types.ts +60 -11
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +6 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +137 -32
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- 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 +333 -94
- package/src/build/route-types/scan-filter.ts +9 -2
- 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 +2256 -241
- 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 +108 -290
- 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/browser.ts +0 -1
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +70 -22
- 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 +69 -10
- package/src/index.ts +112 -14
- 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-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +413 -275
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +107 -32
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +50 -8
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +108 -9
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +46 -30
- package/src/router/intercept-resolution.ts +23 -23
- package/src/router/lazy-includes.ts +15 -52
- package/src/router/loader-resolution.ts +207 -30
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +40 -42
- package/src/router/match-api.ts +120 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +161 -262
- 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 +45 -14
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +87 -39
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -140
- package/src/router/middleware.ts +169 -140
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +109 -63
- package/src/router/prerender-match.ts +190 -54
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +63 -55
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +100 -35
- package/src/router/router-options.ts +91 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +119 -65
- 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 +329 -305
- 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 +91 -46
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +128 -42
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -409
- package/src/rsc/helpers.ts +162 -46
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +18 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +28 -4
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +90 -63
- package/src/rsc/rsc-rendering.ts +53 -56
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -69
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +22 -9
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +134 -0
- package/src/segment-system.tsx +208 -201
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +211 -52
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +26 -24
- package/src/server/loader-registry.ts +10 -28
- package/src/server/request-context.ts +289 -124
- package/src/ssr/index.tsx +22 -15
- package/src/static-handler.ts +27 -18
- 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 +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +124 -70
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +40 -11
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +12 -7
- package/src/types/segments.ts +36 -15
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +192 -99
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +51 -4
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +236 -6
- package/src/vite/plugins/cjs-to-esm.ts +8 -18
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- 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 +194 -0
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +11 -50
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
- package/src/vite/plugins/expose-internal-ids.ts +554 -317
- package/src/vite/plugins/performance-tracks.ts +65 -207
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +46 -17
- package/src/vite/rango.ts +207 -125
- package/src/vite/router-discovery.ts +931 -133
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/banner.ts +1 -1
- 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 +20 -52
- package/src/vite/utils/prerender-utils.ts +27 -29
- package/src/vite/utils/shared-utils.ts +97 -44
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/debug-channel.ts +0 -93
- 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
|
@@ -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,10 +183,15 @@ 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.
|
|
188
|
+
routerId: ctx.router.id,
|
|
211
189
|
segments: errorResult.segments,
|
|
212
190
|
isPartial: true,
|
|
213
191
|
matched: errorResult.matched,
|
|
214
192
|
diff: errorResult.diff,
|
|
193
|
+
resolvedIds: errorResult.resolvedIds,
|
|
194
|
+
params: errorResult.params,
|
|
215
195
|
isError: true,
|
|
216
196
|
handles: handleStore.stream(),
|
|
217
197
|
version: ctx.version,
|
|
@@ -223,32 +203,41 @@ export async function executeServerAction<TEnv>(
|
|
|
223
203
|
// location state is a success-only semantic. Error boundary responses
|
|
224
204
|
// update the error UI but should not mutate browser history state.
|
|
225
205
|
|
|
226
|
-
const debugChannel = requireRequestContext()._debugChannel;
|
|
227
206
|
const rscStream = ctx.renderToReadableStream<RscPayload>(payload, {
|
|
228
207
|
temporaryReferences,
|
|
229
|
-
|
|
208
|
+
onError: (error: unknown) => {
|
|
209
|
+
ctx.callOnError(error, "rendering", { request, url, env });
|
|
210
|
+
},
|
|
230
211
|
});
|
|
231
212
|
|
|
232
213
|
return createResponseWithMergedHeaders(rscStream, {
|
|
233
214
|
status: actionStatus,
|
|
234
|
-
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
|
+
},
|
|
235
221
|
});
|
|
236
222
|
}
|
|
237
223
|
}
|
|
238
224
|
|
|
239
225
|
// Build continuation for the revalidation phase
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
226
|
+
const actionMeta = loadedAction as
|
|
227
|
+
| { $id?: string; $$id?: string }
|
|
228
|
+
| undefined;
|
|
229
|
+
const resolvedActionId = actionMeta?.$id ?? actionMeta?.$$id ?? actionId;
|
|
244
230
|
|
|
245
231
|
return {
|
|
246
232
|
returnValue,
|
|
247
233
|
actionStatus,
|
|
248
234
|
temporaryReferences,
|
|
249
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.
|
|
250
239
|
actionId: resolvedActionId,
|
|
251
|
-
actionUrl: new URL(
|
|
240
|
+
actionUrl: new URL(url),
|
|
252
241
|
actionResult: returnValue.data,
|
|
253
242
|
formData: actionFormData,
|
|
254
243
|
},
|
|
@@ -287,8 +276,8 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
287
276
|
);
|
|
288
277
|
|
|
289
278
|
if (!matchResult) {
|
|
290
|
-
// matchPartial returns null when the route is a redirect or
|
|
291
|
-
//
|
|
279
|
+
// matchPartial returns null when the route is a redirect or no previous-URL
|
|
280
|
+
// context could be resolved. Check for redirect first.
|
|
292
281
|
const fullMatch = await ctx.router.match(request, { env });
|
|
293
282
|
setRequestContextParams(fullMatch.params, fullMatch.routeName);
|
|
294
283
|
|
|
@@ -299,14 +288,17 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
299
288
|
return createSimpleRedirectResponse(fullMatch.redirect);
|
|
300
289
|
}
|
|
301
290
|
|
|
302
|
-
// Non-redirect: this branch is only reachable when
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
//
|
|
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.
|
|
306
296
|
throw new Error(
|
|
307
297
|
`[RSC] matchPartial returned null for a non-redirect route ` +
|
|
308
298
|
`during action revalidation (${url.pathname}). This indicates ` +
|
|
309
|
-
`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.`,
|
|
310
302
|
);
|
|
311
303
|
}
|
|
312
304
|
|
|
@@ -316,10 +308,15 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
316
308
|
const payload: RscPayload = {
|
|
317
309
|
metadata: {
|
|
318
310
|
pathname: url.pathname,
|
|
311
|
+
// routerId exposed for the frontend (current app identity); see
|
|
312
|
+
// rsc-rendering.ts partial branch.
|
|
313
|
+
routerId: ctx.router.id,
|
|
319
314
|
segments: matchResult.segments,
|
|
320
315
|
isPartial: true,
|
|
321
316
|
matched: matchResult.matched,
|
|
322
317
|
diff: matchResult.diff,
|
|
318
|
+
resolvedIds: matchResult.resolvedIds,
|
|
319
|
+
params: matchResult.params,
|
|
323
320
|
slots: matchResult.slots,
|
|
324
321
|
handles: handleStore.stream(),
|
|
325
322
|
version: ctx.version,
|
|
@@ -327,11 +324,14 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
327
324
|
returnValue,
|
|
328
325
|
};
|
|
329
326
|
|
|
330
|
-
|
|
327
|
+
attachLocationStateIfPresent(payload);
|
|
331
328
|
|
|
332
329
|
const renderStart = performance.now();
|
|
333
330
|
const rscStream = ctx.renderToReadableStream<RscPayload>(payload, {
|
|
334
331
|
temporaryReferences,
|
|
332
|
+
onError: (error: unknown) => {
|
|
333
|
+
ctx.callOnError(error, "rendering", { request, url, env });
|
|
334
|
+
},
|
|
335
335
|
});
|
|
336
336
|
const rscSerializeDur = performance.now() - renderStart;
|
|
337
337
|
// This measures synchronous stream creation, not end-to-end stream consumption.
|
|
@@ -345,6 +345,11 @@ export async function revalidateAfterAction<TEnv>(
|
|
|
345
345
|
|
|
346
346
|
return createResponseWithMergedHeaders(rscStream, {
|
|
347
347
|
status: actionStatus,
|
|
348
|
-
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
|
+
},
|
|
349
354
|
});
|
|
350
355
|
}
|
package/src/rsc/ssr-setup.ts
CHANGED
|
@@ -77,7 +77,7 @@ export function getSSRSetup<TEnv>(
|
|
|
77
77
|
url: URL,
|
|
78
78
|
metricsStore: MetricsStore | undefined,
|
|
79
79
|
): Promise<SSRSetup> {
|
|
80
|
-
const early = _getRequestContext()?.
|
|
80
|
+
const early = _getRequestContext()?._variables?.[SSR_SETUP_VAR] as
|
|
81
81
|
| Promise<SSRSetup>
|
|
82
82
|
| undefined;
|
|
83
83
|
if (early) return early;
|
|
@@ -98,7 +98,7 @@ export function getSSRSetup<TEnv>(
|
|
|
98
98
|
* the isRscRequest decision in rsc-rendering.ts.
|
|
99
99
|
*
|
|
100
100
|
* Note: response/mime routes are excluded by the caller — this function
|
|
101
|
-
* runs after
|
|
101
|
+
* runs after classifyRequest() determines the request mode.
|
|
102
102
|
*/
|
|
103
103
|
export function mayNeedSSR(request: Request, url: URL): boolean {
|
|
104
104
|
if (
|
|
@@ -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
|
/**
|
|
@@ -19,10 +19,19 @@ export interface RscPayload {
|
|
|
19
19
|
metadata?: {
|
|
20
20
|
pathname: string;
|
|
21
21
|
segments: ResolvedSegment[];
|
|
22
|
+
/** Router instance ID. When this changes between navigations, the client
|
|
23
|
+
* discards cached segments and does a full tree replacement (app switch). */
|
|
24
|
+
routerId?: string;
|
|
22
25
|
isPartial?: boolean;
|
|
23
26
|
isError?: boolean;
|
|
24
27
|
matched?: string[];
|
|
25
28
|
diff?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* All segment ids re-resolved on the server, including null-component
|
|
31
|
+
* ones excluded from `segments`/`diff`. Drives client-side handle-bucket
|
|
32
|
+
* cleanup. Superset of `diff`. See MatchResult.resolvedIds.
|
|
33
|
+
*/
|
|
34
|
+
resolvedIds?: string[];
|
|
26
35
|
/** Merged route params from the matched route */
|
|
27
36
|
params?: Record<string, string>;
|
|
28
37
|
slots?: Record<string, SlotState>;
|
|
@@ -34,19 +43,26 @@ export interface RscPayload {
|
|
|
34
43
|
version?: string;
|
|
35
44
|
/** TTL in milliseconds for the client-side in-memory prefetch cache */
|
|
36
45
|
prefetchCacheTTL?: number;
|
|
46
|
+
/** Server-resolved rango state cookie name; the client reads it verbatim. */
|
|
47
|
+
stateCookieName?: string;
|
|
37
48
|
/** Theme configuration for FOUC prevention */
|
|
38
49
|
themeConfig?: ResolvedThemeConfig | null;
|
|
39
50
|
/** Initial theme from cookie (for SSR hydration) */
|
|
40
51
|
initialTheme?: Theme;
|
|
52
|
+
/** URL prefix for all routes (from createRouter({ basename })). */
|
|
53
|
+
basename?: string;
|
|
41
54
|
/** Whether connection warmup is enabled */
|
|
42
55
|
warmupEnabled?: boolean;
|
|
43
|
-
/**
|
|
44
|
-
|
|
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 };
|
|
45
62
|
/** Server-set location state to include in history.pushState */
|
|
46
63
|
locationState?: Record<string, unknown>;
|
|
47
64
|
};
|
|
48
65
|
returnValue?: { ok: boolean; data: unknown };
|
|
49
|
-
formState?: unknown;
|
|
50
66
|
}
|
|
51
67
|
|
|
52
68
|
/**
|
|
@@ -65,10 +81,7 @@ export interface RSCDependencies {
|
|
|
65
81
|
payload: T,
|
|
66
82
|
options?: {
|
|
67
83
|
temporaryReferences?: unknown;
|
|
68
|
-
|
|
69
|
-
readable?: ReadableStream;
|
|
70
|
-
writable?: WritableStream;
|
|
71
|
-
};
|
|
84
|
+
onError?: (error: unknown) => string | void;
|
|
72
85
|
},
|
|
73
86
|
) => ReadableStream<Uint8Array>;
|
|
74
87
|
|
|
@@ -177,7 +190,7 @@ export interface CreateRSCHandlerOptions<
|
|
|
177
190
|
/**
|
|
178
191
|
* The RSC router instance
|
|
179
192
|
*/
|
|
180
|
-
router:
|
|
193
|
+
router: RangoInternal<TEnv, TRoutes>;
|
|
181
194
|
|
|
182
195
|
/**
|
|
183
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.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stable Promise wrappers keyed on the component itself. Objects (React
|
|
5
|
+
* elements, functions, lazy payloads) land in a WeakMap so entries GC when
|
|
6
|
+
* the underlying component is released; primitives (string, number, boolean,
|
|
7
|
+
* null) land in a Map so memoization still applies to text-/null-backed
|
|
8
|
+
* segments like those in partial-update flows. Keeping this cache outside
|
|
9
|
+
* the segment eliminates preservation fields on ResolvedSegment — it survives
|
|
10
|
+
* reconciliation naturally because the component ref is what's stable.
|
|
11
|
+
*
|
|
12
|
+
* Browser-only. On the server each SSR render needs a fresh pending promise
|
|
13
|
+
* so Suspense can emit the loading fallback HTML before content streams. A
|
|
14
|
+
* shared already-resolved promise has `.status === "fulfilled"` attached by
|
|
15
|
+
* React on its first observation — subsequent `use()` calls return
|
|
16
|
+
* synchronously without suspending, so the Suspense fallback never makes it
|
|
17
|
+
* into the initial HTML. Route-definition components share refs across
|
|
18
|
+
* requests, so a global cache would leak tracked state between renders.
|
|
19
|
+
*/
|
|
20
|
+
const IS_BROWSER = typeof window !== "undefined";
|
|
21
|
+
const objectContentCache = IS_BROWSER
|
|
22
|
+
? new WeakMap<object, Promise<ReactNode>>()
|
|
23
|
+
: null;
|
|
24
|
+
const primitiveContentCache = IS_BROWSER
|
|
25
|
+
? new Map<unknown, Promise<ReactNode>>()
|
|
26
|
+
: null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Return a stable Promise wrapping `component`, memoized on the component ref.
|
|
30
|
+
*
|
|
31
|
+
* A fresh `Promise.resolve(component)` each render would suspend for one
|
|
32
|
+
* microtask and briefly commit the loading fallback inside Suspender — the
|
|
33
|
+
* intercept / parallel-slot flicker this indirection prevents. Reusing the
|
|
34
|
+
* same Promise ref keeps React's `use()` in "known fulfilled" state after
|
|
35
|
+
* the first observation.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export function getMemoizedContentPromise(
|
|
40
|
+
component: ReactNode,
|
|
41
|
+
): Promise<ReactNode> {
|
|
42
|
+
if (component instanceof Promise) {
|
|
43
|
+
return component as Promise<ReactNode>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!objectContentCache || !primitiveContentCache) {
|
|
47
|
+
return Promise.resolve(component);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (component !== null && typeof component === "object") {
|
|
51
|
+
const cached = objectContentCache.get(component);
|
|
52
|
+
if (cached) {
|
|
53
|
+
return cached;
|
|
54
|
+
}
|
|
55
|
+
const promise = Promise.resolve(component);
|
|
56
|
+
objectContentCache.set(component, promise);
|
|
57
|
+
return promise;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const cached = primitiveContentCache.get(component);
|
|
61
|
+
if (cached) {
|
|
62
|
+
return cached;
|
|
63
|
+
}
|
|
64
|
+
const promise = Promise.resolve(component);
|
|
65
|
+
primitiveContentCache.set(component, promise);
|
|
66
|
+
return promise;
|
|
67
|
+
}
|