@rangojs/router 0.0.0-experimental.135c6902 → 0.0.0-experimental.136
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -0
- package/README.md +245 -49
- package/dist/bin/rango.js +440 -133
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3386 -1111
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +78 -25
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +64 -2
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +247 -23
- package/skills/caching/SKILL.md +281 -11
- package/skills/composability/SKILL.md +27 -3
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +4 -2
- 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 +83 -23
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +87 -18
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +289 -53
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +914 -0
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +144 -7
- package/skills/prerender/SKILL.md +172 -125
- package/skills/rango/SKILL.md +251 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +123 -48
- package/skills/route/SKILL.md +101 -5
- package/skills/router-setup/SKILL.md +117 -10
- package/skills/scripts/SKILL.md +179 -0
- 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 +130 -0
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -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 +122 -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 +123 -0
- package/skills/typesafety/SKILL.md +332 -29
- package/skills/use-cache/SKILL.md +57 -14
- package/skills/view-transitions/SKILL.md +337 -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/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +192 -150
- 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 +131 -30
- package/src/browser/navigation-client.ts +199 -86
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +157 -74
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +174 -109
- package/src/browser/prefetch/cache.ts +222 -69
- package/src/browser/prefetch/fetch.ts +347 -39
- package/src/browser/prefetch/queue.ts +113 -32
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +102 -15
- package/src/browser/react/NavigationProvider.tsx +300 -122
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/deferred-handle-resolution.ts +75 -0
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -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-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +32 -7
- 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 +99 -8
- package/src/browser/rsc-router.tsx +125 -28
- package/src/browser/scroll-restoration.ts +37 -22
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +222 -61
- package/src/browser/types.ts +115 -12
- 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 +5 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +165 -36
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +125 -24
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +260 -94
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +29 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +239 -52
- package/src/cache/cache-scope.ts +234 -87
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +1989 -378
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +89 -21
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +10 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +44 -21
- package/src/client.tsx +119 -290
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +84 -2
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +62 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +37 -8
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +51 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +23 -52
- 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 +99 -13
- package/src/index.ts +139 -19
- package/src/internal-debug.ts +11 -10
- 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/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +461 -304
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +149 -74
- package/src/route-definition/index.ts +4 -2
- package/src/route-definition/redirect.ts +51 -10
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +37 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +164 -17
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +83 -39
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +48 -24
- package/src/router/lazy-includes.ts +15 -52
- package/src/router/loader-resolution.ts +274 -56
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +40 -42
- package/src/router/match-api.ts +124 -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 +54 -25
- package/src/router/match-middleware/cache-lookup.ts +205 -271
- package/src/router/match-middleware/cache-store.ts +81 -50
- 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 +93 -39
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +268 -171
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +132 -90
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +195 -56
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +3 -27
- package/src/router/router-interfaces.ts +129 -35
- package/src/router/router-options.ts +202 -15
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +197 -92
- package/src/router/segment-resolution/helpers.ts +115 -30
- package/src/router/segment-resolution/loader-cache.ts +76 -39
- package/src/router/segment-resolution/revalidation.ts +392 -338
- 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 +56 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +161 -199
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +163 -59
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +174 -54
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +660 -518
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +127 -31
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +133 -13
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +99 -189
- package/src/rsc/rsc-rendering.ts +115 -73
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +287 -113
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +36 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +269 -202
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +235 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +40 -38
- package/src/server/loader-registry.ts +38 -46
- package/src/server/request-context.ts +442 -173
- package/src/ssr/index.tsx +24 -16
- 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 +701 -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 +257 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -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 +357 -0
- package/src/testing/render-route.tsx +581 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -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 +20 -58
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- 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 +234 -82
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +12 -7
- package/src/types/segments.ts +136 -15
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +68 -18
- 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 -19
- 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 +214 -132
- 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 +57 -4
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +6 -0
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +178 -5
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- 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 +96 -51
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +553 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +37 -40
- package/src/vite/plugins/virtual-entries.ts +39 -25
- package/src/vite/rango.ts +119 -111
- package/src/vite/router-discovery.ts +941 -142
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- 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/directive-prologue.ts +40 -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 +81 -34
- package/src/vite/utils/shared-utils.ts +92 -42
- 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/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
package/src/server/context.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
ShouldRevalidateFn,
|
|
11
11
|
TransitionConfig,
|
|
12
12
|
} from "../types";
|
|
13
|
-
import { invariant } from "../errors";
|
|
13
|
+
import { invariant, DslContextError } from "../errors";
|
|
14
14
|
import type { DefaultRouteName } from "../types/global-namespace.js";
|
|
15
15
|
|
|
16
16
|
// ============================================================================
|
|
@@ -40,7 +40,7 @@ export interface MetricsStore {
|
|
|
40
40
|
metrics: PerformanceMetric[];
|
|
41
41
|
}
|
|
42
42
|
// ============================================================================
|
|
43
|
-
//
|
|
43
|
+
// Rango Context
|
|
44
44
|
// ============================================================================
|
|
45
45
|
|
|
46
46
|
/**
|
|
@@ -71,6 +71,10 @@ export type EntryPropCommon = {
|
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
+
* Attachments resolved by walking the parent chain, not owned by the entry:
|
|
75
|
+
* middleware composes downward; revalidate and the error/notFound boundaries are
|
|
76
|
+
* resolved by nearest-ancestor lookup. Inherited, not a single execution chain.
|
|
77
|
+
*
|
|
74
78
|
* @internal This type is an implementation detail and may change without notice.
|
|
75
79
|
*/
|
|
76
80
|
export type EntryPropDatas = {
|
|
@@ -80,6 +84,16 @@ export type EntryPropDatas = {
|
|
|
80
84
|
notFoundBoundary: (ReactNode | NotFoundBoundaryHandler)[];
|
|
81
85
|
};
|
|
82
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Render-time presentation fields shared by every entry variant.
|
|
89
|
+
*
|
|
90
|
+
* @internal This type is an implementation detail and may change without notice.
|
|
91
|
+
*/
|
|
92
|
+
export type EntryPropRender = {
|
|
93
|
+
loading?: ReactNode | false;
|
|
94
|
+
transition?: TransitionConfig;
|
|
95
|
+
};
|
|
96
|
+
|
|
83
97
|
/**
|
|
84
98
|
* Loader entry stored in EntryData
|
|
85
99
|
* Contains the loader definition and its revalidation rules
|
|
@@ -136,6 +150,19 @@ export type InterceptWhenFn<TEnv = any> = (
|
|
|
136
150
|
ctx: InterceptSelectorContext<TEnv>,
|
|
137
151
|
) => boolean;
|
|
138
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Config object passed to intercept() (its 4th argument). `when` gates whether
|
|
155
|
+
* the intercept activates on a soft navigation — a single match-time selector or
|
|
156
|
+
* an array of them (ALL must return true; omit to always activate). This is the
|
|
157
|
+
* intercept counterpart to transition({ when }); both express conditional
|
|
158
|
+
* behavior as a config field rather than a separate DSL helper.
|
|
159
|
+
*
|
|
160
|
+
* @internal This type is an implementation detail and may change without notice.
|
|
161
|
+
*/
|
|
162
|
+
export interface InterceptConfig<TEnv = any> {
|
|
163
|
+
when?: InterceptWhenFn<TEnv> | InterceptWhenFn<TEnv>[];
|
|
164
|
+
}
|
|
165
|
+
|
|
139
166
|
/**
|
|
140
167
|
* Intercept entry stored in EntryData
|
|
141
168
|
* Contains the slot name, route to intercept, and handler
|
|
@@ -158,11 +185,9 @@ export type InterceptEntry = {
|
|
|
158
185
|
};
|
|
159
186
|
|
|
160
187
|
export interface ParallelEntryData
|
|
161
|
-
extends EntryPropCommon, EntryPropDatas, EntryPropSegments {
|
|
188
|
+
extends EntryPropCommon, EntryPropDatas, EntryPropSegments, EntryPropRender {
|
|
162
189
|
type: "parallel";
|
|
163
190
|
handler: Record<`@${string}`, Handler<any, any, any> | ReactNode>;
|
|
164
|
-
loading?: ReactNode | false;
|
|
165
|
-
transition?: TransitionConfig;
|
|
166
191
|
/** Set when any parallel slot is a Static definition */
|
|
167
192
|
isStaticPrerender?: true;
|
|
168
193
|
/** Per-slot static handler $$ids for build-time store lookup */
|
|
@@ -171,6 +196,13 @@ export interface ParallelEntryData
|
|
|
171
196
|
|
|
172
197
|
export type ParallelEntries = Partial<Record<`@${string}`, ParallelEntryData>>;
|
|
173
198
|
|
|
199
|
+
/**
|
|
200
|
+
* This entry's own structural children plus its owned loaders. `loader` lives
|
|
201
|
+
* here (not in EntryPropDatas) because loaders are owned by the entry, not
|
|
202
|
+
* inherited from ancestors.
|
|
203
|
+
*
|
|
204
|
+
* @internal This type is an implementation detail and may change without notice.
|
|
205
|
+
*/
|
|
174
206
|
export type EntryPropSegments = {
|
|
175
207
|
loader: LoaderEntry[];
|
|
176
208
|
layout: EntryData[];
|
|
@@ -182,8 +214,6 @@ export type EntryData =
|
|
|
182
214
|
| ({
|
|
183
215
|
type: "route";
|
|
184
216
|
handler: Handler<any, any, any>;
|
|
185
|
-
loading?: ReactNode | false;
|
|
186
|
-
transition?: TransitionConfig;
|
|
187
217
|
/** URL pattern for this route (used by path() in urls()) */
|
|
188
218
|
pattern?: string;
|
|
189
219
|
/** Set when handler is a Prerender definition */
|
|
@@ -191,8 +221,12 @@ export type EntryData =
|
|
|
191
221
|
/** Original PrerenderHandlerDefinition (for build-time getParams access) */
|
|
192
222
|
prerenderDef?: {
|
|
193
223
|
getParams?: (ctx: any) => Promise<any[]> | any[];
|
|
194
|
-
options?: {
|
|
224
|
+
options?: { concurrency?: number };
|
|
195
225
|
};
|
|
226
|
+
/** Set when route is wrapped with Passthrough() — has a separate live handler */
|
|
227
|
+
isPassthrough?: true;
|
|
228
|
+
/** Live handler for runtime fallback (only set on Passthrough routes) */
|
|
229
|
+
liveHandler?: Handler<any, any, any>;
|
|
196
230
|
/** Set when handler is a Static definition (build-time only) */
|
|
197
231
|
isStaticPrerender?: true;
|
|
198
232
|
/** Static handler $$id for build-time store lookup */
|
|
@@ -201,29 +235,28 @@ export type EntryData =
|
|
|
201
235
|
responseType?: string;
|
|
202
236
|
} & EntryPropCommon &
|
|
203
237
|
EntryPropDatas &
|
|
204
|
-
EntryPropSegments
|
|
238
|
+
EntryPropSegments &
|
|
239
|
+
EntryPropRender)
|
|
205
240
|
| ({
|
|
206
241
|
type: "layout";
|
|
207
242
|
handler: ReactNode | Handler<any, any, any>;
|
|
208
|
-
loading?: ReactNode | false;
|
|
209
|
-
transition?: TransitionConfig;
|
|
210
243
|
/** Set when handler is a Static definition (build-time only) */
|
|
211
244
|
isStaticPrerender?: true;
|
|
212
245
|
/** Static handler $$id for build-time store lookup */
|
|
213
246
|
staticHandlerId?: string;
|
|
214
247
|
} & EntryPropCommon &
|
|
215
248
|
EntryPropDatas &
|
|
216
|
-
EntryPropSegments
|
|
249
|
+
EntryPropSegments &
|
|
250
|
+
EntryPropRender)
|
|
217
251
|
| ParallelEntryData
|
|
218
252
|
| ({
|
|
219
253
|
type: "cache";
|
|
220
254
|
/** Cache entries create cache boundaries and render like layouts (with Outlet) */
|
|
221
255
|
handler: ReactNode | Handler<any, any, any>;
|
|
222
|
-
loading?: ReactNode | false;
|
|
223
|
-
transition?: TransitionConfig;
|
|
224
256
|
} & EntryPropCommon &
|
|
225
257
|
EntryPropDatas &
|
|
226
|
-
EntryPropSegments
|
|
258
|
+
EntryPropSegments &
|
|
259
|
+
EntryPropRender);
|
|
227
260
|
|
|
228
261
|
/**
|
|
229
262
|
* Tracked include info for build-time manifest generation
|
|
@@ -273,6 +306,25 @@ interface HelperContext {
|
|
|
273
306
|
string,
|
|
274
307
|
import("../cache/profile-registry.js").CacheProfile
|
|
275
308
|
>;
|
|
309
|
+
/** True when resolving handlers inside a cache() DSL boundary.
|
|
310
|
+
* Read by ctx.get() to guard non-cacheable variable reads. */
|
|
311
|
+
insideCacheScope?: boolean;
|
|
312
|
+
/**
|
|
313
|
+
* Include scope string applied to direct-descendant shortCodes.
|
|
314
|
+
*
|
|
315
|
+
* Each `include(...)` call allocates a sibling-positional token like `I0`,
|
|
316
|
+
* `I1` from its parent's include counter and stores the composed scope
|
|
317
|
+
* (`${parentScope}I${idx}`) in its lazyContext. When the include's handler
|
|
318
|
+
* evaluates lazily, the store's `includeScope` is set from that context so
|
|
319
|
+
* every direct-descendant shortCode is generated as
|
|
320
|
+
* `${parent.shortCode}${includeScope}${prefix}${index}` — preventing
|
|
321
|
+
* collisions with siblings declared outside the include.
|
|
322
|
+
*
|
|
323
|
+
* The scope is NOT propagated through `store.run(...)`, so layouts /
|
|
324
|
+
* parallels / caches inside the include absorb the scope into their own
|
|
325
|
+
* shortCodes and their children start fresh.
|
|
326
|
+
*/
|
|
327
|
+
includeScope?: string;
|
|
276
328
|
}
|
|
277
329
|
// Use a global symbol key so the AsyncLocalStorage instance survives HMR
|
|
278
330
|
// module re-evaluation. Without this, Vite's RSC module runner may create
|
|
@@ -280,10 +332,28 @@ interface HelperContext {
|
|
|
280
332
|
// hold references to the old instance — causing getStore() to return
|
|
281
333
|
// undefined even inside a run() callback.
|
|
282
334
|
const RSC_CONTEXT_KEY = Symbol.for("rangojs-router:rsc-context");
|
|
283
|
-
export const
|
|
335
|
+
export const RangoContext: AsyncLocalStorage<HelperContext> = ((
|
|
284
336
|
globalThis as any
|
|
285
337
|
)[RSC_CONTEXT_KEY] ??= new AsyncLocalStorage<HelperContext>());
|
|
286
338
|
|
|
339
|
+
/** shortCode prefix letter per entry type (e.g. "L0", "R2", "M1C0"). */
|
|
340
|
+
const SHORT_CODE_PREFIX: Record<
|
|
341
|
+
"layout" | "parallel" | "route" | "loader" | "cache",
|
|
342
|
+
string
|
|
343
|
+
> = {
|
|
344
|
+
layout: "L",
|
|
345
|
+
parallel: "P",
|
|
346
|
+
route: "R",
|
|
347
|
+
loader: "D",
|
|
348
|
+
cache: "C",
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
/** Post-increment a named per-store counter, returning the prior value. */
|
|
352
|
+
function bumpCounter(store: HelperContext, key: string): number {
|
|
353
|
+
store.counters[key] ??= 0;
|
|
354
|
+
return store.counters[key]++;
|
|
355
|
+
}
|
|
356
|
+
|
|
287
357
|
export const getContext = (): {
|
|
288
358
|
context: AsyncLocalStorage<HelperContext>;
|
|
289
359
|
getStore: () => HelperContext;
|
|
@@ -307,12 +377,12 @@ export const getContext = (): {
|
|
|
307
377
|
callback: (...args: any[]) => T,
|
|
308
378
|
) => T;
|
|
309
379
|
} => {
|
|
310
|
-
const context =
|
|
380
|
+
const context = RangoContext;
|
|
311
381
|
|
|
312
382
|
return {
|
|
313
383
|
context,
|
|
314
384
|
getOrCreateStore: (forRoute?: string): HelperContext => {
|
|
315
|
-
let store =
|
|
385
|
+
let store = RangoContext.getStore();
|
|
316
386
|
if (!store) {
|
|
317
387
|
store = {
|
|
318
388
|
manifest: new Map<string, EntryData>(),
|
|
@@ -332,7 +402,7 @@ export const getContext = (): {
|
|
|
332
402
|
const store = context.getStore();
|
|
333
403
|
if (!store) {
|
|
334
404
|
throw new Error(
|
|
335
|
-
"
|
|
405
|
+
"Rango context store is not available. Make sure to run within Rango context.",
|
|
336
406
|
);
|
|
337
407
|
}
|
|
338
408
|
return store;
|
|
@@ -349,48 +419,36 @@ export const getContext = (): {
|
|
|
349
419
|
type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate",
|
|
350
420
|
) => {
|
|
351
421
|
const store = context.getStore();
|
|
352
|
-
invariant(store, "No context
|
|
353
|
-
store
|
|
354
|
-
const index = store.counters[type];
|
|
355
|
-
store.counters[type] = index + 1;
|
|
356
|
-
return `$${type}.${index}`;
|
|
422
|
+
invariant(store, "No context RangoContext available");
|
|
423
|
+
return `$${type}.${bumpCounter(store, type)}`;
|
|
357
424
|
},
|
|
358
425
|
getShortCode: (
|
|
359
426
|
type: "layout" | "parallel" | "route" | "loader" | "cache",
|
|
360
427
|
) => {
|
|
361
428
|
const store = context.getStore();
|
|
362
|
-
invariant(store, "No context
|
|
429
|
+
invariant(store, "No context RangoContext available");
|
|
363
430
|
|
|
364
431
|
const parent = store.parent;
|
|
365
|
-
const prefix =
|
|
366
|
-
type === "layout"
|
|
367
|
-
? "L"
|
|
368
|
-
: type === "parallel"
|
|
369
|
-
? "P"
|
|
370
|
-
: type === "loader"
|
|
371
|
-
? "D"
|
|
372
|
-
: type === "cache"
|
|
373
|
-
? "C"
|
|
374
|
-
: "R";
|
|
432
|
+
const prefix = SHORT_CODE_PREFIX[type];
|
|
375
433
|
const mountPrefix =
|
|
376
434
|
store.mountIndex !== undefined ? `M${store.mountIndex}` : "";
|
|
377
435
|
|
|
436
|
+
const includeScope = store.includeScope ?? "";
|
|
437
|
+
|
|
378
438
|
if (!parent) {
|
|
379
439
|
// Root entry: prefix with mount index and use mount-scoped counter
|
|
380
440
|
const counterKey = mountPrefix
|
|
381
441
|
? `${mountPrefix}_root_${type}`
|
|
382
442
|
: `root_${type}`;
|
|
383
|
-
store
|
|
384
|
-
const index = store.counters[counterKey];
|
|
385
|
-
store.counters[counterKey] = index + 1;
|
|
386
|
-
return `${mountPrefix}${prefix}${index}`;
|
|
443
|
+
return `${mountPrefix}${prefix}${bumpCounter(store, counterKey)}`;
|
|
387
444
|
} else {
|
|
388
|
-
// Child entry: use parent-scoped counter
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
445
|
+
// Child entry: use parent-scoped counter with includeScope appended.
|
|
446
|
+
// When we're evaluating a lazy include's direct children, includeScope
|
|
447
|
+
// is a per-include token like "I0" / "I1I0" that partitions the
|
|
448
|
+
// parent's counter namespace so routes inside one include cannot
|
|
449
|
+
// collide with siblings declared outside it.
|
|
450
|
+
const counterKey = `${parent.shortCode}${includeScope}_${type}`;
|
|
451
|
+
return `${parent.shortCode}${includeScope}${prefix}${bumpCounter(store, counterKey)}`;
|
|
394
452
|
}
|
|
395
453
|
},
|
|
396
454
|
runWithStore: <T>(
|
|
@@ -417,6 +475,7 @@ export const getContext = (): {
|
|
|
417
475
|
rootScoped: store.rootScoped,
|
|
418
476
|
trackedIncludes: store.trackedIncludes,
|
|
419
477
|
cacheProfiles: store.cacheProfiles,
|
|
478
|
+
includeScope: store.includeScope,
|
|
420
479
|
},
|
|
421
480
|
callback,
|
|
422
481
|
);
|
|
@@ -463,6 +522,31 @@ export const getContext = (): {
|
|
|
463
522
|
};
|
|
464
523
|
};
|
|
465
524
|
|
|
525
|
+
/**
|
|
526
|
+
* Acquire the active DSL build context, throwing `message` if a helper was
|
|
527
|
+
* called outside a urls()/map() builder. Returns the store API and the live
|
|
528
|
+
* HelperContext so callers avoid a second getContext() lookup.
|
|
529
|
+
*/
|
|
530
|
+
export function requireDslContext(message: string): {
|
|
531
|
+
store: ReturnType<typeof getContext>;
|
|
532
|
+
ctx: HelperContext;
|
|
533
|
+
} {
|
|
534
|
+
const store = getContext();
|
|
535
|
+
const ctx = store.context.getStore();
|
|
536
|
+
if (!ctx) {
|
|
537
|
+
// The only reason the store is absent here is that a route-definition helper
|
|
538
|
+
// ran with no active RangoContext — i.e. outside a urls()/map() builder.
|
|
539
|
+
// Record that as the cause so the throw is self-explanatory, not a bare
|
|
540
|
+
// "must be called inside urls()" with no indication of the mechanism.
|
|
541
|
+
throw new DslContextError(message, {
|
|
542
|
+
cause:
|
|
543
|
+
"RangoContext store is undefined: a route-definition helper was called " +
|
|
544
|
+
"outside an active urls()/map() builder.",
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
return { store, ctx };
|
|
548
|
+
}
|
|
549
|
+
|
|
466
550
|
/**
|
|
467
551
|
* Run a callback with specific URL and name prefixes
|
|
468
552
|
* Used by include() to apply prefixes to nested patterns
|
|
@@ -472,7 +556,7 @@ export function runWithPrefixes<T>(
|
|
|
472
556
|
namePrefix: string | undefined,
|
|
473
557
|
callback: () => T,
|
|
474
558
|
): T {
|
|
475
|
-
const store =
|
|
559
|
+
const store = RangoContext.getStore();
|
|
476
560
|
if (!store) {
|
|
477
561
|
throw new Error("runWithPrefixes must be called within router context");
|
|
478
562
|
}
|
|
@@ -517,7 +601,7 @@ export function runWithPrefixes<T>(
|
|
|
517
601
|
? (store.rootScoped ?? false)
|
|
518
602
|
: store.rootScoped;
|
|
519
603
|
|
|
520
|
-
return
|
|
604
|
+
return RangoContext.run(
|
|
521
605
|
{
|
|
522
606
|
...store,
|
|
523
607
|
urlPrefix: combinedUrlPrefix,
|
|
@@ -532,7 +616,7 @@ export function runWithPrefixes<T>(
|
|
|
532
616
|
* Get current URL prefix from context
|
|
533
617
|
*/
|
|
534
618
|
export function getUrlPrefix(): string {
|
|
535
|
-
const store =
|
|
619
|
+
const store = RangoContext.getStore();
|
|
536
620
|
return store?.urlPrefix || "";
|
|
537
621
|
}
|
|
538
622
|
|
|
@@ -540,7 +624,7 @@ export function getUrlPrefix(): string {
|
|
|
540
624
|
* Get current name prefix from context
|
|
541
625
|
*/
|
|
542
626
|
export function getNamePrefix(): string | undefined {
|
|
543
|
-
const store =
|
|
627
|
+
const store = RangoContext.getStore();
|
|
544
628
|
return store?.namePrefix;
|
|
545
629
|
}
|
|
546
630
|
|
|
@@ -549,7 +633,7 @@ export function getNamePrefix(): string | undefined {
|
|
|
549
633
|
* Returns true at root or inside { name: "" } includes, false inside named includes.
|
|
550
634
|
*/
|
|
551
635
|
export function getRootScoped(): boolean {
|
|
552
|
-
const store =
|
|
636
|
+
const store = RangoContext.getStore();
|
|
553
637
|
return store?.rootScoped ?? true;
|
|
554
638
|
}
|
|
555
639
|
|
|
@@ -646,7 +730,7 @@ export function getParallelSlotCount(
|
|
|
646
730
|
* ```
|
|
647
731
|
*/
|
|
648
732
|
export function track(label: string, depth?: number): () => void {
|
|
649
|
-
const store =
|
|
733
|
+
const store = RangoContext.getStore();
|
|
650
734
|
|
|
651
735
|
// No-op if context unavailable or metrics not enabled
|
|
652
736
|
if (!store?.metrics?.enabled) {
|
|
@@ -666,3 +750,103 @@ export function track(label: string, depth?: number): () => void {
|
|
|
666
750
|
});
|
|
667
751
|
};
|
|
668
752
|
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Separate ALS for tracking loader execution scope.
|
|
756
|
+
* Uses a dedicated ALS (not RangoContext) to avoid issues with
|
|
757
|
+
* nested RangoContext.run() calls in Vite's module runner.
|
|
758
|
+
*/
|
|
759
|
+
const LOADER_SCOPE_KEY = Symbol.for("rangojs-router:loader-scope");
|
|
760
|
+
const loaderScopeALS: AsyncLocalStorage<{ active: true }> = ((
|
|
761
|
+
globalThis as any
|
|
762
|
+
)[LOADER_SCOPE_KEY] ??= new AsyncLocalStorage<{ active: true }>());
|
|
763
|
+
|
|
764
|
+
// Purity-only scope: marks that a loader FUNCTION BODY is executing, regardless
|
|
765
|
+
// of how the loader was invoked (DSL via runInsideLoaderScope, or handler-
|
|
766
|
+
// invoked via ctx.use). Consulted ONLY by isInsideCacheScope() to exempt
|
|
767
|
+
// request-scoped reads. It deliberately does NOT affect isInsideLoaderScope(),
|
|
768
|
+
// so rendered()/barrier/deadlock gating (which must distinguish DSL from
|
|
769
|
+
// handler-invoked loaders) is unchanged.
|
|
770
|
+
const LOADER_BODY_SCOPE_KEY = Symbol.for("rangojs-router:loader-body-scope");
|
|
771
|
+
const loaderBodyScopeALS: AsyncLocalStorage<{ active: true }> = ((
|
|
772
|
+
globalThis as any
|
|
773
|
+
)[LOADER_BODY_SCOPE_KEY] ??= new AsyncLocalStorage<{ active: true }>());
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Check if the current execution is inside a cache() DSL boundary.
|
|
777
|
+
* Returns false inside loader execution — loaders are always fresh
|
|
778
|
+
* (never cached), so non-cacheable reads are safe.
|
|
779
|
+
*/
|
|
780
|
+
export function isInsideCacheScope(): boolean {
|
|
781
|
+
if (RangoContext.getStore()?.insideCacheScope !== true) return false;
|
|
782
|
+
// Loaders are always fresh — even inside a cache() boundary, the loader
|
|
783
|
+
// function re-executes on every request. Skip the guard when running
|
|
784
|
+
// inside a loader.
|
|
785
|
+
if (loaderScopeALS.getStore()?.active) return false;
|
|
786
|
+
// Also exempt handler-invoked loaders: their bodies run in a loader-body
|
|
787
|
+
// scope (not the DSL loader scope above), so request-scoped reads inside any
|
|
788
|
+
// loader — however invoked — are safe (loaders always re-run fresh).
|
|
789
|
+
if (loaderBodyScopeALS.getStore()?.active) return false;
|
|
790
|
+
return true;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Check if the current execution is inside a DSL loader scope
|
|
795
|
+
* (wrapped by runInsideLoaderScope). Used by rendered() barrier
|
|
796
|
+
* to distinguish DSL loaders from handler-invoked loaders.
|
|
797
|
+
*/
|
|
798
|
+
export function isInsideLoaderScope(): boolean {
|
|
799
|
+
return loaderScopeALS.getStore()?.active === true;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Run `fn` inside a loader scope. While active, cache-scope guards
|
|
804
|
+
* are bypassed because loaders are always fresh (never cached) and
|
|
805
|
+
* their side effects (setCookie, header, etc.) are safe.
|
|
806
|
+
*/
|
|
807
|
+
export function runInsideLoaderScope<T>(fn: () => T): T {
|
|
808
|
+
return loaderScopeALS.run({ active: true }, fn);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Run `fn` inside a loader BODY scope. Marks loader-function execution for the
|
|
813
|
+
* cache-purity guard only (isInsideCacheScope), WITHOUT affecting
|
|
814
|
+
* isInsideLoaderScope()/rendered() gating. Applied to every loader body (DSL
|
|
815
|
+
* and handler-invoked via ctx.use) so request-scoped reads inside a loader
|
|
816
|
+
* never trip the cache-scope guards — loaders always run fresh.
|
|
817
|
+
*/
|
|
818
|
+
export function runInsideLoaderBodyScope<T>(fn: () => T): T {
|
|
819
|
+
return loaderBodyScopeALS.run({ active: true }, fn);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Scope for handle PUSH CALLBACKS (push(() => ...), including async ones).
|
|
823
|
+
// A push callback's value is stored as-is; if it is a promise it is NOT tracked
|
|
824
|
+
// by handleStore.settled and does not block segment resolution, so a
|
|
825
|
+
// ctx.use(loader) made from inside such a callback can never form a rendered()
|
|
826
|
+
// deadlock. This is an ALS (not a plain boolean) so the exemption survives the
|
|
827
|
+
// callback's own awaits — an async push callback that resumes after `await`
|
|
828
|
+
// still reads as "inside a push callback" and stays out of the deadlock guard.
|
|
829
|
+
const PUSH_CALLBACK_SCOPE_KEY = Symbol.for(
|
|
830
|
+
"rangojs-router:push-callback-scope",
|
|
831
|
+
);
|
|
832
|
+
const pushCallbackScopeALS: AsyncLocalStorage<{ active: true }> = ((
|
|
833
|
+
globalThis as any
|
|
834
|
+
)[PUSH_CALLBACK_SCOPE_KEY] ??= new AsyncLocalStorage<{ active: true }>());
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Check if the current execution is inside a handle push callback (sync or an
|
|
838
|
+
* async callback's continuation). Used by the handler-to-loader deadlock guard
|
|
839
|
+
* to exempt push-callback continuations.
|
|
840
|
+
*/
|
|
841
|
+
export function isInsidePushCallbackScope(): boolean {
|
|
842
|
+
return pushCallbackScopeALS.getStore()?.active === true;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Run `fn` inside a push-callback scope. Wraps the invocation of a handle push
|
|
847
|
+
* callback so that any ctx.use(loader) it makes — including after one of its own
|
|
848
|
+
* awaits — is exempt from the deadlock guard.
|
|
849
|
+
*/
|
|
850
|
+
export function runInsidePushCallbackScope<T>(fn: () => T): T {
|
|
851
|
+
return pushCallbackScopeALS.run({ active: true }, fn);
|
|
852
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical inbound-Cookie-header parser.
|
|
3
|
+
*
|
|
4
|
+
* Kept as a dependency-free leaf so any consumer (request-context, the host
|
|
5
|
+
* dispatcher, tests) can share one implementation without pulling a heavier
|
|
6
|
+
* module's graph. A duplicate copy in middleware-cookies.ts was removed; the
|
|
7
|
+
* host copy in cookie-handler.ts was collapsed onto this one. Not part of the
|
|
8
|
+
* public export surface.
|
|
9
|
+
*/
|
|
10
|
+
export function parseCookiesFromHeader(
|
|
11
|
+
cookieHeader: string | null,
|
|
12
|
+
): Record<string, string> {
|
|
13
|
+
if (!cookieHeader) return {};
|
|
14
|
+
|
|
15
|
+
const cookies: Record<string, string> = {};
|
|
16
|
+
const pairs = cookieHeader.split(";");
|
|
17
|
+
|
|
18
|
+
for (const pair of pairs) {
|
|
19
|
+
const [name, ...rest] = pair.trim().split("=");
|
|
20
|
+
if (name) {
|
|
21
|
+
const raw = rest.join("=");
|
|
22
|
+
try {
|
|
23
|
+
cookies[name] = decodeURIComponent(raw);
|
|
24
|
+
} catch {
|
|
25
|
+
// Malformed percent-encoding: fall back to raw value
|
|
26
|
+
cookies[name] = raw;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return cookies;
|
|
32
|
+
}
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { CookieOptions } from "../router/middleware-types.js";
|
|
11
|
-
import { getRequestContext } from "./request-context.js";
|
|
11
|
+
import { getRequestContext, _getRequestContext } from "./request-context.js";
|
|
12
|
+
import { isInsideCacheScope } from "./context.js";
|
|
12
13
|
import { INSIDE_CACHE_EXEC } from "../cache/taint.js";
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -84,10 +85,23 @@ export interface ReadonlyHeaders {
|
|
|
84
85
|
type HeadersIterator<T> = IterableIterator<T>;
|
|
85
86
|
|
|
86
87
|
/**
|
|
87
|
-
* Throw if called inside a "use cache" function
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
88
|
+
* Throw if called inside a cache boundary — either a "use cache" function
|
|
89
|
+
* (`INSIDE_CACHE_EXEC` stamped on ctx by the cache runtime) or a `cache()`
|
|
90
|
+
* DSL boundary (`isInsideCacheScope()` — the render-store flag set while
|
|
91
|
+
* resolving a `type: "cache"` route entry).
|
|
92
|
+
*
|
|
93
|
+
* Reading request-scoped data (cookies, headers) inside a cached scope
|
|
94
|
+
* produces per-request values that are NOT reflected in the cache key, so
|
|
95
|
+
* they would be frozen into the shared cache entry and served to the wrong
|
|
96
|
+
* users. This is the same hazard for both scopes: a `cache()` boundary caches
|
|
97
|
+
* everything except loaders (it is the document-level "PPR shell"), so a read
|
|
98
|
+
* here is baked into the shell exactly like a `"use cache"` return value is
|
|
99
|
+
* baked into its cache entry.
|
|
100
|
+
*
|
|
101
|
+
* `isInsideCacheScope()` returns false inside loaders (loaders always run
|
|
102
|
+
* fresh on every request, even on a cache hit), so reading cookies()/headers()
|
|
103
|
+
* from a loader is allowed — loaders are the dynamic "holes" of a cached
|
|
104
|
+
* document.
|
|
91
105
|
*/
|
|
92
106
|
function assertNotInsideCacheContext(ctx: unknown, fnName: string): void {
|
|
93
107
|
if (
|
|
@@ -106,6 +120,16 @@ function assertNotInsideCacheContext(ctx: unknown, fnName: string): void {
|
|
|
106
120
|
` const data = await getCachedData(locale); // locale is now in the cache key`,
|
|
107
121
|
);
|
|
108
122
|
}
|
|
123
|
+
if (isInsideCacheScope()) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`${fnName}() cannot be called inside a cache() boundary. ` +
|
|
126
|
+
`A cache() scope caches everything except loaders, so request-scoped ` +
|
|
127
|
+
`data (cookies, headers) read here would be frozen into the shared ` +
|
|
128
|
+
`cached shell and served to other users. Read it inside a loader ` +
|
|
129
|
+
`instead — loaders always run fresh on every request, even on a cache hit:\n\n` +
|
|
130
|
+
` loader("user", () => getUser(cookies().get("session")?.value));`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
109
133
|
}
|
|
110
134
|
|
|
111
135
|
const HEADERS_MUTATION_METHODS = new Set(["set", "append", "delete"]);
|
|
@@ -144,6 +168,57 @@ export function headers(): ReadonlyHeaders {
|
|
|
144
168
|
}) as unknown as ReadonlyHeaders;
|
|
145
169
|
}
|
|
146
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Force the calling client's caches to miss from now on, from the server seat:
|
|
173
|
+
* write a rotated `Set-Cookie` for the rango state. The responding client
|
|
174
|
+
* applies it on receipt, and its history cache is marked stale by the
|
|
175
|
+
* jar-divergence observer at its next read. Per-client and lazy — it rotates
|
|
176
|
+
* only the client that receives this response, not every client.
|
|
177
|
+
*
|
|
178
|
+
* Idempotent within a request (one `Set-Cookie`). Inert (a dev warning) when
|
|
179
|
+
* called outside a request context. Like `cookies()`, it throws inside a
|
|
180
|
+
* `"use cache"` / `cache()` boundary, but is allowed from a loader (loaders are
|
|
181
|
+
* the dynamic holes of a cached document).
|
|
182
|
+
*/
|
|
183
|
+
export function invalidateClientCache(): void {
|
|
184
|
+
const ctx = _getRequestContext();
|
|
185
|
+
if (!ctx) {
|
|
186
|
+
if (process.env.NODE_ENV !== "production") {
|
|
187
|
+
console.warn(
|
|
188
|
+
"[rango] invalidateClientCache() was called outside a request context; ignored.",
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
assertNotInsideCacheContext(ctx, "invalidateClientCache");
|
|
194
|
+
ctx._rotateStateCookie();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Suppress a server action's automatic client-cache invalidation: tell the
|
|
199
|
+
* action bridge this action changed nothing a route renders, so it should leave
|
|
200
|
+
* the client's state and caches alone (no rotation, no prefetch wipe, no
|
|
201
|
+
* broadcast, no revalidation refetch). Per-response, not per-action-definition —
|
|
202
|
+
* only the execution knows whether anything changed.
|
|
203
|
+
*
|
|
204
|
+
* Sets an internal response header the bridge reads. Idempotent within a
|
|
205
|
+
* request. Inert (a dev warning) outside a request context — there is no
|
|
206
|
+
* automatic invalidation to suppress.
|
|
207
|
+
*/
|
|
208
|
+
export function keepClientCache(): void {
|
|
209
|
+
const ctx = _getRequestContext();
|
|
210
|
+
if (!ctx) {
|
|
211
|
+
if (process.env.NODE_ENV !== "production") {
|
|
212
|
+
console.warn(
|
|
213
|
+
"[rango] keepClientCache() was called outside a request context; ignored.",
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
assertNotInsideCacheContext(ctx, "keepClientCache");
|
|
219
|
+
ctx._setKeepCacheDirective();
|
|
220
|
+
}
|
|
221
|
+
|
|
147
222
|
/**
|
|
148
223
|
* Create a CookieStore backed by a RequestContext.
|
|
149
224
|
* @internal Shared between cookies() shorthand and context methods.
|