@rangojs/router 0.0.0-experimental.7 → 0.0.0-experimental.8a4d0430
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 +5 -0
- package/README.md +884 -4
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +4474 -863
- package/package.json +60 -51
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +50 -21
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +334 -72
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +131 -8
- package/skills/layout/SKILL.md +100 -3
- package/skills/links/SKILL.md +89 -30
- package/skills/loader/SKILL.md +388 -38
- package/skills/middleware/SKILL.md +171 -34
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +78 -1
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +85 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +226 -14
- package/skills/router-setup/SKILL.md +123 -30
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +318 -89
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +87 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +285 -553
- package/src/browser/navigation-client.ts +124 -71
- package/src/browser/navigation-store.ts +33 -50
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +258 -308
- package/src/browser/prefetch/cache.ts +146 -0
- package/src/browser/prefetch/fetch.ts +135 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +185 -73
- package/src/browser/react/NavigationProvider.tsx +51 -11
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +32 -79
- package/src/browser/react/use-href.tsx +2 -2
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +22 -63
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +107 -26
- package/src/browser/scroll-restoration.ts +92 -16
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +504 -599
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +109 -47
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +13 -0
- package/src/build/route-trie.ts +265 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +411 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +469 -0
- package/src/build/route-types/scan-filter.ts +78 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +338 -0
- package/src/cache/cache-scope.ts +120 -303
- package/src/cache/cf/cf-cache-store.ts +119 -7
- package/src/cache/cf/index.ts +8 -2
- package/src/cache/document-cache.ts +101 -72
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +0 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +98 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +106 -126
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +86 -0
- package/src/debug.ts +17 -7
- package/src/errors.ts +108 -2
- package/src/handle.ts +15 -29
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +119 -29
- package/src/index.rsc.ts +153 -19
- package/src/index.ts +211 -30
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +26 -157
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -1428
- package/src/route-map-builder.ts +211 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +59 -8
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +158 -0
- package/src/router/handler-context.ts +374 -81
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +215 -122
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +148 -35
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +80 -93
- package/src/router/match-middleware/cache-lookup.ts +382 -9
- package/src/router/match-middleware/cache-store.ts +51 -22
- package/src/router/match-middleware/intercept-resolution.ts +55 -17
- package/src/router/match-middleware/segment-resolution.ts +24 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +34 -28
- package/src/router/metrics.ts +235 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +324 -367
- package/src/router/pattern-matching.ts +211 -43
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/router-context.ts +36 -21
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1241 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +289 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +77 -3
- package/src/router.ts +692 -4257
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +764 -754
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +235 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +38 -11
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +25 -13
- package/src/server/context.ts +182 -51
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +94 -15
- package/src/server/loader-registry.ts +15 -56
- package/src/server/request-context.ts +430 -70
- package/src/server.ts +35 -130
- package/src/ssr/index.tsx +100 -31
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +102 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -1623
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -802
- package/src/use-loader.tsx +85 -77
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +110 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +11 -1129
- package/src/vite/plugin-types.ts +131 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +254 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +510 -0
- package/src/vite/router-discovery.ts +785 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
- package/CLAUDE.md +0 -43
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/href.ts +0 -255
- package/src/server/route-manifest-cache.ts +0 -173
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Segment Codec
|
|
3
|
+
*
|
|
4
|
+
* RSC serialization/deserialization for cached segments.
|
|
5
|
+
* Handles the Flight protocol stream <-> string conversion
|
|
6
|
+
* and the segment-level encode/decode lifecycle.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/// <reference types="@vitejs/plugin-rsc/types" />
|
|
10
|
+
|
|
11
|
+
import type { ResolvedSegment } from "../types.js";
|
|
12
|
+
import type { SerializedSegmentData } from "./types.js";
|
|
13
|
+
import {
|
|
14
|
+
renderToReadableStream,
|
|
15
|
+
createTemporaryReferenceSet,
|
|
16
|
+
} from "@vitejs/plugin-rsc/rsc";
|
|
17
|
+
import { createFromReadableStream } from "@vitejs/plugin-rsc/rsc";
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Stream Utilities (internal)
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Convert a ReadableStream to a string.
|
|
25
|
+
*/
|
|
26
|
+
export async function streamToString(
|
|
27
|
+
stream: ReadableStream<Uint8Array>,
|
|
28
|
+
): Promise<string> {
|
|
29
|
+
const reader = stream.getReader();
|
|
30
|
+
const decoder = new TextDecoder();
|
|
31
|
+
let result = "";
|
|
32
|
+
|
|
33
|
+
while (true) {
|
|
34
|
+
const { done, value } = await reader.read();
|
|
35
|
+
if (done) break;
|
|
36
|
+
result += decoder.decode(value, { stream: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
result += decoder.decode(); // flush
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Convert a string to a ReadableStream.
|
|
45
|
+
*/
|
|
46
|
+
export function stringToStream(str: string): ReadableStream<Uint8Array> {
|
|
47
|
+
const encoder = new TextEncoder();
|
|
48
|
+
const uint8 = encoder.encode(str);
|
|
49
|
+
|
|
50
|
+
return new ReadableStream({
|
|
51
|
+
start(controller) {
|
|
52
|
+
controller.enqueue(uint8);
|
|
53
|
+
controller.close();
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ============================================================================
|
|
59
|
+
// RSC Serialization Primitives (internal)
|
|
60
|
+
// ============================================================================
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* RSC-serialize a value using React Server Components stream.
|
|
64
|
+
* Used for serializing loaderData, layout, loading components etc.
|
|
65
|
+
*
|
|
66
|
+
* Returns undefined for null/undefined inputs (component fields that are absent).
|
|
67
|
+
* For contexts where null is a valid result (loader caching, "use cache"),
|
|
68
|
+
* use serializeResult() instead which preserves null through RSC Flight.
|
|
69
|
+
*/
|
|
70
|
+
export async function rscSerialize(
|
|
71
|
+
value: unknown,
|
|
72
|
+
): Promise<string | undefined> {
|
|
73
|
+
if (value === undefined || value === null) return undefined;
|
|
74
|
+
|
|
75
|
+
const temporaryReferences = createTemporaryReferenceSet();
|
|
76
|
+
const stream = renderToReadableStream(value, { temporaryReferences });
|
|
77
|
+
return streamToString(stream);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* RSC-deserialize a value from a stored string.
|
|
82
|
+
*/
|
|
83
|
+
export async function rscDeserialize<T>(
|
|
84
|
+
encoded: string | undefined,
|
|
85
|
+
): Promise<T | undefined> {
|
|
86
|
+
if (!encoded) return undefined;
|
|
87
|
+
|
|
88
|
+
const temporaryReferences = createTemporaryReferenceSet();
|
|
89
|
+
const stream = stringToStream(encoded);
|
|
90
|
+
return createFromReadableStream<T>(stream, { temporaryReferences });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// Null-Preserving RSC Serialization (for caching)
|
|
95
|
+
// ============================================================================
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* RSC-serialize any value including null.
|
|
99
|
+
* Unlike rscSerialize(), this does NOT skip null — it serializes it through
|
|
100
|
+
* RSC Flight so that a loader returning null produces a valid cached entry
|
|
101
|
+
* rather than a permanent cache miss.
|
|
102
|
+
*
|
|
103
|
+
* Returns null only on serialization failure.
|
|
104
|
+
*/
|
|
105
|
+
export async function serializeResult(value: unknown): Promise<string | null> {
|
|
106
|
+
try {
|
|
107
|
+
const temporaryReferences = createTemporaryReferenceSet();
|
|
108
|
+
const stream = renderToReadableStream(value, { temporaryReferences });
|
|
109
|
+
return await streamToString(stream);
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* RSC-deserialize a cached result string.
|
|
117
|
+
* Counterpart to serializeResult() — always receives a non-empty string.
|
|
118
|
+
*/
|
|
119
|
+
export async function deserializeResult<T>(encoded: string): Promise<T> {
|
|
120
|
+
const temporaryReferences = createTemporaryReferenceSet();
|
|
121
|
+
const stream = stringToStream(encoded);
|
|
122
|
+
return createFromReadableStream<T>(stream, { temporaryReferences });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// Public API
|
|
127
|
+
// ============================================================================
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* RSC-deserialize a single encoded component string back to a React element.
|
|
131
|
+
* Used by the static handler runtime to revive pre-rendered components.
|
|
132
|
+
* Identical to deserializeResult<unknown>.
|
|
133
|
+
*/
|
|
134
|
+
export const deserializeComponent: (encoded: string) => Promise<unknown> =
|
|
135
|
+
deserializeResult;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Serialize segments for storage.
|
|
139
|
+
* Each segment's component, layout, loading, and loaderData are RSC-serialized.
|
|
140
|
+
* Metadata is preserved as-is.
|
|
141
|
+
*/
|
|
142
|
+
export async function serializeSegments(
|
|
143
|
+
segments: ResolvedSegment[],
|
|
144
|
+
): Promise<SerializedSegmentData[]> {
|
|
145
|
+
return Promise.all(
|
|
146
|
+
segments.map(async (segment): Promise<SerializedSegmentData> => {
|
|
147
|
+
const temporaryReferences = createTemporaryReferenceSet();
|
|
148
|
+
|
|
149
|
+
// Await component if it's a Promise (intercepts with loading keep component as Promise)
|
|
150
|
+
const componentResolved =
|
|
151
|
+
segment.component instanceof Promise
|
|
152
|
+
? await segment.component
|
|
153
|
+
: segment.component;
|
|
154
|
+
|
|
155
|
+
// Serialize the component to RSC stream
|
|
156
|
+
const stream = renderToReadableStream(componentResolved, {
|
|
157
|
+
temporaryReferences,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// RSC-serialize loading: "null" string distinguishes explicit null from undefined
|
|
161
|
+
const encodedLoading =
|
|
162
|
+
segment.loading !== undefined
|
|
163
|
+
? segment.loading === null
|
|
164
|
+
? "null"
|
|
165
|
+
: await rscSerialize(segment.loading)
|
|
166
|
+
: undefined;
|
|
167
|
+
|
|
168
|
+
// Await loaderData / loaderDataPromise if they're Promises
|
|
169
|
+
const loaderDataResolved =
|
|
170
|
+
segment.loaderData instanceof Promise
|
|
171
|
+
? await segment.loaderData
|
|
172
|
+
: segment.loaderData;
|
|
173
|
+
const loaderDataPromiseResolved =
|
|
174
|
+
segment.loaderDataPromise instanceof Promise
|
|
175
|
+
? await segment.loaderDataPromise
|
|
176
|
+
: segment.loaderDataPromise;
|
|
177
|
+
|
|
178
|
+
// Parallelize stream-to-string and RSC serialization of sub-fields
|
|
179
|
+
const [
|
|
180
|
+
encoded,
|
|
181
|
+
encodedLayout,
|
|
182
|
+
encodedLoaderData,
|
|
183
|
+
encodedLoaderDataPromise,
|
|
184
|
+
] = await Promise.all([
|
|
185
|
+
streamToString(stream),
|
|
186
|
+
segment.layout ? rscSerialize(segment.layout) : undefined,
|
|
187
|
+
rscSerialize(loaderDataResolved),
|
|
188
|
+
rscSerialize(loaderDataPromiseResolved),
|
|
189
|
+
]);
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
encoded,
|
|
193
|
+
encodedLayout,
|
|
194
|
+
encodedLoading,
|
|
195
|
+
encodedLoaderData,
|
|
196
|
+
encodedLoaderDataPromise,
|
|
197
|
+
metadata: {
|
|
198
|
+
id: segment.id,
|
|
199
|
+
type: segment.type,
|
|
200
|
+
namespace: segment.namespace,
|
|
201
|
+
index: segment.index,
|
|
202
|
+
params: segment.params,
|
|
203
|
+
slot: segment.slot,
|
|
204
|
+
belongsToRoute: segment.belongsToRoute,
|
|
205
|
+
layoutName: segment.layoutName,
|
|
206
|
+
parallelName: segment.parallelName,
|
|
207
|
+
loaderId: segment.loaderId,
|
|
208
|
+
loaderIds: segment.loaderIds,
|
|
209
|
+
transition: segment.transition,
|
|
210
|
+
mountPath: segment.mountPath,
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}),
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Deserialize segments from storage.
|
|
219
|
+
* Reconstructs ResolvedSegment objects from RSC-serialized data.
|
|
220
|
+
*/
|
|
221
|
+
export async function deserializeSegments(
|
|
222
|
+
data: SerializedSegmentData[],
|
|
223
|
+
): Promise<ResolvedSegment[]> {
|
|
224
|
+
return Promise.all(
|
|
225
|
+
data.map(async (item): Promise<ResolvedSegment> => {
|
|
226
|
+
const temporaryReferences = createTemporaryReferenceSet();
|
|
227
|
+
|
|
228
|
+
// Handle the "null" sentinel for loading before RSC deserialization.
|
|
229
|
+
// During serialization, loading: null is stored as the string "null" to
|
|
230
|
+
// distinguish it from undefined.
|
|
231
|
+
const loadingIsNullSentinel = item.encodedLoading === "null";
|
|
232
|
+
|
|
233
|
+
const [component, layout, loaderData, loaderDataPromise, loadingData] =
|
|
234
|
+
await Promise.all([
|
|
235
|
+
createFromReadableStream(stringToStream(item.encoded), {
|
|
236
|
+
temporaryReferences,
|
|
237
|
+
}),
|
|
238
|
+
rscDeserialize(item.encodedLayout),
|
|
239
|
+
rscDeserialize(item.encodedLoaderData),
|
|
240
|
+
rscDeserialize(item.encodedLoaderDataPromise),
|
|
241
|
+
loadingIsNullSentinel
|
|
242
|
+
? (null as any)
|
|
243
|
+
: rscDeserialize(item.encodedLoading),
|
|
244
|
+
]);
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
...item.metadata,
|
|
248
|
+
component,
|
|
249
|
+
layout,
|
|
250
|
+
loading: loadingData,
|
|
251
|
+
loaderData,
|
|
252
|
+
loaderDataPromise,
|
|
253
|
+
} as ResolvedSegment;
|
|
254
|
+
}),
|
|
255
|
+
);
|
|
256
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Taint symbol for request-scoped objects.
|
|
3
|
+
*
|
|
4
|
+
* Objects branded with NOCACHE_SYMBOL (ctx, env, req) are excluded from
|
|
5
|
+
* "use cache" cache keys and trigger handle capture mode so that side
|
|
6
|
+
* effects (breadcrumbs, metadata) are recorded and replayed on cache hit.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const NOCACHE_SYMBOL: unique symbol = Symbol.for("rango:nocache") as any;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if a value is tainted (request-scoped, should not be in cache key).
|
|
13
|
+
*/
|
|
14
|
+
export function isTainted(value: unknown): boolean {
|
|
15
|
+
return (
|
|
16
|
+
value !== null &&
|
|
17
|
+
value !== undefined &&
|
|
18
|
+
typeof value === "object" &&
|
|
19
|
+
(NOCACHE_SYMBOL as symbol) in (value as Record<symbol, unknown>)
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Symbol stamped on tainted ctx during "use cache" function execution.
|
|
25
|
+
* cookies(), headers(), ctx.set(), ctx.header(), etc. check this flag and
|
|
26
|
+
* throw if present — reads would cache per-request data under a shared key,
|
|
27
|
+
* and side effects would be lost on cache hit.
|
|
28
|
+
*
|
|
29
|
+
* The value is a numeric reference count, not a boolean. Multiple concurrent
|
|
30
|
+
* cached functions sharing the same ctx/requestCtx each increment on entry
|
|
31
|
+
* and decrement on exit. Guards fire when count > 0.
|
|
32
|
+
*/
|
|
33
|
+
export const INSIDE_CACHE_EXEC: unique symbol = Symbol.for(
|
|
34
|
+
"rango:inside-cache-exec",
|
|
35
|
+
) as any;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Increment the INSIDE_CACHE_EXEC ref count on an object.
|
|
39
|
+
*/
|
|
40
|
+
export function stampCacheExec(obj: object): void {
|
|
41
|
+
const current = (obj as any)[INSIDE_CACHE_EXEC] ?? 0;
|
|
42
|
+
(obj as any)[INSIDE_CACHE_EXEC] = current + 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Decrement the INSIDE_CACHE_EXEC ref count on an object.
|
|
47
|
+
* Deletes the symbol when the count reaches zero so the `in` check
|
|
48
|
+
* used by guards no longer fires.
|
|
49
|
+
*/
|
|
50
|
+
export function unstampCacheExec(obj: object): void {
|
|
51
|
+
const current = (obj as any)[INSIDE_CACHE_EXEC] ?? 0;
|
|
52
|
+
if (current <= 1) {
|
|
53
|
+
delete (obj as any)[INSIDE_CACHE_EXEC];
|
|
54
|
+
} else {
|
|
55
|
+
(obj as any)[INSIDE_CACHE_EXEC] = current - 1;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Throw if ctx is inside a "use cache" execution.
|
|
61
|
+
* Call from side-effecting ctx methods (set, header, etc.) and cookie mutations.
|
|
62
|
+
*/
|
|
63
|
+
export function assertNotInsideCacheExec(
|
|
64
|
+
ctx: unknown,
|
|
65
|
+
methodName: string,
|
|
66
|
+
): void {
|
|
67
|
+
if (
|
|
68
|
+
ctx !== null &&
|
|
69
|
+
ctx !== undefined &&
|
|
70
|
+
typeof ctx === "object" &&
|
|
71
|
+
(INSIDE_CACHE_EXEC as symbol) in (ctx as Record<symbol, unknown>)
|
|
72
|
+
) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`ctx.${methodName}() cannot be called inside a "use cache" function. ` +
|
|
75
|
+
`Side effects on the request context are lost on cache hit because ` +
|
|
76
|
+
`the function body is skipped. Extract the data fetch into a separate ` +
|
|
77
|
+
`cached function and call ctx.${methodName}() outside it, or use the ` +
|
|
78
|
+
`route-level cache() DSL which caches all segments (handler + children) ` +
|
|
79
|
+
`together.`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Brand symbol for functions wrapped by registerCachedFunction().
|
|
86
|
+
* Used at runtime to detect when a "use cache" function is misused
|
|
87
|
+
* (e.g., passed as middleware).
|
|
88
|
+
*/
|
|
89
|
+
export const CACHED_FN_SYMBOL: unique symbol = Symbol.for(
|
|
90
|
+
"rango:cached-fn",
|
|
91
|
+
) as any;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check if a value is a "use cache" wrapped function.
|
|
95
|
+
*/
|
|
96
|
+
export function isCachedFunction(value: unknown): boolean {
|
|
97
|
+
return typeof value === "function" && (CACHED_FN_SYMBOL as symbol) in value;
|
|
98
|
+
}
|
package/src/cache/types.ts
CHANGED
|
@@ -75,14 +75,14 @@ export interface SegmentCacheStore<TEnv = unknown> {
|
|
|
75
75
|
* @example Using cookies for locale
|
|
76
76
|
* ```typescript
|
|
77
77
|
* keyGenerator: (ctx, defaultKey) => {
|
|
78
|
-
* const locale =
|
|
78
|
+
* const locale = cookies().get('locale')?.value || 'en';
|
|
79
79
|
* return `${locale}:${defaultKey}`;
|
|
80
80
|
* }
|
|
81
81
|
* ```
|
|
82
82
|
*/
|
|
83
83
|
readonly keyGenerator?: (
|
|
84
84
|
ctx: RequestContext<TEnv>,
|
|
85
|
-
defaultKey: string
|
|
85
|
+
defaultKey: string,
|
|
86
86
|
) => string | Promise<string>;
|
|
87
87
|
|
|
88
88
|
/**
|
|
@@ -98,7 +98,12 @@ export interface SegmentCacheStore<TEnv = unknown> {
|
|
|
98
98
|
* @param ttl - Time-to-live in seconds
|
|
99
99
|
* @param swr - Optional stale-while-revalidate window in seconds
|
|
100
100
|
*/
|
|
101
|
-
set(
|
|
101
|
+
set(
|
|
102
|
+
key: string,
|
|
103
|
+
data: CachedEntryData,
|
|
104
|
+
ttl: number,
|
|
105
|
+
swr?: number,
|
|
106
|
+
): Promise<void>;
|
|
102
107
|
|
|
103
108
|
/**
|
|
104
109
|
* Delete a cached entry
|
|
@@ -121,7 +126,9 @@ export interface SegmentCacheStore<TEnv = unknown> {
|
|
|
121
126
|
* Get a cached Response by key.
|
|
122
127
|
* Returns the response and whether it should be revalidated (SWR).
|
|
123
128
|
*/
|
|
124
|
-
getResponse?(
|
|
129
|
+
getResponse?(
|
|
130
|
+
key: string,
|
|
131
|
+
): Promise<{ response: Response; shouldRevalidate: boolean } | null>;
|
|
125
132
|
|
|
126
133
|
/**
|
|
127
134
|
* Store a Response with TTL and optional SWR window.
|
|
@@ -130,7 +137,62 @@ export interface SegmentCacheStore<TEnv = unknown> {
|
|
|
130
137
|
* @param ttl - Time-to-live in seconds
|
|
131
138
|
* @param swr - Optional stale-while-revalidate window in seconds
|
|
132
139
|
*/
|
|
133
|
-
putResponse?(
|
|
140
|
+
putResponse?(
|
|
141
|
+
key: string,
|
|
142
|
+
response: Response,
|
|
143
|
+
ttl: number,
|
|
144
|
+
swr?: number,
|
|
145
|
+
): Promise<void>;
|
|
146
|
+
|
|
147
|
+
// ============================================================================
|
|
148
|
+
// Function Cache Methods (optional, for "use cache" directive)
|
|
149
|
+
// ============================================================================
|
|
150
|
+
// These methods cache individual function/component return values.
|
|
151
|
+
// Stores that support "use cache" should implement these methods.
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get a cached function result by key.
|
|
155
|
+
* Returns the serialized value, optional handle data, and staleness flag.
|
|
156
|
+
*/
|
|
157
|
+
getItem?(key: string): Promise<CacheItemResult | null>;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Store a function result with TTL and optional SWR window.
|
|
161
|
+
* @param key - Cache key (format: use-cache:{functionId}:{serializedArgs})
|
|
162
|
+
* @param value - RSC-serialized return value
|
|
163
|
+
* @param options - TTL, SWR, handle data, and tags
|
|
164
|
+
*/
|
|
165
|
+
setItem?(
|
|
166
|
+
key: string,
|
|
167
|
+
value: string,
|
|
168
|
+
options?: CacheItemOptions,
|
|
169
|
+
): Promise<void>;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Result from getItem() for function-level caching ("use cache").
|
|
174
|
+
*/
|
|
175
|
+
export interface CacheItemResult {
|
|
176
|
+
/** RSC-serialized return value */
|
|
177
|
+
value: string;
|
|
178
|
+
/** Handle data captured during execution (breadcrumbs, metadata, etc.) */
|
|
179
|
+
handles?: Record<string, SegmentHandleData>;
|
|
180
|
+
/** Whether the entry is stale and should be revalidated */
|
|
181
|
+
shouldRevalidate: boolean;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Options for setItem() for function-level caching ("use cache").
|
|
186
|
+
*/
|
|
187
|
+
export interface CacheItemOptions {
|
|
188
|
+
/** Handle data to store alongside the value */
|
|
189
|
+
handles?: Record<string, SegmentHandleData>;
|
|
190
|
+
/** Time-to-live in seconds */
|
|
191
|
+
ttl?: number;
|
|
192
|
+
/** Stale-while-revalidate window in seconds */
|
|
193
|
+
swr?: number;
|
|
194
|
+
/** Cache tags for invalidation */
|
|
195
|
+
tags?: string[];
|
|
134
196
|
}
|
|
135
197
|
|
|
136
198
|
/**
|
|
@@ -151,7 +213,10 @@ export interface SerializedSegmentData {
|
|
|
151
213
|
/** RSC-encoded loaderDataPromise (if present) */
|
|
152
214
|
encodedLoaderDataPromise?: string;
|
|
153
215
|
/** Segment metadata (everything except component, layout, loading, and loader data) */
|
|
154
|
-
metadata: Omit<
|
|
216
|
+
metadata: Omit<
|
|
217
|
+
ResolvedSegment,
|
|
218
|
+
"component" | "layout" | "loading" | "loaderData" | "loaderDataPromise"
|
|
219
|
+
>;
|
|
155
220
|
}
|
|
156
221
|
|
|
157
222
|
/**
|
|
@@ -234,7 +299,6 @@ export interface CachedEntryResult {
|
|
|
234
299
|
handles: Record<string, SegmentHandleData>;
|
|
235
300
|
}
|
|
236
301
|
|
|
237
|
-
|
|
238
302
|
/**
|
|
239
303
|
* Segment cache provider interface
|
|
240
304
|
*
|
|
@@ -262,7 +326,7 @@ export interface SegmentCacheProvider {
|
|
|
262
326
|
restore(
|
|
263
327
|
cacheKey: string,
|
|
264
328
|
params: Record<string, string>,
|
|
265
|
-
loaderPromises: Map<string, Promise<any
|
|
329
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
266
330
|
): Promise<[ResolvedSegment[], string[]] | null>;
|
|
267
331
|
|
|
268
332
|
/**
|
|
@@ -276,117 +340,3 @@ export interface SegmentCacheProvider {
|
|
|
276
340
|
*/
|
|
277
341
|
cacheEntry(cacheKey: string, segments: ResolvedSegment[]): void;
|
|
278
342
|
}
|
|
279
|
-
|
|
280
|
-
// ============================================================================
|
|
281
|
-
// Generic Cache Store (for future extensibility)
|
|
282
|
-
// ============================================================================
|
|
283
|
-
// These types support a general-purpose cache interface that can be used
|
|
284
|
-
// for caching arbitrary values (responses, streams, objects). Currently,
|
|
285
|
-
// the segment caching system uses SegmentCacheStore directly, but these
|
|
286
|
-
// types enable future use cases like response caching or data caching.
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Supported cache value types for the generic CacheStore interface.
|
|
290
|
-
* @internal Reserved for future extensibility
|
|
291
|
-
*/
|
|
292
|
-
export type CacheValue =
|
|
293
|
-
| ReadableStream<Uint8Array>
|
|
294
|
-
| Response
|
|
295
|
-
| ArrayBuffer
|
|
296
|
-
| string
|
|
297
|
-
| unknown[] // JSON-serializable array
|
|
298
|
-
| Record<string, unknown>; // JSON-serializable object
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Cache entry returned by match().
|
|
302
|
-
* @internal Reserved for future extensibility
|
|
303
|
-
*/
|
|
304
|
-
export interface CacheEntry<T = CacheValue> {
|
|
305
|
-
/** The cached value */
|
|
306
|
-
value: T;
|
|
307
|
-
/** Optional metadata stored with the entry */
|
|
308
|
-
metadata?: CacheMetadata;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Original value type for reconstruction.
|
|
313
|
-
* @internal Reserved for future extensibility
|
|
314
|
-
*/
|
|
315
|
-
export type CacheValueType =
|
|
316
|
-
| "stream"
|
|
317
|
-
| "response"
|
|
318
|
-
| "arraybuffer"
|
|
319
|
-
| "string"
|
|
320
|
-
| "object";
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Metadata associated with a cache entry.
|
|
324
|
-
* @internal Reserved for future extensibility
|
|
325
|
-
*/
|
|
326
|
-
export interface CacheMetadata {
|
|
327
|
-
/** Timestamp when entry expires (ms since epoch) */
|
|
328
|
-
expiresAt?: number;
|
|
329
|
-
/** Tags for bulk invalidation */
|
|
330
|
-
tags?: string[];
|
|
331
|
-
/** Original value type for reconstruction on read */
|
|
332
|
-
valueType?: CacheValueType;
|
|
333
|
-
/** Response headers (preserved when caching Response) */
|
|
334
|
-
responseHeaders?: Record<string, string>;
|
|
335
|
-
/** Response status (preserved when caching Response) */
|
|
336
|
-
responseStatus?: number;
|
|
337
|
-
/** Custom metadata */
|
|
338
|
-
[key: string]: unknown;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Options for put().
|
|
343
|
-
* @internal Reserved for future extensibility
|
|
344
|
-
*/
|
|
345
|
-
export interface CachePutOptions {
|
|
346
|
-
/** Time-to-live in seconds */
|
|
347
|
-
ttl?: number;
|
|
348
|
-
/** Metadata to store with entry */
|
|
349
|
-
metadata?: Omit<CacheMetadata, "expiresAt">;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Generic cache store interface for arbitrary value types.
|
|
354
|
-
*
|
|
355
|
-
* This interface is designed for future extensibility to support caching
|
|
356
|
-
* responses, streams, and other values. Currently, segment caching uses
|
|
357
|
-
* the SegmentCacheStore interface directly.
|
|
358
|
-
*
|
|
359
|
-
* Implementations must handle:
|
|
360
|
-
* - Stream values (clone before storing, streams can only be read once)
|
|
361
|
-
* - Promise values (await before storing)
|
|
362
|
-
* - Expiration/TTL
|
|
363
|
-
*
|
|
364
|
-
* @internal Reserved for future extensibility
|
|
365
|
-
*/
|
|
366
|
-
export interface CacheStore {
|
|
367
|
-
/**
|
|
368
|
-
* Retrieve a cached entry by key.
|
|
369
|
-
* @param key - Cache key
|
|
370
|
-
* @returns The cached entry or undefined if not found/expired
|
|
371
|
-
*/
|
|
372
|
-
match<T = CacheValue>(key: string): Promise<CacheEntry<T> | undefined>;
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Store a value in the cache.
|
|
376
|
-
* @param key - Cache key
|
|
377
|
-
* @param value - Value to cache (stream, response, string, object, etc.)
|
|
378
|
-
* @param options - TTL, metadata, etc.
|
|
379
|
-
*/
|
|
380
|
-
put<T extends CacheValue>(
|
|
381
|
-
key: string,
|
|
382
|
-
value: T,
|
|
383
|
-
options?: CachePutOptions
|
|
384
|
-
): Promise<void>;
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Delete a cached entry.
|
|
388
|
-
* @param key - Cache key
|
|
389
|
-
* @returns true if entry was deleted, false if not found
|
|
390
|
-
*/
|
|
391
|
-
delete(key: string): Promise<boolean>;
|
|
392
|
-
}
|
package/src/client.rsc.tsx
CHANGED
|
@@ -17,7 +17,6 @@ export {
|
|
|
17
17
|
OutletProvider,
|
|
18
18
|
useOutlet,
|
|
19
19
|
useLoader,
|
|
20
|
-
useLoaderData,
|
|
21
20
|
ErrorBoundary,
|
|
22
21
|
type ErrorBoundaryProps,
|
|
23
22
|
} from "./client.js";
|
|
@@ -64,6 +63,8 @@ export { Meta } from "./handles/meta.js";
|
|
|
64
63
|
// MetaTags is a "use client" component that can be imported from RSC
|
|
65
64
|
export { MetaTags } from "./handles/MetaTags.js";
|
|
66
65
|
export type { MetaDescriptor, MetaDescriptorBase } from "./router/types.js";
|
|
66
|
+
// Breadcrumbs handle works in RSC context
|
|
67
|
+
export { Breadcrumbs, type BreadcrumbItem } from "./handles/breadcrumbs.js";
|
|
67
68
|
|
|
68
69
|
// Location state - createLocationState works in RSC (just creates definition)
|
|
69
70
|
// useLocationState is NOT exported here as it uses client hooks
|
|
@@ -71,6 +72,7 @@ export {
|
|
|
71
72
|
createLocationState,
|
|
72
73
|
type LocationStateDefinition,
|
|
73
74
|
type LocationStateEntry,
|
|
75
|
+
type LocationStateOptions,
|
|
74
76
|
} from "./browser/react/location-state-shared.js";
|
|
75
77
|
|
|
76
78
|
// Re-export useHref - it's a "use client" hook
|