@ivogt/rsc-router 0.0.0-experimental.1

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.
Files changed (123) hide show
  1. package/README.md +19 -0
  2. package/package.json +131 -0
  3. package/src/__mocks__/version.ts +6 -0
  4. package/src/__tests__/route-definition.test.ts +63 -0
  5. package/src/browser/event-controller.ts +876 -0
  6. package/src/browser/index.ts +18 -0
  7. package/src/browser/link-interceptor.ts +121 -0
  8. package/src/browser/lru-cache.ts +69 -0
  9. package/src/browser/merge-segment-loaders.ts +126 -0
  10. package/src/browser/navigation-bridge.ts +891 -0
  11. package/src/browser/navigation-client.ts +155 -0
  12. package/src/browser/navigation-store.ts +823 -0
  13. package/src/browser/partial-update.ts +545 -0
  14. package/src/browser/react/Link.tsx +248 -0
  15. package/src/browser/react/NavigationProvider.tsx +228 -0
  16. package/src/browser/react/ScrollRestoration.tsx +94 -0
  17. package/src/browser/react/context.ts +53 -0
  18. package/src/browser/react/index.ts +52 -0
  19. package/src/browser/react/location-state-shared.ts +120 -0
  20. package/src/browser/react/location-state.ts +62 -0
  21. package/src/browser/react/use-action.ts +240 -0
  22. package/src/browser/react/use-client-cache.ts +56 -0
  23. package/src/browser/react/use-handle.ts +178 -0
  24. package/src/browser/react/use-link-status.ts +134 -0
  25. package/src/browser/react/use-navigation.ts +150 -0
  26. package/src/browser/react/use-segments.ts +188 -0
  27. package/src/browser/request-controller.ts +149 -0
  28. package/src/browser/rsc-router.tsx +310 -0
  29. package/src/browser/scroll-restoration.ts +324 -0
  30. package/src/browser/server-action-bridge.ts +747 -0
  31. package/src/browser/shallow.ts +35 -0
  32. package/src/browser/types.ts +443 -0
  33. package/src/cache/__tests__/memory-segment-store.test.ts +487 -0
  34. package/src/cache/__tests__/memory-store.test.ts +484 -0
  35. package/src/cache/cache-scope.ts +565 -0
  36. package/src/cache/cf/__tests__/cf-cache-store.test.ts +361 -0
  37. package/src/cache/cf/cf-cache-store.ts +274 -0
  38. package/src/cache/cf/index.ts +19 -0
  39. package/src/cache/index.ts +52 -0
  40. package/src/cache/memory-segment-store.ts +150 -0
  41. package/src/cache/memory-store.ts +253 -0
  42. package/src/cache/types.ts +366 -0
  43. package/src/client.rsc.tsx +88 -0
  44. package/src/client.tsx +609 -0
  45. package/src/components/DefaultDocument.tsx +20 -0
  46. package/src/default-error-boundary.tsx +88 -0
  47. package/src/deps/browser.ts +8 -0
  48. package/src/deps/html-stream-client.ts +2 -0
  49. package/src/deps/html-stream-server.ts +2 -0
  50. package/src/deps/rsc.ts +10 -0
  51. package/src/deps/ssr.ts +2 -0
  52. package/src/errors.ts +259 -0
  53. package/src/handle.ts +120 -0
  54. package/src/handles/MetaTags.tsx +178 -0
  55. package/src/handles/index.ts +6 -0
  56. package/src/handles/meta.ts +247 -0
  57. package/src/href-client.ts +128 -0
  58. package/src/href.ts +139 -0
  59. package/src/index.rsc.ts +69 -0
  60. package/src/index.ts +84 -0
  61. package/src/loader.rsc.ts +204 -0
  62. package/src/loader.ts +47 -0
  63. package/src/network-error-thrower.tsx +21 -0
  64. package/src/outlet-context.ts +15 -0
  65. package/src/root-error-boundary.tsx +277 -0
  66. package/src/route-content-wrapper.tsx +198 -0
  67. package/src/route-definition.ts +1333 -0
  68. package/src/route-map-builder.ts +140 -0
  69. package/src/route-types.ts +148 -0
  70. package/src/route-utils.ts +89 -0
  71. package/src/router/__tests__/match-context.test.ts +104 -0
  72. package/src/router/__tests__/match-pipelines.test.ts +537 -0
  73. package/src/router/__tests__/match-result.test.ts +566 -0
  74. package/src/router/__tests__/on-error.test.ts +935 -0
  75. package/src/router/__tests__/pattern-matching.test.ts +577 -0
  76. package/src/router/error-handling.ts +287 -0
  77. package/src/router/handler-context.ts +60 -0
  78. package/src/router/loader-resolution.ts +326 -0
  79. package/src/router/manifest.ts +116 -0
  80. package/src/router/match-context.ts +261 -0
  81. package/src/router/match-middleware/background-revalidation.ts +236 -0
  82. package/src/router/match-middleware/cache-lookup.ts +261 -0
  83. package/src/router/match-middleware/cache-store.ts +250 -0
  84. package/src/router/match-middleware/index.ts +81 -0
  85. package/src/router/match-middleware/intercept-resolution.ts +268 -0
  86. package/src/router/match-middleware/segment-resolution.ts +174 -0
  87. package/src/router/match-pipelines.ts +214 -0
  88. package/src/router/match-result.ts +212 -0
  89. package/src/router/metrics.ts +62 -0
  90. package/src/router/middleware.test.ts +1355 -0
  91. package/src/router/middleware.ts +748 -0
  92. package/src/router/pattern-matching.ts +271 -0
  93. package/src/router/revalidation.ts +190 -0
  94. package/src/router/router-context.ts +299 -0
  95. package/src/router/types.ts +96 -0
  96. package/src/router.ts +3484 -0
  97. package/src/rsc/__tests__/helpers.test.ts +175 -0
  98. package/src/rsc/handler.ts +942 -0
  99. package/src/rsc/helpers.ts +64 -0
  100. package/src/rsc/index.ts +56 -0
  101. package/src/rsc/nonce.ts +18 -0
  102. package/src/rsc/types.ts +225 -0
  103. package/src/segment-system.tsx +405 -0
  104. package/src/server/__tests__/request-context.test.ts +171 -0
  105. package/src/server/context.ts +340 -0
  106. package/src/server/handle-store.ts +230 -0
  107. package/src/server/loader-registry.ts +174 -0
  108. package/src/server/request-context.ts +470 -0
  109. package/src/server/root-layout.tsx +10 -0
  110. package/src/server/tsconfig.json +14 -0
  111. package/src/server.ts +126 -0
  112. package/src/ssr/__tests__/ssr-handler.test.tsx +188 -0
  113. package/src/ssr/index.tsx +215 -0
  114. package/src/types.ts +1473 -0
  115. package/src/use-loader.tsx +346 -0
  116. package/src/vite/__tests__/expose-loader-id.test.ts +117 -0
  117. package/src/vite/expose-action-id.ts +344 -0
  118. package/src/vite/expose-handle-id.ts +209 -0
  119. package/src/vite/expose-loader-id.ts +357 -0
  120. package/src/vite/expose-location-state-id.ts +177 -0
  121. package/src/vite/index.ts +608 -0
  122. package/src/vite/version.d.ts +12 -0
  123. package/src/vite/virtual-entries.ts +109 -0
