@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
|
@@ -11,7 +11,14 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
14
|
+
import type { CacheErrorCategory } from "../cache/cache-error.js";
|
|
14
15
|
import type { CookieOptions } from "../router/middleware.js";
|
|
16
|
+
import {
|
|
17
|
+
KEEP_CACHE_HEADER,
|
|
18
|
+
getRawCookieValue,
|
|
19
|
+
mintStateValue,
|
|
20
|
+
serializeStateCookie,
|
|
21
|
+
} from "../browser/cookie-name.js";
|
|
15
22
|
import type { LoaderDefinition, LoaderContext } from "../types.js";
|
|
16
23
|
import type { ScopedReverseFunction } from "../reverse.js";
|
|
17
24
|
import type {
|
|
@@ -33,6 +40,7 @@ import {
|
|
|
33
40
|
type HandleData,
|
|
34
41
|
} from "./handle-store.js";
|
|
35
42
|
import { isHandle } from "../handle.js";
|
|
43
|
+
import { withDefer } from "../defer.js";
|
|
36
44
|
import { track, type MetricsStore } from "./context.js";
|
|
37
45
|
import { getFetchableLoader } from "./fetchable-loader-store.js";
|
|
38
46
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
@@ -102,6 +110,10 @@ export interface RequestContext<
|
|
|
102
110
|
setStatus(status: number): void;
|
|
103
111
|
/** @internal Set status bypassing cache-exec guard (for framework error handling) */
|
|
104
112
|
_setStatus(status: number): void;
|
|
113
|
+
/** @internal Rotate the rango state cookie (server seat of invalidateClientCache). */
|
|
114
|
+
_rotateStateCookie(): void;
|
|
115
|
+
/** @internal Set the keepClientCache() directive header on the response. */
|
|
116
|
+
_setKeepCacheDirective(): void;
|
|
105
117
|
|
|
106
118
|
/**
|
|
107
119
|
* Access loader data or push handle data.
|
|
@@ -140,6 +152,25 @@ export interface RequestContext<
|
|
|
140
152
|
/** @internal Cache store for segment caching (optional, used by CacheScope) */
|
|
141
153
|
_cacheStore?: SegmentCacheStore;
|
|
142
154
|
|
|
155
|
+
/**
|
|
156
|
+
* @internal Handler-owned registry of explicit per-scope stores from
|
|
157
|
+
* cache({ store }). Created once per createRSCHandler() and threaded into
|
|
158
|
+
* every request context, so it accumulates every explicit store the handler
|
|
159
|
+
* resolves. updateTag()/revalidateTag() iterate this set plus _cacheStore to
|
|
160
|
+
* reach every store that may hold tagged entries. The app-level store is not
|
|
161
|
+
* added here (it is always reachable via _cacheStore).
|
|
162
|
+
*/
|
|
163
|
+
_explicitTaggedStores?: Set<SegmentCacheStore>;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* @internal Union of every cache tag resolved while producing this request's
|
|
167
|
+
* response (from cache({ tags }), runtime cacheTag(), and loader cache tags).
|
|
168
|
+
* Populated at the tag-resolution sites via recordRequestTags(). Read by the
|
|
169
|
+
* document cache middleware so a full-page entry is tagged with everything its
|
|
170
|
+
* content used and can therefore be invalidated by updateTag()/revalidateTag().
|
|
171
|
+
*/
|
|
172
|
+
_requestTags: Set<string>;
|
|
173
|
+
|
|
143
174
|
/** @internal Cache profiles for "use cache" profile resolution (per-router) */
|
|
144
175
|
_cacheProfiles?: Record<
|
|
145
176
|
string,
|
|
@@ -273,7 +304,9 @@ export interface RequestContext<
|
|
|
273
304
|
|
|
274
305
|
/**
|
|
275
306
|
* @internal Set to true when the matched entry tree contains any `loading()`
|
|
276
|
-
* entries (streaming).
|
|
307
|
+
* entries (streaming). On a streaming tree rendered() waits for the streaming
|
|
308
|
+
* handlers to settle (via handleStore.settled) before resolving, and the
|
|
309
|
+
* deadlock guard state is kept live until that wait completes.
|
|
277
310
|
*/
|
|
278
311
|
_treeHasStreaming?: boolean;
|
|
279
312
|
|
|
@@ -297,6 +330,18 @@ export interface RequestContext<
|
|
|
297
330
|
*/
|
|
298
331
|
_renderBarrierHandleSnapshot?: HandleData;
|
|
299
332
|
|
|
333
|
+
/**
|
|
334
|
+
* @internal The deadlock guard window is closed (no further handler-awaits-
|
|
335
|
+
* loader cycle is possible). For non-streaming trees this is set when the
|
|
336
|
+
* barrier resolves. For streaming trees the window stays open until
|
|
337
|
+
* handleStore.settled — rendered() keeps waiting past the barrier and a
|
|
338
|
+
* loading() handler can still resume and await a still-waiting loader — so it
|
|
339
|
+
* is set only after settled. The guard (loader-resolution `setupLoaderAccess`)
|
|
340
|
+
* reads this instead of `_renderBarrierSegmentOrder` so it does not go blind
|
|
341
|
+
* during the streaming settle wait.
|
|
342
|
+
*/
|
|
343
|
+
_renderBarrierGuardClosed?: boolean;
|
|
344
|
+
|
|
300
345
|
/** @internal Per-request error dedup set for onError reporting */
|
|
301
346
|
_reportedErrors: WeakSet<object>;
|
|
302
347
|
|
|
@@ -304,9 +349,13 @@ export interface RequestContext<
|
|
|
304
349
|
* @internal Report a non-fatal background error through the router's
|
|
305
350
|
* onError callback. Wired by the RSC handler / router during request
|
|
306
351
|
* creation. Cache-runtime and other subsystems call this to surface
|
|
307
|
-
* errors without failing the response.
|
|
352
|
+
* errors without failing the response. `category` is surfaced to consumers as
|
|
353
|
+
* `metadata.category` on the onError context (phase `cache`).
|
|
308
354
|
*/
|
|
309
|
-
_reportBackgroundError?: (
|
|
355
|
+
_reportBackgroundError?: (
|
|
356
|
+
error: unknown,
|
|
357
|
+
category: CacheErrorCategory,
|
|
358
|
+
) => void;
|
|
310
359
|
|
|
311
360
|
/** @internal Per-request debug performance override (set via ctx.debugPerformance()) */
|
|
312
361
|
_debugPerformance?: boolean;
|
|
@@ -322,6 +371,15 @@ export interface RequestContext<
|
|
|
322
371
|
* to avoid a second resolveRoute call. Cleared on HMR invalidation.
|
|
323
372
|
*/
|
|
324
373
|
_classifiedRoute?: import("../router/route-snapshot.js").RouteSnapshot;
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* @internal Coarse route-level cache signal for the X-Rango-Cache debug
|
|
377
|
+
* header. Populated by match/matchPartial only when the debug cache signal
|
|
378
|
+
* gate is enabled (debugCacheSignal option or RANGO_TEST_SIGNALS=1). Read by
|
|
379
|
+
* the response-finalization path (createResponseWithMergedHeaders). Undefined
|
|
380
|
+
* when the gate is off, so no header is emitted.
|
|
381
|
+
*/
|
|
382
|
+
_cacheSignal?: import("../router/telemetry.js").CacheSegmentSignal[];
|
|
325
383
|
}
|
|
326
384
|
|
|
327
385
|
/**
|
|
@@ -341,6 +399,8 @@ export type PublicRequestContext<
|
|
|
341
399
|
| "deleteCookie"
|
|
342
400
|
| "_handleStore"
|
|
343
401
|
| "_cacheStore"
|
|
402
|
+
| "_explicitTaggedStores"
|
|
403
|
+
| "_requestTags"
|
|
344
404
|
| "_cacheProfiles"
|
|
345
405
|
| "_onResponseCallbacks"
|
|
346
406
|
| "_themeConfig"
|
|
@@ -355,13 +415,17 @@ export type PublicRequestContext<
|
|
|
355
415
|
| "_renderBarrierWaiters"
|
|
356
416
|
| "_handlerLoaderDeps"
|
|
357
417
|
| "_renderBarrierHandleSnapshot"
|
|
418
|
+
| "_renderBarrierGuardClosed"
|
|
358
419
|
| "_reportBackgroundError"
|
|
359
420
|
| "_debugPerformance"
|
|
360
421
|
| "_metricsStore"
|
|
361
422
|
| "_basename"
|
|
362
423
|
| "_setStatus"
|
|
424
|
+
| "_rotateStateCookie"
|
|
425
|
+
| "_setKeepCacheDirective"
|
|
363
426
|
| "_variables"
|
|
364
427
|
| "_classifiedRoute"
|
|
428
|
+
| "_cacheSignal"
|
|
365
429
|
| "res"
|
|
366
430
|
>;
|
|
367
431
|
|
|
@@ -413,6 +477,7 @@ export function _getRequestContext<TEnv = DefaultEnv>():
|
|
|
413
477
|
export function setRequestContextParams(
|
|
414
478
|
params: Record<string, string>,
|
|
415
479
|
routeName?: string,
|
|
480
|
+
routeMap?: Record<string, string>,
|
|
416
481
|
): void {
|
|
417
482
|
const ctx = requestContextStorage.getStore();
|
|
418
483
|
if (ctx) {
|
|
@@ -425,9 +490,13 @@ export function setRequestContextParams(
|
|
|
425
490
|
: undefined
|
|
426
491
|
) as DefaultRouteName | undefined;
|
|
427
492
|
}
|
|
428
|
-
// Update reverse with scoped resolution now that route is known
|
|
493
|
+
// Update reverse with scoped resolution now that route is known. Production
|
|
494
|
+
// omits routeMap and uses the global map (routes are registered globally);
|
|
495
|
+
// the testing primitives (renderToFlightString/renderServerTree) pass a
|
|
496
|
+
// scoped routeMap so `ctx.reverse` is not order-dependent on whatever router
|
|
497
|
+
// registered last.
|
|
429
498
|
ctx.reverse = createReverseFunction(
|
|
430
|
-
getGlobalRouteMap(),
|
|
499
|
+
routeMap ?? getGlobalRouteMap(),
|
|
431
500
|
routeName,
|
|
432
501
|
params,
|
|
433
502
|
routeName ? isRouteRootScoped(routeName) : undefined,
|
|
@@ -485,6 +554,11 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
485
554
|
initialResponse?: Response;
|
|
486
555
|
/** Optional cache store for segment caching (used by CacheScope) */
|
|
487
556
|
cacheStore?: SegmentCacheStore;
|
|
557
|
+
/**
|
|
558
|
+
* Handler-owned registry of explicit per-scope stores for cross-store tag
|
|
559
|
+
* invalidation. Created once per handler, reused across requests.
|
|
560
|
+
*/
|
|
561
|
+
explicitTaggedStores?: Set<SegmentCacheStore>;
|
|
488
562
|
/** Optional cache profiles for "use cache" resolution (per-router) */
|
|
489
563
|
cacheProfiles?: Record<
|
|
490
564
|
string,
|
|
@@ -494,6 +568,10 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
494
568
|
executionContext?: ExecutionContext;
|
|
495
569
|
/** Optional theme configuration (enables ctx.theme and ctx.setTheme) */
|
|
496
570
|
themeConfig?: ResolvedThemeConfig | null;
|
|
571
|
+
/** Resolved rango state cookie name, for the server seat of invalidateClientCache(). */
|
|
572
|
+
stateCookieName?: string;
|
|
573
|
+
/** Build version, used as the prefix of a server-rotated rango state value. */
|
|
574
|
+
version?: string;
|
|
497
575
|
}
|
|
498
576
|
|
|
499
577
|
/**
|
|
@@ -514,15 +592,17 @@ export function createRequestContext<TEnv>(
|
|
|
514
592
|
variables,
|
|
515
593
|
initialResponse,
|
|
516
594
|
cacheStore,
|
|
595
|
+
explicitTaggedStores,
|
|
517
596
|
cacheProfiles,
|
|
518
597
|
executionContext,
|
|
519
598
|
themeConfig,
|
|
599
|
+
stateCookieName,
|
|
600
|
+
version: stateVersion,
|
|
520
601
|
} = options;
|
|
521
602
|
const cookieHeader = request.headers.get("Cookie");
|
|
603
|
+
let rangoStateRotated = false;
|
|
522
604
|
let parsedCookies: Record<string, string> | null = null;
|
|
523
605
|
|
|
524
|
-
// Create stub response for collecting headers/cookies.
|
|
525
|
-
// All cookie/header mutations go here; cookie reads derive from it.
|
|
526
606
|
let stubResponse = initialResponse
|
|
527
607
|
? new Response(null, {
|
|
528
608
|
status: initialResponse.status,
|
|
@@ -531,11 +611,9 @@ export function createRequestContext<TEnv>(
|
|
|
531
611
|
})
|
|
532
612
|
: new Response(null, { status: 200 });
|
|
533
613
|
|
|
534
|
-
// Create handle store and loader memoization for this request
|
|
535
614
|
const handleStore = createHandleStore();
|
|
536
615
|
const loaderPromises = new Map<string, Promise<any>>();
|
|
537
616
|
|
|
538
|
-
// Lazy parse cookies from the original Cookie header
|
|
539
617
|
const getParsedCookies = (): Record<string, string> => {
|
|
540
618
|
if (!parsedCookies) {
|
|
541
619
|
parsedCookies = parseCookiesFromHeader(cookieHeader);
|
|
@@ -543,7 +621,6 @@ export function createRequestContext<TEnv>(
|
|
|
543
621
|
return parsedCookies;
|
|
544
622
|
};
|
|
545
623
|
|
|
546
|
-
// Cached response cookie mutations — invalidated on setCookie/deleteCookie/setTheme
|
|
547
624
|
let responseCookieCache: Map<string, string | null> | null = null;
|
|
548
625
|
const getResponseCookies = (): Map<string, string | null> => {
|
|
549
626
|
if (!responseCookieCache) {
|
|
@@ -555,8 +632,6 @@ export function createRequestContext<TEnv>(
|
|
|
555
632
|
responseCookieCache = null;
|
|
556
633
|
};
|
|
557
634
|
|
|
558
|
-
// Guard: throw if a response-level side effect is called inside a cache() scope.
|
|
559
|
-
// Uses ALS to detect the scope (set during segment resolution).
|
|
560
635
|
function assertNotInsideCacheScopeALS(methodName: string): void {
|
|
561
636
|
if (isInsideCacheScope()) {
|
|
562
637
|
throw new Error(
|
|
@@ -567,8 +642,7 @@ export function createRequestContext<TEnv>(
|
|
|
567
642
|
}
|
|
568
643
|
}
|
|
569
644
|
|
|
570
|
-
//
|
|
571
|
-
// The stub IS the source of truth for same-request mutations.
|
|
645
|
+
// Response stub Set-Cookie wins, then original header (source of truth for mutations).
|
|
572
646
|
const effectiveCookie = (name: string): string | undefined => {
|
|
573
647
|
const mutations = getResponseCookies();
|
|
574
648
|
if (mutations.has(name)) {
|
|
@@ -578,14 +652,11 @@ export function createRequestContext<TEnv>(
|
|
|
578
652
|
return getParsedCookies()[name];
|
|
579
653
|
};
|
|
580
654
|
|
|
581
|
-
// Theme helpers (only used when themeConfig is provided)
|
|
582
655
|
const getTheme = (): Theme | undefined => {
|
|
583
656
|
if (!themeConfig) return undefined;
|
|
584
657
|
|
|
585
|
-
// Use overlay-aware read so setTheme() in the same request is reflected
|
|
586
658
|
const stored = effectiveCookie(themeConfig.storageKey);
|
|
587
659
|
if (stored) {
|
|
588
|
-
// Validate stored value
|
|
589
660
|
if (stored === "system" && themeConfig.enableSystem) {
|
|
590
661
|
return "system";
|
|
591
662
|
}
|
|
@@ -599,7 +670,6 @@ export function createRequestContext<TEnv>(
|
|
|
599
670
|
const setTheme = (theme: Theme): void => {
|
|
600
671
|
if (!themeConfig) return;
|
|
601
672
|
|
|
602
|
-
// Validate theme value
|
|
603
673
|
if (theme !== "system" && !themeConfig.themes.includes(theme)) {
|
|
604
674
|
console.warn(
|
|
605
675
|
`[Theme] Invalid theme value: "${theme}". Valid values: system, ${themeConfig.themes.join(", ")}`,
|
|
@@ -607,7 +677,6 @@ export function createRequestContext<TEnv>(
|
|
|
607
677
|
return;
|
|
608
678
|
}
|
|
609
679
|
|
|
610
|
-
// Write to stub — effectiveCookie() will pick it up on next read
|
|
611
680
|
stubResponse.headers.append(
|
|
612
681
|
"Set-Cookie",
|
|
613
682
|
serializeCookieValue(themeConfig.storageKey, theme, {
|
|
@@ -619,10 +688,8 @@ export function createRequestContext<TEnv>(
|
|
|
619
688
|
invalidateResponseCookieCache();
|
|
620
689
|
};
|
|
621
690
|
|
|
622
|
-
// Strip internal _rsc* params so userland sees a clean URL.
|
|
623
691
|
const cleanUrl = stripInternalParams(url);
|
|
624
692
|
|
|
625
|
-
// Build the context object first (without use), then add use
|
|
626
693
|
const ctx: RequestContext<TEnv> = {
|
|
627
694
|
env,
|
|
628
695
|
request,
|
|
@@ -708,6 +775,45 @@ export function createRequestContext<TEnv>(
|
|
|
708
775
|
stubResponse.headers.set(name, value);
|
|
709
776
|
},
|
|
710
777
|
|
|
778
|
+
// Rotate the rango state cookie for the responding client (the server seat
|
|
779
|
+
// of invalidateClientCache). Writes ONE Set-Cookie per request with the
|
|
780
|
+
// value {version}:{timestamp}; the `:` stays raw (the cookie-name.ts
|
|
781
|
+
// serializer), not the URL-encoded form serializeCookieValue would produce.
|
|
782
|
+
// The timestamp is strictly greater than the client's current one (inbound
|
|
783
|
+
// X-Rango-State), so a same-millisecond server rotation still differs from
|
|
784
|
+
// the client value and the divergence observer fires.
|
|
785
|
+
_rotateStateCookie(): void {
|
|
786
|
+
if (rangoStateRotated) return;
|
|
787
|
+
rangoStateRotated = true;
|
|
788
|
+
if (!stateCookieName) return;
|
|
789
|
+
// The client's current value, for the monotonic guard: prefer the
|
|
790
|
+
// X-Rango-State header (router navigation/prefetch fetches send it), but
|
|
791
|
+
// fall back to the request's rango state cookie — action POSTs / plain
|
|
792
|
+
// app fetch()s carry no router header yet DO send the cookie. Without the
|
|
793
|
+
// fallback, prevTs stays 0 and a same-ms mint can equal the client value,
|
|
794
|
+
// leaving the divergence observer silent. `|| null` so an empty header
|
|
795
|
+
// ('' from proxy normalization) falls through instead of short-circuiting.
|
|
796
|
+
// getRawCookieValue reads the cookie undecoded (the wire value
|
|
797
|
+
// decodeStateValue decodes exactly once) AND is the same parser the client
|
|
798
|
+
// mirror uses, so both seats read the same jar entry.
|
|
799
|
+
const prevRaw =
|
|
800
|
+
(request.headers.get("x-rango-state") || null) ??
|
|
801
|
+
getRawCookieValue(cookieHeader, stateCookieName);
|
|
802
|
+
const value = mintStateValue(stateVersion ?? "0", prevRaw);
|
|
803
|
+
stubResponse.headers.append(
|
|
804
|
+
"Set-Cookie",
|
|
805
|
+
serializeStateCookie(stateCookieName, value, url.protocol === "https:"),
|
|
806
|
+
);
|
|
807
|
+
invalidateResponseCookieCache();
|
|
808
|
+
},
|
|
809
|
+
|
|
810
|
+
// Set the keepClientCache() directive header. The action bridge reads it on
|
|
811
|
+
// the response and suppresses its automatic invalidation. `.set` makes this
|
|
812
|
+
// idempotent (one header regardless of call count).
|
|
813
|
+
_setKeepCacheDirective(): void {
|
|
814
|
+
stubResponse.headers.set(KEEP_CACHE_HEADER, "1");
|
|
815
|
+
},
|
|
816
|
+
|
|
711
817
|
setStatus(status: number): void {
|
|
712
818
|
assertNotInsideCacheExec(ctx, "setStatus");
|
|
713
819
|
assertNotInsideCacheScopeALS("setStatus");
|
|
@@ -724,13 +830,14 @@ export function createRequestContext<TEnv>(
|
|
|
724
830
|
});
|
|
725
831
|
},
|
|
726
832
|
|
|
727
|
-
// Placeholder - will be replaced below
|
|
728
833
|
use: null as any,
|
|
729
834
|
|
|
730
835
|
method: request.method,
|
|
731
836
|
|
|
732
837
|
_handleStore: handleStore,
|
|
733
838
|
_cacheStore: cacheStore,
|
|
839
|
+
_explicitTaggedStores: explicitTaggedStores,
|
|
840
|
+
_requestTags: new Set<string>(),
|
|
734
841
|
_cacheProfiles: cacheProfiles,
|
|
735
842
|
|
|
736
843
|
waitUntil(fn: () => Promise<void>): void {
|
|
@@ -751,7 +858,6 @@ export function createRequestContext<TEnv>(
|
|
|
751
858
|
this._onResponseCallbacks.push(callback);
|
|
752
859
|
},
|
|
753
860
|
|
|
754
|
-
// Theme properties (only set when themeConfig is provided)
|
|
755
861
|
get theme() {
|
|
756
862
|
return themeConfig ? getTheme() : undefined;
|
|
757
863
|
},
|
|
@@ -775,19 +881,17 @@ export function createRequestContext<TEnv>(
|
|
|
775
881
|
_reportedErrors: new WeakSet<object>(),
|
|
776
882
|
_metricsStore: undefined,
|
|
777
883
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
_resolveRenderBarrier: null as any, // set below
|
|
884
|
+
_renderBarrier: null as any,
|
|
885
|
+
_resolveRenderBarrier: null as any,
|
|
781
886
|
_renderBarrierSegmentOrder: undefined,
|
|
782
887
|
|
|
783
888
|
reverse: createReverseFunction(getGlobalRouteMap(), undefined, {}),
|
|
784
889
|
};
|
|
785
890
|
|
|
786
|
-
// Lazy
|
|
787
|
-
// calls rendered(). Requests that don't use rendered() pay zero cost.
|
|
891
|
+
// Lazy allocation: only create Promise when a loader calls rendered().
|
|
788
892
|
let barrierResolved = false;
|
|
789
893
|
let resolveBarrier: (() => void) | undefined;
|
|
790
|
-
ctx._renderBarrier = null as any;
|
|
894
|
+
ctx._renderBarrier = null as any;
|
|
791
895
|
ctx._resolveRenderBarrier = (
|
|
792
896
|
segments: Array<{ type: string; id: string }>,
|
|
793
897
|
) => {
|
|
@@ -797,21 +901,26 @@ export function createRequestContext<TEnv>(
|
|
|
797
901
|
.filter((s) => s.type !== "loader")
|
|
798
902
|
.map((s) => s.id);
|
|
799
903
|
ctx._renderBarrierSegmentOrder = segOrder;
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
ctx.
|
|
904
|
+
|
|
905
|
+
const closeGuard = () => {
|
|
906
|
+
ctx._renderBarrierWaiters = undefined;
|
|
907
|
+
ctx._handlerLoaderDeps = undefined;
|
|
908
|
+
ctx._renderBarrierGuardClosed = true;
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
if (ctx._treeHasStreaming) {
|
|
912
|
+
handleStore.settled.then(closeGuard);
|
|
913
|
+
} else {
|
|
914
|
+
ctx._renderBarrierHandleSnapshot = buildHandleSnapshot(
|
|
915
|
+
handleStore,
|
|
916
|
+
segOrder,
|
|
917
|
+
);
|
|
918
|
+
closeGuard();
|
|
919
|
+
}
|
|
808
920
|
if (resolveBarrier) resolveBarrier();
|
|
809
921
|
};
|
|
810
922
|
Object.defineProperty(ctx, "_renderBarrier", {
|
|
811
923
|
get() {
|
|
812
|
-
// Barrier already resolved (cache/prerender hit) or first lazy access.
|
|
813
|
-
// Either way, replace the getter with a concrete value to avoid
|
|
814
|
-
// repeated Promise.resolve() allocations on subsequent reads.
|
|
815
924
|
const p = barrierResolved
|
|
816
925
|
? Promise.resolve()
|
|
817
926
|
: new Promise<void>((resolve) => {
|
|
@@ -827,24 +936,16 @@ export function createRequestContext<TEnv>(
|
|
|
827
936
|
configurable: true,
|
|
828
937
|
});
|
|
829
938
|
|
|
830
|
-
// Now create use() with access to ctx
|
|
831
939
|
ctx.use = createUseFunction({
|
|
832
940
|
handleStore,
|
|
833
941
|
loaderPromises,
|
|
834
942
|
getContext: () => ctx,
|
|
835
943
|
});
|
|
836
944
|
|
|
837
|
-
// Brand with taint symbol so "use cache" excludes ctx from cache keys
|
|
838
945
|
(ctx as any)[NOCACHE_SYMBOL] = true;
|
|
839
946
|
return ctx;
|
|
840
947
|
}
|
|
841
948
|
|
|
842
|
-
/**
|
|
843
|
-
* Parse Set-Cookie headers from a response into effective cookie state.
|
|
844
|
-
* Returns a map of cookie name -> value (string) or name -> null (deleted).
|
|
845
|
-
* Last-write-wins: later Set-Cookie entries for the same name overwrite earlier ones.
|
|
846
|
-
* Max-Age=0 is treated as a delete.
|
|
847
|
-
*/
|
|
848
949
|
const MAX_AGE_ZERO_RE = /;\s*Max-Age\s*=\s*0/i;
|
|
849
950
|
|
|
850
951
|
function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
@@ -852,7 +953,6 @@ function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
|
852
953
|
const setCookies = response.headers.getSetCookie();
|
|
853
954
|
|
|
854
955
|
for (const header of setCookies) {
|
|
855
|
-
// First segment before ';' is the name=value pair
|
|
856
956
|
const semiIdx = header.indexOf(";");
|
|
857
957
|
const pair = semiIdx === -1 ? header : header.substring(0, semiIdx);
|
|
858
958
|
const eqIdx = pair.indexOf("=");
|
|
@@ -864,11 +964,9 @@ function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
|
864
964
|
name = decodeURIComponent(pair.substring(0, eqIdx).trim());
|
|
865
965
|
value = decodeURIComponent(pair.substring(eqIdx + 1).trim());
|
|
866
966
|
} catch {
|
|
867
|
-
// Malformed encoding — skip this entry
|
|
868
967
|
continue;
|
|
869
968
|
}
|
|
870
969
|
|
|
871
|
-
// Max-Age=0 means the cookie is being deleted
|
|
872
970
|
const isDeleted = MAX_AGE_ZERO_RE.test(header);
|
|
873
971
|
result.set(name, isDeleted ? null : value);
|
|
874
972
|
}
|
|
@@ -876,10 +974,10 @@ function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
|
876
974
|
return result;
|
|
877
975
|
}
|
|
878
976
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
function parseCookiesFromHeader(
|
|
977
|
+
// Exported for unit tests; the canonical cookie parse/serialize lives here
|
|
978
|
+
// (a duplicate copy in middleware-cookies.ts was removed). Not part of the
|
|
979
|
+
// public export surface.
|
|
980
|
+
export function parseCookiesFromHeader(
|
|
883
981
|
cookieHeader: string | null,
|
|
884
982
|
): Record<string, string> {
|
|
885
983
|
if (!cookieHeader) return {};
|
|
@@ -894,7 +992,7 @@ function parseCookiesFromHeader(
|
|
|
894
992
|
try {
|
|
895
993
|
cookies[name] = decodeURIComponent(raw);
|
|
896
994
|
} catch {
|
|
897
|
-
// Malformed percent-
|
|
995
|
+
// Malformed percent-encoding: fall back to raw value
|
|
898
996
|
cookies[name] = raw;
|
|
899
997
|
}
|
|
900
998
|
}
|
|
@@ -903,10 +1001,7 @@ function parseCookiesFromHeader(
|
|
|
903
1001
|
return cookies;
|
|
904
1002
|
}
|
|
905
1003
|
|
|
906
|
-
|
|
907
|
-
* Serialize a cookie for Set-Cookie header
|
|
908
|
-
*/
|
|
909
|
-
function serializeCookieValue(
|
|
1004
|
+
export function serializeCookieValue(
|
|
910
1005
|
name: string,
|
|
911
1006
|
value: string,
|
|
912
1007
|
options: CookieOptions = {},
|
|
@@ -933,20 +1028,12 @@ export interface CreateUseFunctionOptions<TEnv> {
|
|
|
933
1028
|
getContext: () => RequestContext<TEnv>;
|
|
934
1029
|
}
|
|
935
1030
|
|
|
936
|
-
/**
|
|
937
|
-
* Create the use() function for loader and handle composition.
|
|
938
|
-
*
|
|
939
|
-
* This is the unified implementation used by both RequestContext and HandlerContext.
|
|
940
|
-
* - For loaders: executes and memoizes loader functions
|
|
941
|
-
* - For handles: returns a push function to add handle data
|
|
942
|
-
*/
|
|
943
1031
|
export function createUseFunction<TEnv>(
|
|
944
1032
|
options: CreateUseFunctionOptions<TEnv>,
|
|
945
1033
|
): RequestContext["use"] {
|
|
946
1034
|
const { handleStore, loaderPromises, getContext } = options;
|
|
947
1035
|
|
|
948
1036
|
return ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
949
|
-
// Handle case: return a push function
|
|
950
1037
|
if (isHandle(item)) {
|
|
951
1038
|
const handle = item;
|
|
952
1039
|
const ctx = getContext();
|
|
@@ -959,30 +1046,24 @@ export function createUseFunction<TEnv>(
|
|
|
959
1046
|
);
|
|
960
1047
|
}
|
|
961
1048
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
// Push directly - promises will be serialized by RSC and streamed
|
|
973
|
-
handleStore.push(handle.$$id, segmentId, valueOrPromise);
|
|
974
|
-
};
|
|
1049
|
+
return withDefer(
|
|
1050
|
+
(dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>)) => {
|
|
1051
|
+
const valueOrPromise =
|
|
1052
|
+
typeof dataOrFn === "function"
|
|
1053
|
+
? (dataOrFn as () => Promise<unknown>)()
|
|
1054
|
+
: dataOrFn;
|
|
1055
|
+
|
|
1056
|
+
handleStore.push(handle.$$id, segmentId, valueOrPromise);
|
|
1057
|
+
},
|
|
1058
|
+
);
|
|
975
1059
|
}
|
|
976
1060
|
|
|
977
|
-
// Loader case
|
|
978
1061
|
const loader = item as LoaderDefinition<any, any>;
|
|
979
1062
|
|
|
980
|
-
// Return cached promise if already started
|
|
981
1063
|
if (loaderPromises.has(loader.$$id)) {
|
|
982
1064
|
return loaderPromises.get(loader.$$id);
|
|
983
1065
|
}
|
|
984
1066
|
|
|
985
|
-
// Get loader function - either from loader object or fetchable registry
|
|
986
1067
|
let loaderFn = loader.fn;
|
|
987
1068
|
if (!loaderFn) {
|
|
988
1069
|
const fetchable = getFetchableLoader(loader.$$id);
|
|
@@ -999,7 +1080,6 @@ export function createUseFunction<TEnv>(
|
|
|
999
1080
|
|
|
1000
1081
|
const ctx = getContext();
|
|
1001
1082
|
|
|
1002
|
-
// Create loader context with recursive use() support
|
|
1003
1083
|
const loaderCtx: LoaderContext<Record<string, string | undefined>, TEnv> = {
|
|
1004
1084
|
params: ctx.params,
|
|
1005
1085
|
routeParams: (ctx.params ?? {}) as Record<string, string>,
|
|
@@ -1016,7 +1096,6 @@ export function createUseFunction<TEnv>(
|
|
|
1016
1096
|
use: (<TDep, TDepParams = any>(
|
|
1017
1097
|
dep: LoaderDefinition<TDep, TDepParams>,
|
|
1018
1098
|
): Promise<TDep> => {
|
|
1019
|
-
// Recursive call - will start dep loader if not already started
|
|
1020
1099
|
return ctx.use(dep);
|
|
1021
1100
|
}) as LoaderContext["use"],
|
|
1022
1101
|
method: "GET",
|
|
@@ -1040,7 +1119,6 @@ export function createUseFunction<TEnv>(
|
|
|
1040
1119
|
doneLoader();
|
|
1041
1120
|
});
|
|
1042
1121
|
|
|
1043
|
-
// Memoize for subsequent calls
|
|
1044
1122
|
loaderPromises.set(loader.$$id, promise);
|
|
1045
1123
|
|
|
1046
1124
|
return promise;
|
package/src/ssr/index.tsx
CHANGED
|
@@ -71,7 +71,7 @@ export interface SSRRenderOptions {
|
|
|
71
71
|
*/
|
|
72
72
|
export interface SSRDependencies<TEnv = unknown> {
|
|
73
73
|
/**
|
|
74
|
-
* createFromReadableStream from @
|
|
74
|
+
* createFromReadableStream from @rangojs/router/internal/deps/ssr
|
|
75
75
|
*/
|
|
76
76
|
createFromReadableStream: <T>(
|
|
77
77
|
stream: ReadableStream<Uint8Array>,
|
|
@@ -86,7 +86,7 @@ export interface SSRDependencies<TEnv = unknown> {
|
|
|
86
86
|
) => Promise<ReactDOMReadableStream>;
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
|
-
* injectRSCPayload from
|
|
89
|
+
* injectRSCPayload from @rangojs/router/internal/deps/html-stream-server
|
|
90
90
|
*/
|
|
91
91
|
injectRSCPayload: (
|
|
92
92
|
rscStream: ReadableStream<Uint8Array>,
|
|
@@ -218,10 +218,10 @@ function createSsrEventController(opts: {
|
|
|
218
218
|
*
|
|
219
219
|
* @example
|
|
220
220
|
* ```tsx
|
|
221
|
-
* import { createSSRHandler } from "
|
|
222
|
-
* import { createFromReadableStream } from "@
|
|
221
|
+
* import { createSSRHandler } from "@rangojs/router/ssr";
|
|
222
|
+
* import { createFromReadableStream } from "@rangojs/router/internal/deps/ssr";
|
|
223
223
|
* import { renderToReadableStream } from "react-dom/server.edge";
|
|
224
|
-
* import { injectRSCPayload } from "
|
|
224
|
+
* import { injectRSCPayload } from "@rangojs/router/internal/deps/html-stream-server";
|
|
225
225
|
*
|
|
226
226
|
* export const renderHTML = createSSRHandler({
|
|
227
227
|
* createFromReadableStream,
|
|
@@ -263,6 +263,7 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
263
263
|
let payload: Promise<RscPayload> | undefined;
|
|
264
264
|
let handlesPromise: Promise<HandleData> | undefined;
|
|
265
265
|
let ssrContextValue: NavigationStoreContextValue | undefined;
|
|
266
|
+
let rootPromise: Promise<React.ReactNode> | undefined;
|
|
266
267
|
function SsrRoot() {
|
|
267
268
|
payload ??= createFromReadableStream<RscPayload>(rscStream1);
|
|
268
269
|
const resolved = React.use(payload);
|
|
@@ -296,17 +297,16 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
296
297
|
};
|
|
297
298
|
|
|
298
299
|
// Build content tree from segments.
|
|
299
|
-
// Order must match NavigationProvider: NavigationStoreContext > ThemeProvider > content
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
300
|
+
// Order must match NavigationProvider: NavigationStoreContext > NonceContext > ThemeProvider > content
|
|
301
|
+
// Memoize like payload/handles above: renderSegments is async, so
|
|
302
|
+
// React.use() on a fresh promise suspends and replays SsrRoot, which
|
|
303
|
+
// would re-run the entire segment-tree build on every initial render.
|
|
304
|
+
rootPromise ??= Promise.resolve(
|
|
305
|
+
renderSegments(resolved.metadata?.segments ?? [], {
|
|
303
306
|
rootLayout: resolved.metadata?.rootLayout,
|
|
304
|
-
},
|
|
307
|
+
}),
|
|
305
308
|
);
|
|
306
|
-
let content: React.ReactNode =
|
|
307
|
-
reconstructedRoot instanceof Promise
|
|
308
|
-
? React.use(reconstructedRoot)
|
|
309
|
-
: reconstructedRoot;
|
|
309
|
+
let content: React.ReactNode = React.use(rootPromise);
|
|
310
310
|
|
|
311
311
|
// Wrap content with ThemeProvider if theme is enabled
|
|
312
312
|
if (themeConfig) {
|