@octanejs/remix-router 0.1.0

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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +90 -0
  3. package/package.json +53 -0
  4. package/src/dom.ts +16 -0
  5. package/src/index.ts +267 -0
  6. package/src/internal.ts +37 -0
  7. package/src/lib/Await.tsrx +145 -0
  8. package/src/lib/Await.tsrx.d.ts +6 -0
  9. package/src/lib/DefaultErrorComponent.tsrx +47 -0
  10. package/src/lib/DefaultErrorComponent.tsrx.d.ts +2 -0
  11. package/src/lib/RenderErrorBoundary.tsrx +117 -0
  12. package/src/lib/RenderErrorBoundary.tsrx.d.ts +14 -0
  13. package/src/lib/actions.ts +90 -0
  14. package/src/lib/components/MemoryRouter.tsrx +42 -0
  15. package/src/lib/components/MemoryRouter.tsrx.d.ts +10 -0
  16. package/src/lib/components/Navigate.ts +60 -0
  17. package/src/lib/components/Router.tsrx +88 -0
  18. package/src/lib/components/Router.tsrx.d.ts +13 -0
  19. package/src/lib/components/RouterProvider.tsrx +320 -0
  20. package/src/lib/components/RouterProvider.tsrx.d.ts +21 -0
  21. package/src/lib/components/Routes.tsrx +53 -0
  22. package/src/lib/components/Routes.tsrx.d.ts +7 -0
  23. package/src/lib/components/routes-collector.ts +317 -0
  24. package/src/lib/components/utils.ts +171 -0
  25. package/src/lib/components/with-props.ts +72 -0
  26. package/src/lib/context.ts +129 -0
  27. package/src/lib/dom/Form.tsrx +76 -0
  28. package/src/lib/dom/Form.tsrx.d.ts +22 -0
  29. package/src/lib/dom/Link.tsrx +91 -0
  30. package/src/lib/dom/Link.tsrx.d.ts +22 -0
  31. package/src/lib/dom/NavLink.tsrx +118 -0
  32. package/src/lib/dom/NavLink.tsrx.d.ts +33 -0
  33. package/src/lib/dom/dom.ts +344 -0
  34. package/src/lib/dom/hooks.ts +93 -0
  35. package/src/lib/dom/lib.ts +822 -0
  36. package/src/lib/dom/routers.tsrx +104 -0
  37. package/src/lib/dom/routers.tsrx.d.ts +23 -0
  38. package/src/lib/dom/server.tsrx +350 -0
  39. package/src/lib/dom/server.tsrx.d.ts +25 -0
  40. package/src/lib/errors.ts +84 -0
  41. package/src/lib/framework-stubs.ts +103 -0
  42. package/src/lib/hooks.ts +1797 -0
  43. package/src/lib/href.ts +71 -0
  44. package/src/lib/react-types.ts +10 -0
  45. package/src/lib/router/history.ts +763 -0
  46. package/src/lib/router/instrumentation.ts +496 -0
  47. package/src/lib/router/links.ts +192 -0
  48. package/src/lib/router/router.ts +7165 -0
  49. package/src/lib/router/server-runtime-types.ts +12 -0
  50. package/src/lib/router/url.ts +8 -0
  51. package/src/lib/router/utils.ts +2179 -0
  52. package/src/lib/server-runtime/cookies.ts +240 -0
  53. package/src/lib/server-runtime/crypto.ts +55 -0
  54. package/src/lib/server-runtime/mode.ts +16 -0
  55. package/src/lib/server-runtime/sessions/cookieStorage.ts +54 -0
  56. package/src/lib/server-runtime/sessions/memoryStorage.ts +59 -0
  57. package/src/lib/server-runtime/sessions.ts +291 -0
  58. package/src/lib/server-runtime/warnings.ts +10 -0
  59. package/src/lib/types/future.ts +16 -0
  60. package/src/lib/types/params.ts +8 -0
  61. package/src/lib/types/register.ts +39 -0
  62. package/src/lib/types/route-module.ts +17 -0
  63. package/src/lib/types/utils.ts +37 -0