@@ -0,0 +1,155 @@
1
+ import type {
2
+ NavigationClient,
3
+ FetchPartialOptions,
4
+ FetchPartialResult,
5
+ RscPayload,
6
+ RscBrowserDependencies,
7
+ } from "./types.js";
8
+ import { NetworkError, isNetworkError } from "../errors.js";
9
+
10
+ /**
11
+ * Create a navigation client for fetching RSC payloads
12
+ *
13
+ * The client handles building URLs with RSC parameters and
14
+ * deserializing the response using the RSC runtime.
15
+ *
16
+ * @param deps - RSC browser dependencies (createFromFetch)
17
+ * @returns NavigationClient instance
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * import { createFromFetch } from "@vitejs/plugin-rsc/browser";
22
+ *
23
+ * const client = createNavigationClient({ createFromFetch });
24
+ *
25
+ * const payload = await client.fetchPartial({
26
+ * targetUrl: "/shop/products",
27
+ * segmentIds: ["root", "shop"],
28
+ * previousUrl: "/",
29
+ * });
30
+ * ```
31
+ */
32
+ export function createNavigationClient(
33
+ deps: Pick<RscBrowserDependencies, "createFromFetch">
34
+ ): NavigationClient {
35
+ return {
36
+ /**
37
+ * Fetch a partial RSC payload for navigation
38
+ *
39
+ * Sends current segment IDs to the server so it can determine
40
+ * which segments need to be re-rendered (diff).
41
+ *
42
+ * @param options - Fetch options
43
+ * @returns RSC payload with segments and metadata, plus stream completion promise
44
+ */
45
+ async fetchPartial(
46
+ options: FetchPartialOptions
47
+ ): Promise<FetchPartialResult> {
48
+ const {
49
+ targetUrl,
50
+ segmentIds,
51
+ previousUrl,
52
+ signal,
53
+ staleRevalidation,
54
+ interceptSourceUrl,
55
+ version,
56
+ } = options;
57
+
58
+ console.log(`\n[Browser] >>> NAVIGATION`);
59
+ console.log(`[Browser] From: ${previousUrl}`);
60
+ console.log(`[Browser] To: ${targetUrl}`);
61
+ console.log(`[Browser] Segments to send: ${segmentIds.join(", ")}`);
62
+ if (staleRevalidation) {
63
+ console.log(`[Browser] Stale revalidation request`);
64
+ }
65
+
66
+ // Build fetch URL with partial rendering params
67
+ const fetchUrl = new URL(targetUrl, window.location.origin);
68
+ fetchUrl.searchParams.set("_rsc_partial", "true");
69
+ fetchUrl.searchParams.set("_rsc_segments", segmentIds.join(","));
70
+ if (staleRevalidation) {
71
+ fetchUrl.searchParams.set("_rsc_stale", "true");
72
+ }
73
+ if (version) {
74
+ fetchUrl.searchParams.set("_rsc_v", version);
75
+ }
76
+
77
+ console.log(`[Browser] Fetching: ${fetchUrl.pathname}${fetchUrl.search}`);
78
+
79
+ // Track when the stream completes
80
+ let resolveStreamComplete: () => void;
81
+ const streamComplete = new Promise<void>((resolve) => {
82
+ resolveStreamComplete = resolve;
83
+ });
84
+
85
+ // Create a response promise that tracks stream completion
86
+ const responsePromise = fetch(fetchUrl, {
87
+ headers: {
88
+ "X-RSC-Router-Client-Path": previousUrl,
89
+ ...(interceptSourceUrl && {
90
+ "X-RSC-Router-Intercept-Source": interceptSourceUrl,
91
+ }),
92
+ },
93
+ signal,
94
+ }).then((response) => {
95
+ // Check for version mismatch - server wants us to reload
96
+ const reloadUrl = response.headers.get("X-RSC-Reload");
97
+ if (reloadUrl) {
98
+ console.log(`[Browser] Version mismatch - reloading: ${reloadUrl}`);
99
+ window.location.href = reloadUrl;
100
+ // Return a never-resolving promise to prevent further processing
101
+ return new Promise<Response>(() => {});
102
+ }
103
+
104
+ if (!response.body) {
105
+ // No body means stream is already complete
106
+ resolveStreamComplete();
107
+ return response;
108
+ }
109
+
110
+ // Tee the stream: one for RSC runtime, one for tracking completion
111
+ const [rscStream, trackingStream] = response.body.tee();
112
+
113
+ // Consume the tracking stream to detect when it closes
114
+ (async () => {
115
+ const reader = trackingStream.getReader();
116
+ try {
117
+ while (true) {
118
+ const { done } = await reader.read();
119
+ if (done) break;
120
+ }
121
+ } finally {
122
+ console.log("[STREAMING] RSC stream complete");
123
+ resolveStreamComplete();
124
+ }
125
+ })();
126
+
127
+ // Return response with the RSC stream
128
+ return new Response(rscStream, {
129
+ headers: response.headers,
130
+ status: response.status,
131
+ statusText: response.statusText,
132
+ });
133
+ });
134
+
135
+ try {
136
+ // Deserialize RSC payload
137
+ const payload = await deps.createFromFetch<RscPayload>(responsePromise);
138
+ return { payload, streamComplete };
139
+ } catch (error) {
140
+ // Convert network-level errors to NetworkError for proper handling
141
+ if (isNetworkError(error)) {
142
+ throw new NetworkError(
143
+ "Unable to connect to server. Please check your connection.",
144
+ {
145
+ cause: error,
146
+ url: fetchUrl.toString(),
147
+ operation: staleRevalidation ? "revalidation" : "navigation",
148
+ }
149
+ );
150
+ }
151
+ throw error;
152
+ }
153
+ },
154
+ };
155
+ }