@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,198 @@
1
+ "use client";
2
+ import type { ReactNode } from "react";
3
+ import { Suspense, use, useId } from "react";
4
+ import { invariant } from "./errors";
5
+ import { OutletProvider } from "./client.js";
6
+ import type { ResolvedSegment } from "./types.js";
7
+ import { isLoaderDataResult } from "./types.js";
8
+
9
+ /**
10
+ * Stable async wrapper component for route content
11
+ * Using a module-level component ensures React sees the same component reference
12
+ * across renders, preventing unnecessary remounts during actions.
13
+ *
14
+ * When content is a pending promise, React suspends and shows the nearest
15
+ * Suspense fallback. When content is already resolved, it renders immediately
16
+ * without suspension.
17
+ *
18
+ * @param segmentId - Stable ID from segment, used for consistent keys across renders
19
+ */
20
+ export function RouteContentWrapper({
21
+ content,
22
+ fallback,
23
+ segmentId,
24
+ }: {
25
+ content: Promise<ReactNode>;
26
+ fallback?: ReactNode;
27
+ segmentId?: string;
28
+ }): ReactNode {
29
+ // Use server-provided segmentId for stable keys, fall back to useId for backwards compat
30
+ const generatedId = useId();
31
+ const id = segmentId || generatedId;
32
+ if (!content) {
33
+ // Already resolved
34
+ return content as ReactNode;
35
+ }
36
+ return (
37
+ <Suspense fallback={fallback ?? null} key={"route-content-suspense-" + id}>
38
+ <Suspender content={content} key={id} />
39
+ </Suspense>
40
+ );
41
+ }
42
+
43
+ export function RouteContentWrapperCallback<T>({
44
+ resolve,
45
+ fallback,
46
+ children,
47
+ }: {
48
+ resolve: Promise<T> | T;
49
+ fallback?: ReactNode;
50
+ children: (data: T) => ReactNode;
51
+ }): ReactNode {
52
+ const id = useId();
53
+ invariant(children, "RouteContentWrapperCallback requires children");
54
+ invariant(
55
+ typeof children === "function",
56
+ "RouteContentWrapperCallback requires children to be a function"
57
+ );
58
+ invariant(
59
+ resolve !== undefined,
60
+ "RouteContentWrapperCallback requires resolve"
61
+ );
62
+ return (
63
+ <Suspense
64
+ fallback={fallback ?? null}
65
+ key={"route-content-suspense-callback-" + id}
66
+ >
67
+ <SuspenderCallback resolve={resolve} key={id}>
68
+ {children}
69
+ </SuspenderCallback>
70
+ </Suspense>
71
+ );
72
+ }
73
+
74
+ const Suspender = ({
75
+ content,
76
+ }: {
77
+ content: Promise<ReactNode> | ReactNode;
78
+ }): ReactNode => {
79
+ invariant(content instanceof Promise, "Suspender expects a Promise content");
80
+
81
+ return use(content);
82
+ };
83
+
84
+ const SuspenderCallback = <T,>({
85
+ resolve,
86
+ children,
87
+ }: {
88
+ resolve: Promise<T> | T;
89
+ children: (data: T) => ReactNode;
90
+ }): ReactNode => {
91
+ return resolve instanceof Promise
92
+ ? children(use(resolve))
93
+ : children(resolve);
94
+ };
95
+
96
+ /**
97
+ * LoaderBoundary - Client component that resolves loader promises and renders OutletProvider
98
+ *
99
+ * This component enables streaming with loaders by:
100
+ * 1. Receiving loader promises (serializable across RSC boundary)
101
+ * 2. Using React's use() to resolve them (triggers Suspense)
102
+ * 3. Rendering OutletProvider with resolved data
103
+ *
104
+ * The callback logic lives inside this client component, avoiding the
105
+ * "Functions are not valid as a child of Client Components" error.
106
+ */
107
+ export interface LoaderBoundaryProps {
108
+ loaderDataPromise: Promise<any[]> | any[];
109
+ loaderIds: string[];
110
+ fallback?: ReactNode;
111
+ outletKey: string;
112
+ outletContent: ReactNode;
113
+ segment: ResolvedSegment;
114
+ parallel?: ResolvedSegment[];
115
+ children: ReactNode;
116
+ }
117
+
118
+ export function LoaderBoundary({
119
+ loaderDataPromise,
120
+ loaderIds,
121
+ fallback,
122
+ outletKey,
123
+ outletContent,
124
+ segment,
125
+ parallel,
126
+ children,
127
+ }: LoaderBoundaryProps): ReactNode {
128
+ const id = useId();
129
+
130
+ return (
131
+ <Suspense fallback={fallback ?? null} key={`loader-boundary-${id}`}>
132
+ <LoaderResolver
133
+ loaderDataPromise={loaderDataPromise}
134
+ loaderIds={loaderIds}
135
+ outletKey={outletKey}
136
+ outletContent={outletContent}
137
+ segment={segment}
138
+ parallel={parallel}
139
+ >
140
+ {children}
141
+ </LoaderResolver>
142
+ </Suspense>
143
+ );
144
+ }
145
+
146
+ /**
147
+ * Internal component that resolves loader promises and renders OutletProvider
148
+ */
149
+ function LoaderResolver({
150
+ loaderDataPromise,
151
+ loaderIds,
152
+ outletKey,
153
+ outletContent,
154
+ segment,
155
+ parallel,
156
+ children,
157
+ }: Omit<LoaderBoundaryProps, "fallback">): ReactNode {
158
+ // Resolve loader promises using React's use()
159
+ const resolvedData =
160
+ loaderDataPromise instanceof Promise
161
+ ? use(loaderDataPromise)
162
+ : loaderDataPromise;
163
+
164
+ // Build loaderData record from resolved values
165
+ const loaderData: Record<string, any> = {};
166
+ let loaderErrorFallback: ReactNode = null;
167
+
168
+ loaderIds.forEach((id, i) => {
169
+ const result = resolvedData[i];
170
+
171
+ if (isLoaderDataResult(result)) {
172
+ if (result.ok) {
173
+ loaderData[id] = result.data;
174
+ } else {
175
+ if (result.fallback) {
176
+ loaderErrorFallback = result.fallback;
177
+ } else {
178
+ throw new Error(result.error.message);
179
+ }
180
+ }
181
+ } else {
182
+ // Legacy format - direct data
183
+ loaderData[id] = result;
184
+ }
185
+ });
186
+
187
+ return (
188
+ <OutletProvider
189
+ key={outletKey}
190
+ content={outletContent}
191
+ segment={segment}
192
+ parallel={parallel}
193
+ loaderData={Object.keys(loaderData).length > 0 ? loaderData : undefined}
194
+ >
195
+ {loaderErrorFallback ?? children}
196
+ </OutletProvider>
197
+ );
198
+ }