@@ -0,0 +1,104 @@
1
+ // BrowserRouter / HashRouter / HistoryRouter — transcribed from
2
+ // react-router@7.18.1 lib/dom/lib.tsx. Declarative routers over browser /
3
+ // hash / caller-provided history (upstream repeats the subscribe body in each;
4
+ // so does this transcription).
5
+ import { startTransition, useCallback, useLayoutEffect, useRef, useState } from 'octane';
6
+ import { createBrowserHistory, createHashHistory } from '../router/history';
7
+ import { Router } from '../components/Router.tsrx';
8
+
9
+ export function BrowserRouter(props) @{
10
+ const { basename, children, useTransitions, window } = props;
11
+
12
+ const historyRef = useRef(null);
13
+ if (historyRef.current == null) {
14
+ historyRef.current = createBrowserHistory({ window, v5Compat: true });
15
+ }
16
+
17
+ const history = historyRef.current;
18
+ const [state, setStateImpl] = useState({
19
+ action: history.action,
20
+ location: history.location,
21
+ });
22
+ const setState = useCallback((newState) => {
23
+ if (useTransitions === false) {
24
+ setStateImpl(newState);
25
+ } else {
26
+ startTransition(() => setStateImpl(newState));
27
+ }
28
+ }, [useTransitions]);
29
+
30
+ useLayoutEffect(() => history.listen(setState), [history, setState]);
31
+
32
+ <Router
33
+ basename={basename}
34
+ location={state.location}
35
+ navigationType={state.action}
36
+ navigator={history}
37
+ useTransitions={useTransitions}
38
+ children={children}
39
+ />
40
+ }
41
+
42
+ export function HashRouter(props) @{
43
+ const { basename, children, useTransitions, window } = props;
44
+
45
+ const historyRef = useRef(null);
46
+ if (historyRef.current == null) {
47
+ historyRef.current = createHashHistory({ window, v5Compat: true });
48
+ }
49
+
50
+ const history = historyRef.current;
51
+ const [state, setStateImpl] = useState({
52
+ action: history.action,
53
+ location: history.location,
54
+ });
55
+ const setState = useCallback((newState) => {
56
+ if (useTransitions === false) {
57
+ setStateImpl(newState);
58
+ } else {
59
+ startTransition(() => setStateImpl(newState));
60
+ }
61
+ }, [useTransitions]);
62
+
63
+ useLayoutEffect(() => history.listen(setState), [history, setState]);
64
+
65
+ <Router
66
+ basename={basename}
67
+ location={state.location}
68
+ navigationType={state.action}
69
+ navigator={history}
70
+ useTransitions={useTransitions}
71
+ children={children}
72
+ />
73
+ }
74
+
75
+ /**
76
+ * A declarative router that accepts a pre-instantiated `history` object.
77
+ * Exported as `unstable_HistoryRouter`, as upstream.
78
+ */
79
+ export function HistoryRouter(props) @{
80
+ const { basename, children, history, useTransitions } = props;
81
+
82
+ const [state, setStateImpl] = useState({
83
+ action: history.action,
84
+ location: history.location,
85
+ });
86
+ const setState = useCallback((newState) => {
87
+ if (useTransitions === false) {
88
+ setStateImpl(newState);
89
+ } else {
90
+ startTransition(() => setStateImpl(newState));
91
+ }
92
+ }, [useTransitions]);
93
+
94
+ useLayoutEffect(() => history.listen(setState), [history, setState]);
95
+
96
+ <Router
97
+ basename={basename}
98
+ location={state.location}
99
+ navigationType={state.action}
100
+ navigator={history}
101
+ useTransitions={useTransitions}
102
+ children={children}
103
+ />
104
+ }
@@ -0,0 +1,23 @@
1
+ // Type declaration for the .tsrx components (resolved by relative path).
2
+ import type { History } from '../router/history';
3
+
4
+ export declare const BrowserRouter: (props: {
5
+ basename?: string;
6
+ children?: unknown;
7
+ useTransitions?: boolean;
8
+ window?: Window;
9
+ }) => unknown;
10
+
11
+ export declare const HashRouter: (props: {
12
+ basename?: string;
13
+ children?: unknown;
14
+ useTransitions?: boolean;
15
+ window?: Window;
16
+ }) => unknown;
17
+
18
+ export declare const HistoryRouter: (props: {
19
+ basename?: string;
20
+ children?: unknown;
21
+ history: History;
22
+ useTransitions?: boolean;
23
+ }) => unknown;
@@ -0,0 +1,350 @@
1
+ // Static SSR — transcribed from react-router@7.18.1 lib/dom/server.tsx onto
2
+ // octane: StaticRouter (a Router that may not navigate), StaticRouterProvider
3
+ // (renders a static data router + the __staticRouterHydrationData script),
4
+ // createStaticHandler (the dom wrapper adding mapRouteProperties), and
5
+ // createStaticRouter (a stateless DataRouter over a StaticHandlerContext).
6
+ // Render through `octane/server` (renderToString / renderToPipeableStream).
7
+ import type { Location, Path, To } from '../router/history.ts';
8
+ import { Action as NavigationType, createPath, invariant, parsePath } from '../router/history.ts';
9
+ import {
10
+ IDLE_BLOCKER,
11
+ IDLE_FETCHER,
12
+ IDLE_NAVIGATION,
13
+ createStaticHandler as routerCreateStaticHandler,
14
+ } from '../router/router.ts';
15
+ import { convertRoutesToDataRoutes, isRouteErrorResponse } from '../router/utils.ts';
16
+ import { ABSOLUTE_URL_REGEX } from '../router/url.ts';
17
+ import { Router } from '../components/Router.tsrx';
18
+ import { DataRoutes } from '../components/RouterProvider.tsrx';
19
+ import { mapRouteProperties } from '../components/utils.ts';
20
+ import {
21
+ DataRouterContext,
22
+ DataRouterStateContext,
23
+ FetchersContext,
24
+ ViewTransitionContext,
25
+ } from '../context.ts';
26
+
27
+ /**
28
+ * A Router that may not navigate to any other location. This is useful on the
29
+ * server where there is no stateful UI.
30
+ */
31
+ export function StaticRouter(props) @{
32
+ const { basename, children, location: locationPropIn = '/' } = props;
33
+
34
+ let locationProp = locationPropIn;
35
+ if (typeof locationProp === 'string') {
36
+ locationProp = parsePath(locationProp);
37
+ }
38
+
39
+ const action = NavigationType.Pop;
40
+ const location = {
41
+ pathname: locationProp.pathname || '/',
42
+ search: locationProp.search || '',
43
+ hash: locationProp.hash || '',
44
+ state: locationProp.state != null ? locationProp.state : null,
45
+ key: locationProp.key || 'default',
46
+ mask: undefined,
47
+ };
48
+
49
+ const staticNavigator = getStatelessNavigator();
50
+ <Router
51
+ basename={basename}
52
+ location={location}
53
+ navigationType={action}
54
+ navigator={staticNavigator}
55
+ static={true}
56
+ useTransitions={false}
57
+ children={children}
58
+ />
59
+ }
60
+
61
+ /**
62
+ * A DataRouter that may not navigate to any other location. This is useful on
63
+ * the server where there is no stateful UI.
64
+ */
65
+ export function StaticRouterProvider(props) @{
66
+ const { context, router, hydrate = true, nonce } = props;
67
+
68
+ invariant(router && context, 'You must provide `router` and `context` to <StaticRouterProvider>');
69
+
70
+ const dataRouterContext = {
71
+ router,
72
+ navigator: getStatelessNavigator(),
73
+ static: true,
74
+ staticContext: context,
75
+ basename: context.basename || '/',
76
+ };
77
+
78
+ const fetchersContext = new Map();
79
+
80
+ let hydrateScript = '';
81
+
82
+ if (hydrate !== false) {
83
+ const data = {
84
+ loaderData: context.loaderData,
85
+ actionData: context.actionData,
86
+ errors: serializeErrors(context.errors),
87
+ };
88
+ // Use JSON.parse here instead of embedding a raw JS object here to speed
89
+ // up parsing on the client. Dual-stringify is needed to ensure all quotes
90
+ // are properly escaped in the resulting string. See:
91
+ // https://v8.dev/blog/cost-of-javascript-2019#json
92
+ const json = escapeHtml(JSON.stringify(JSON.stringify(data)));
93
+ hydrateScript = `window.__staticRouterHydrationData = JSON.parse(${json});`;
94
+ }
95
+
96
+ const { state } = dataRouterContext.router;
97
+
98
+ <>
99
+ <DataRouterContext.Provider value={dataRouterContext}>
100
+ <DataRouterStateContext.Provider value={state}>
101
+ <FetchersContext.Provider value={fetchersContext}>
102
+ <ViewTransitionContext.Provider value={{ isTransitioning: false }}>
103
+ <Router
104
+ basename={dataRouterContext.basename}
105
+ location={state.location}
106
+ navigationType={state.historyAction}
107
+ navigator={dataRouterContext.navigator}
108
+ static={dataRouterContext.static}
109
+ useTransitions={false}
110
+ >
111
+ <DataRoutes
112
+ manifest={router.manifest}
113
+ routes={router.routes}
114
+ future={router.future}
115
+ state={state}
116
+ isStatic={true}
117
+ />
118
+ </Router>
119
+ </ViewTransitionContext.Provider>
120
+ </FetchersContext.Provider>
121
+ </DataRouterStateContext.Provider>
122
+ </DataRouterContext.Provider>
123
+ @if (hydrateScript !== '') {
124
+ <script
125
+ suppressHydrationWarning
126
+ nonce={nonce}
127
+ dangerouslySetInnerHTML={{ __html: hydrateScript }}
128
+ />
129
+ }
130
+ </>
131
+ }
132
+
133
+ function serializeErrors(errors) {
134
+ if (!errors) return null;
135
+ const entries = Object.entries(errors);
136
+ const serialized = {};
137
+ for (const [key, val] of entries) {
138
+ // Hey you! If you change this, please change the corresponding logic in
139
+ // deserializeErrors in react-router-dom/index.tsx :)
140
+ if (isRouteErrorResponse(val)) {
141
+ serialized[key] = { ...val, __type: 'RouteErrorResponse' };
142
+ } else if (val instanceof Error) {
143
+ // Do not serialize stack traces from SSR for security reasons
144
+ serialized[key] = {
145
+ message: val.message,
146
+ __type: 'Error',
147
+ // If this is a subclass (i.e., ReferenceError), send up the type so we
148
+ // can re-create the same type during hydration.
149
+ ...(val.name !== 'Error'
150
+ ? {
151
+ __subType: val.name,
152
+ }
153
+ : {}),
154
+ };
155
+ } else {
156
+ serialized[key] = val;
157
+ }
158
+ }
159
+ return serialized;
160
+ }
161
+
162
+ function getStatelessNavigator() {
163
+ return {
164
+ createHref,
165
+ encodeLocation,
166
+ push(to) {
167
+ throw new Error(`You cannot use navigator.push() on the server because it is a stateless ` +
168
+ `environment. This error was probably triggered when you did a ` +
169
+ `\`navigate(${JSON.stringify(to)})\` somewhere in your app.`);
170
+ },
171
+ replace(to) {
172
+ throw new Error(`You cannot use navigator.replace() on the server because it is a stateless ` +
173
+ `environment. This error was probably triggered when you did a ` +
174
+ `\`navigate(${JSON.stringify(to)}, { replace: true })\` somewhere ` +
175
+ `in your app.`);
176
+ },
177
+ go(delta) {
178
+ throw new Error(`You cannot use navigator.go() on the server because it is a stateless ` +
179
+ `environment. This error was probably triggered when you did a ` +
180
+ `\`navigate(${delta})\` somewhere in your app.`);
181
+ },
182
+ back() {
183
+ throw new Error(`You cannot use navigator.back() on the server because it is a stateless ` +
184
+ `environment.`);
185
+ },
186
+ forward() {
187
+ throw new Error(`You cannot use navigator.forward() on the server because it is a stateless ` +
188
+ `environment.`);
189
+ },
190
+ };
191
+ }
192
+
193
+ /**
194
+ * Create a static handler to perform server-side data loading.
195
+ */
196
+ export function createStaticHandler(routes, opts) {
197
+ return routerCreateStaticHandler(routes, {
198
+ ...opts,
199
+ mapRouteProperties,
200
+ });
201
+ }
202
+
203
+ /**
204
+ * Create a static DataRouter for server-side rendering.
205
+ */
206
+ export function createStaticRouter(routes, context, opts = {}) {
207
+ const manifest = {};
208
+ const dataRoutes = convertRoutesToDataRoutes(routes, mapRouteProperties, undefined, manifest);
209
+
210
+ // Because our context matches may be from a set of routes passed to
211
+ // createStaticHandler(), we update them here with our newly created/enhanced
212
+ // data routes
213
+ const matches = context.matches.map((match) => {
214
+ const route = manifest[match.route.id] || match.route;
215
+ return {
216
+ ...match,
217
+ route,
218
+ };
219
+ });
220
+
221
+ const msg = (method) =>
222
+ `You cannot use router.${method}() on the server because it is a stateless environment`;
223
+
224
+ return {
225
+ get basename() {
226
+ return context.basename;
227
+ },
228
+ get future() {
229
+ return {
230
+ v8_middleware: false,
231
+ v8_passThroughRequests: false,
232
+ v8_trailingSlashAwareDataRequests: false,
233
+ ...opts?.future,
234
+ };
235
+ },
236
+ get state() {
237
+ return {
238
+ historyAction: NavigationType.Pop,
239
+ location: context.location,
240
+ matches,
241
+ loaderData: context.loaderData,
242
+ actionData: context.actionData,
243
+ errors: context.errors,
244
+ initialized: true,
245
+ renderFallback: false,
246
+ navigation: IDLE_NAVIGATION,
247
+ restoreScrollPosition: null,
248
+ preventScrollReset: false,
249
+ revalidation: 'idle',
250
+ fetchers: new Map(),
251
+ blockers: new Map(),
252
+ };
253
+ },
254
+ get routes() {
255
+ return dataRoutes;
256
+ },
257
+ get branches() {
258
+ return opts.branches;
259
+ },
260
+ get manifest() {
261
+ return manifest;
262
+ },
263
+ get window() {
264
+ return undefined;
265
+ },
266
+ initialize() {
267
+ throw msg('initialize');
268
+ },
269
+ subscribe() {
270
+ throw msg('subscribe');
271
+ },
272
+ enableScrollRestoration() {
273
+ throw msg('enableScrollRestoration');
274
+ },
275
+ navigate() {
276
+ throw msg('navigate');
277
+ },
278
+ fetch() {
279
+ throw msg('fetch');
280
+ },
281
+ revalidate() {
282
+ throw msg('revalidate');
283
+ },
284
+ createHref,
285
+ encodeLocation,
286
+ getFetcher() {
287
+ return IDLE_FETCHER;
288
+ },
289
+ deleteFetcher() {
290
+ throw msg('deleteFetcher');
291
+ },
292
+ resetFetcher() {
293
+ throw msg('resetFetcher');
294
+ },
295
+ dispose() {
296
+ throw msg('dispose');
297
+ },
298
+ getBlocker() {
299
+ return IDLE_BLOCKER;
300
+ },
301
+ deleteBlocker() {
302
+ throw msg('deleteBlocker');
303
+ },
304
+ patchRoutes() {
305
+ throw msg('patchRoutes');
306
+ },
307
+ _internalFetchControllers: new Map(),
308
+ _internalSetRoutes() {
309
+ throw msg('_internalSetRoutes');
310
+ },
311
+ _internalSetStateDoNotUseOrYouWillBreakYourApp() {
312
+ throw msg('_internalSetStateDoNotUseOrYouWillBreakYourApp');
313
+ },
314
+ };
315
+ }
316
+
317
+ function createHref(to) {
318
+ return typeof to === 'string' ? to : createPath(to);
319
+ }
320
+
321
+ function encodeLocation(to) {
322
+ let href =
323
+ typeof to === 'string' ? to : createPath(to);
324
+ // Treating this as a full URL will strip any trailing spaces so we need to
325
+ // pre-encode them since they might be part of a matching splat param from
326
+ // an ancestor route
327
+ href = href.replace(/ $/, '%20');
328
+ const encoded = ABSOLUTE_URL_REGEX.test(href) ? new URL(href) : new URL(href, 'http://localhost');
329
+ return {
330
+ pathname: encoded.pathname,
331
+ search: encoded.search,
332
+ hash: encoded.hash,
333
+ };
334
+ }
335
+
336
+ // Upstream imports escapeHtml from lib/dom/ssr/markup.ts (framework dir, not
337
+ // vendored) — the implementation is this table escape, transcribed verbatim.
338
+ const ESCAPE_LOOKUP: { [match: string]: string } = {
339
+ '&': '\\u0026',
340
+ '>': '\\u003e',
341
+ '<': '\\u003c',
342
+ '
': '\\u2028',
343
+ '
': '\\u2029',
344
+ };
345
+
346
+ const ESCAPE_REGEX = /[&><\u2028\u2029]/g;
347
+
348
+ function escapeHtml(html: string): string {
349
+ return html.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
350
+ }
@@ -0,0 +1,25 @@
1
+ // Type declaration for the .tsrx module (resolved by relative path).
2
+ import type { Location } from '../router/history';
3
+ import type { Router as DataRouter, StaticHandler, StaticHandlerContext } from '../router/router';
4
+ import type { RouteObject } from '../router/utils';
5
+
6
+ export declare const StaticRouter: (props: {
7
+ basename?: string;
8
+ children?: unknown;
9
+ location?: Partial<Location> | string;
10
+ }) => unknown;
11
+
12
+ export declare const StaticRouterProvider: (props: {
13
+ context: StaticHandlerContext;
14
+ router: DataRouter;
15
+ hydrate?: boolean;
16
+ nonce?: string;
17
+ }) => unknown;
18
+
19
+ export declare function createStaticHandler(routes: RouteObject[], opts?: any): StaticHandler;
20
+
21
+ export declare function createStaticRouter(
22
+ routes: RouteObject[],
23
+ context: StaticHandlerContext,
24
+ opts?: { branches?: any[]; future?: any },
25
+ ): DataRouter;
@@ -0,0 +1,84 @@
1
+ // Vendored from react-router@7.18.1 packages/react-router/lib/errors.ts — unmodified.
2
+ // Re-vendor with `node scripts/vendor-remix-router.mjs`; never hand-edit.
3
+ import { isDataWithResponseInit } from './router/router';
4
+ import { ErrorResponseImpl } from './router/utils';
5
+ import type { DataWithResponseInit } from './router/utils';
6
+
7
+ const ERROR_DIGEST_BASE = 'REACT_ROUTER_ERROR'; // 18
8
+ const ERROR_DIGEST_REDIRECT = 'REDIRECT'; // 8
9
+ const ERROR_DIGEST_ROUTE_ERROR_RESPONSE = 'ROUTE_ERROR_RESPONSE'; // 20
10
+
11
+ export function createRedirectErrorDigest(response: Response) {
12
+ return `${ERROR_DIGEST_BASE}:${ERROR_DIGEST_REDIRECT}:${JSON.stringify({
13
+ status: response.status,
14
+ statusText: response.statusText,
15
+ location: response.headers.get('Location'),
16
+ reloadDocument: response.headers.get('X-Remix-Reload-Document') === 'true',
17
+ replace: response.headers.get('X-Remix-Replace') === 'true',
18
+ })}`;
19
+ }
20
+
21
+ export function decodeRedirectErrorDigest(digest: string):
22
+ | undefined
23
+ | {
24
+ status: number;
25
+ statusText: string;
26
+ location: string;
27
+ reloadDocument: boolean;
28
+ replace: boolean;
29
+ } {
30
+ if (digest.startsWith(`${ERROR_DIGEST_BASE}:${ERROR_DIGEST_REDIRECT}:{`)) {
31
+ try {
32
+ let parsed = JSON.parse(digest.slice(28));
33
+ if (
34
+ typeof parsed === 'object' &&
35
+ parsed &&
36
+ typeof parsed.status === 'number' &&
37
+ typeof parsed.statusText === 'string' &&
38
+ typeof parsed.location === 'string' &&
39
+ typeof parsed.reloadDocument === 'boolean' &&
40
+ typeof parsed.replace === 'boolean'
41
+ ) {
42
+ return parsed;
43
+ }
44
+ } catch {}
45
+ }
46
+ }
47
+
48
+ export function createRouteErrorResponseDigest(response: DataWithResponseInit<unknown> | Response) {
49
+ let status = 500;
50
+ let statusText = '';
51
+ let data: unknown;
52
+ if (isDataWithResponseInit(response)) {
53
+ status = response.init?.status ?? status;
54
+ statusText = response.init?.statusText ?? statusText;
55
+ data = response.data;
56
+ } else {
57
+ status = response.status;
58
+ statusText = response.statusText;
59
+ // We can't do async work here to read the response body.
60
+ data = undefined;
61
+ }
62
+
63
+ return `${ERROR_DIGEST_BASE}:${ERROR_DIGEST_ROUTE_ERROR_RESPONSE}:${JSON.stringify({
64
+ status,
65
+ statusText,
66
+ data,
67
+ })}`;
68
+ }
69
+
70
+ export function decodeRouteErrorResponseDigest(digest: string): undefined | ErrorResponseImpl {
71
+ if (digest.startsWith(`${ERROR_DIGEST_BASE}:${ERROR_DIGEST_ROUTE_ERROR_RESPONSE}:{`)) {
72
+ try {
73
+ let parsed = JSON.parse(digest.slice(40));
74
+ if (
75
+ typeof parsed === 'object' &&
76
+ parsed &&
77
+ typeof parsed.status === 'number' &&
78
+ typeof parsed.statusText === 'string'
79
+ ) {
80
+ return new ErrorResponseImpl(parsed.status, parsed.statusText, parsed.data);
81
+ }
82
+ } catch {}
83
+ }
84
+ }
@@ -0,0 +1,103 @@
1
+ // Framework-mode + RSC surface — THROWING STUBS. react-router's framework
2
+ // mode (Meta/Links/Scripts, createRequestHandler over a @react-router/dev
3
+ // ServerBuild, fog-of-war route discovery, turbo-stream single fetch) and the
4
+ // RSC integration require the framework compiler/runtime, which is
5
+ // permanently out of scope for this port (see docs/remix-router-port-plan.md
6
+ // §1). Each export exists so the export surface reaches full parity honestly:
7
+ // importing is safe; CALLING (or rendering) throws with a pointer to the
8
+ // scope policy instead of failing somewhere deep inside.
9
+ import { createContext } from 'octane';
10
+
11
+ function frameworkStub(name: string): never {
12
+ throw new Error(
13
+ `${name} is part of react-router's FRAMEWORK mode (it requires the ` +
14
+ `@react-router/dev compiler/runtime), which @octanejs/remix-router does ` +
15
+ `not support. Library mode (data routers, declarative routers, Form/` +
16
+ `fetchers, static SSR) is fully supported — see ` +
17
+ `docs/remix-router-port-plan.md for the scope policy.`,
18
+ );
19
+ }
20
+
21
+ function rscStub(name: string): never {
22
+ throw new Error(
23
+ `${name} is part of react-router's RSC integration, which ` +
24
+ `@octanejs/remix-router does not support (octane has no RSC runtime). ` +
25
+ `See docs/remix-router-port-plan.md for the scope policy.`,
26
+ );
27
+ }
28
+
29
+ // ── Framework-mode components (throw on render) ────────────────────────────
30
+ export function Meta(): never {
31
+ frameworkStub('<Meta>');
32
+ }
33
+ export function Links(): never {
34
+ frameworkStub('<Links>');
35
+ }
36
+ export function Scripts(): never {
37
+ frameworkStub('<Scripts>');
38
+ }
39
+ export function PrefetchPageLinks(): never {
40
+ frameworkStub('<PrefetchPageLinks>');
41
+ }
42
+ export function ServerRouter(): never {
43
+ frameworkStub('<ServerRouter>');
44
+ }
45
+
46
+ // ── Framework-mode utilities ────────────────────────────────────────────────
47
+ export function createRoutesStub(): never {
48
+ frameworkStub('createRoutesStub');
49
+ }
50
+ export function createRequestHandler(): never {
51
+ frameworkStub('createRequestHandler');
52
+ }
53
+ export function unstable_setDevServerHooks(): never {
54
+ frameworkStub('unstable_setDevServerHooks');
55
+ }
56
+
57
+ // ── UNSAFE_ framework internals ─────────────────────────────────────────────
58
+ // FrameworkContext is a real context upstream (consumers null-check it — e.g.
59
+ // ScrollRestoration's SSR-script branch); a real octane context holding null
60
+ // is the honest equivalent of "not in framework mode".
61
+ export const FrameworkContext = createContext<null>(null);
62
+
63
+ export function RemixErrorBoundary(): never {
64
+ frameworkStub('UNSAFE_RemixErrorBoundary');
65
+ }
66
+ export function getPatchRoutesOnNavigationFunction(): never {
67
+ frameworkStub('UNSAFE_getPatchRoutesOnNavigationFunction');
68
+ }
69
+ export function useFogOFWarDiscovery(): never {
70
+ frameworkStub('UNSAFE_useFogOFWarDiscovery');
71
+ }
72
+ export function getHydrationData(): never {
73
+ frameworkStub('UNSAFE_getHydrationData');
74
+ }
75
+ export function createClientRoutes(): never {
76
+ frameworkStub('UNSAFE_createClientRoutes');
77
+ }
78
+ export function createClientRoutesWithHMRRevalidationOptOut(): never {
79
+ frameworkStub('UNSAFE_createClientRoutesWithHMRRevalidationOptOut');
80
+ }
81
+ export function shouldHydrateRouteLoader(): never {
82
+ frameworkStub('UNSAFE_shouldHydrateRouteLoader');
83
+ }
84
+
85
+ // ── Turbo-stream single fetch (framework data protocol) ────────────────────
86
+ export const SingleFetchRedirectSymbol = Symbol('SingleFetchRedirect');
87
+ export function decodeViaTurboStream(): never {
88
+ frameworkStub('UNSAFE_decodeViaTurboStream');
89
+ }
90
+ export function getTurboStreamSingleFetchDataStrategy(): never {
91
+ frameworkStub('UNSAFE_getTurboStreamSingleFetchDataStrategy');
92
+ }
93
+
94
+ // ── RSC ─────────────────────────────────────────────────────────────────────
95
+ export function routeRSCServerRequest(): never {
96
+ rscStub('unstable_routeRSCServerRequest');
97
+ }
98
+ export function RSCStaticRouter(): never {
99
+ rscStub('unstable_RSCStaticRouter');
100
+ }
101
+ export function RSCDefaultRootErrorBoundary(): never {
102
+ rscStub('UNSAFE_RSCDefaultRootErrorBoundary');
103
+ }