@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dominic Gannaway
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # @octanejs/remix-router
2
+
3
+ [react-router](https://reactrouter.com) for the [octane](https://github.com/octanejs/octane) UI framework.
4
+
5
+ react-router v7 ships as a single package, so this port **vendors the
6
+ framework-agnostic router core byte-close** (loaders, actions, redirects,
7
+ matching, history — validated by 161 of upstream's own router unit tests
8
+ running against the vendored copy) and transcribes the React layer onto
9
+ octane's hooks. The shipped surface matches react-router 1:1 — existing code
10
+ works by changing the import.
11
+
12
+ **The port is complete — full export parity.** Data mode, declarative mode
13
+ (`MemoryRouter`, `<Routes>`/`<Route>`, `<Navigate>`), the DOM entry points
14
+ (`BrowserRouter`/`HashRouter`, `createBrowserRouter`/`createHashRouter`,
15
+ `NavLink`, `useSearchParams`), mutations (`Form`, `useSubmit`,
16
+ `useFetcher`/`useFetchers`), guards + scroll (`useBlocker`,
17
+ `unstable_usePrompt`, `ScrollRestoration`, `useBeforeUnload`,
18
+ `useViewTransitionState`), static SSR (`StaticRouter`,
19
+ `StaticRouterProvider`, `createStaticHandler`/`createStaticRouter` on
20
+ `octane/server`), and the cookie/session server runtime (`createCookie`,
21
+ `createSession`, `createCookieSessionStorage`, …). Framework mode
22
+ (`Meta`/`Links`/`Scripts`, `createRequestHandler`) and RSC are permanently out
23
+ of scope — those names exist as throwing stubs so
24
+ `tests/conformance/parity.test.ts` pins the surface at exact parity
25
+ ([docs/remix-router-port-plan.md](../../docs/remix-router-port-plan.md) has
26
+ the scope policy).
27
+
28
+ ```tsx
29
+ // before
30
+ import { createBrowserRouter, RouterProvider, Link, useLoaderData } from 'react-router';
31
+ // after
32
+ import { createBrowserRouter, RouterProvider, Link, useLoaderData } from '@octanejs/remix-router';
33
+
34
+ const router = createBrowserRouter([
35
+ {
36
+ path: '/',
37
+ Component: Layout,
38
+ children: [
39
+ { index: true, Component: Home },
40
+ { path: 'users/:id', loader: fetchUser, Component: User, errorElement: <Oops /> },
41
+ ],
42
+ },
43
+ ]);
44
+
45
+ function App() @{
46
+ <RouterProvider router={router} />
47
+ }
48
+
49
+ function User() @{
50
+ const user = useLoaderData();
51
+ <h1>{user.name as string}</h1>
52
+ }
53
+ ```
54
+
55
+ ## Entry points
56
+
57
+ | import | what you get | notes |
58
+ | --- | --- | --- |
59
+ | `@octanejs/remix-router` | the vendored core surface (matchPath, redirect, data, …) + `createMemoryRouter`, `RouterProvider`, `Outlet`, `Await`, `Link`, `useLinkClickHandler`, the full read-hook family (`useLoaderData`, `useNavigate`, `useNavigation`, `useRouteError`, …), declarative mode (`MemoryRouter`, `Routes`/`Route`, `Navigate`, `createRoutesFromChildren`/`Elements`), the DOM layer (`createBrowserRouter`/`createHashRouter`, `BrowserRouter`/`HashRouter`/`unstable_HistoryRouter`, `NavLink`, `useSearchParams`), mutations (`Form`, `useSubmit`, `useFormAction`, `useFetcher`/`useFetchers`), guards/scroll (`useBlocker`, `ScrollRestoration`, …), static SSR (`StaticRouter`/`StaticRouterProvider`/`createStaticHandler`/`createStaticRouter`), and cookies/sessions (`createCookie`, `createCookieSessionStorage`, …) | full parity |
60
+ | `@octanejs/remix-router/dom` | `RouterProvider` with octane's `flushSync` wired in | mirror of `react-router/dom` |
61
+
62
+ ## How it works
63
+
64
+ - The router core under `src/lib/router/` is vendored verbatim from
65
+ react-router 7.18.1 (`scripts/vendor-remix-router.mjs`; two documented
66
+ type-only deviations; hand-edits prohibited).
67
+ - `RouterProvider` preserves upstream's exact commit paths — `startTransition`
68
+ wrapping, the `flushSync` option, and the ViewTransition dance (dormant until
69
+ Phase E) — on octane's `useState`/`useLayoutEffect`/`useOptimistic`/
70
+ `startTransition`/`flushSync`.
71
+ - `errorElement`/`useRouteError` run on octane's `@try/@catch` (octane has no
72
+ class components); `<Await>` suspends through octane's `use()` with
73
+ upstream's TrackedPromise decoration kept verbatim, so `useAsyncValue`/
74
+ `useAsyncError` are unchanged. Render-prop children work via octane's
75
+ `isChildrenBlock` guard.
76
+ - `<Routes>` accepts BOTH children forms: descriptor children
77
+ (`createRoutesFromElements`, `{expr}` arrays) are walked upstream-style,
78
+ while the natural `.tsrx` block-children authoring goes through a
79
+ registration collector — each `<Route>` registers its config in a pre-paint
80
+ layout effect, so the first paint already shows the matched route.
81
+ Registration order is mount order, which differs from upstream's source
82
+ order only for `matchRoutes` score ties (pinned + documented).
83
+ - Refs are props (no `forwardRef`); hooks are keyed by octane's
84
+ compiler-injected per-call-site slots, forwarded through every custom hook.
85
+
86
+ ## Status
87
+
88
+ Current scope, known divergences, and verification status are tracked in the
89
+ generated [bindings status table](../../docs/bindings-status.md), sourced from
90
+ this package's [`status.json`](./status.json).
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@octanejs/remix-router",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "octane": {
7
+ "hookSlots": {
8
+ "manual": [
9
+ "src"
10
+ ]
11
+ }
12
+ },
13
+ "description": "react-router for the octane renderer — the framework-agnostic router core vendored byte-close from react-router v7, with the React layer (RouterProvider, hooks, Link, boundaries) transcribed onto octane.",
14
+ "author": {
15
+ "name": "Dominic Gannaway",
16
+ "email": "dg@domgan.com"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/octanejs/octane.git",
24
+ "directory": "packages/remix-router"
25
+ },
26
+ "main": "src/index.ts",
27
+ "module": "src/index.ts",
28
+ "types": "src/index.ts",
29
+ "files": [
30
+ "src",
31
+ "README.md"
32
+ ],
33
+ "exports": {
34
+ ".": "./src/index.ts",
35
+ "./dom": "./src/dom.ts"
36
+ },
37
+ "dependencies": {
38
+ "cookie": "^1.0.1",
39
+ "octane": "0.1.4"
40
+ },
41
+ "devDependencies": {
42
+ "@tsrx/react": "^0.2.37",
43
+ "esbuild": "^0.28.1",
44
+ "react": "^19.2.0",
45
+ "react-dom": "^19.2.0",
46
+ "react-router": "7.18.1",
47
+ "react-router-dom": "7.18.1",
48
+ "vitest": "^4.1.9"
49
+ },
50
+ "scripts": {
51
+ "test": "vitest run"
52
+ }
53
+ }
package/src/dom.ts ADDED
@@ -0,0 +1,16 @@
1
+ // `@octanejs/remix-router/dom` — mirror of upstream `react-router/dom`
2
+ // (dom-export.ts): the RouterProvider variant with flushSync wired in.
3
+ // Upstream needs the react-dom split for this; octane is one package, so
4
+ // this simply binds octane's flushSync.
5
+ import { createElement, flushSync } from 'octane';
6
+ import { RouterProvider as BaseRouterProvider } from './lib/components/RouterProvider.tsrx';
7
+ import type { Router as DataRouter } from './lib/router/router';
8
+ import type { ClientOnErrorFunction } from './lib/context';
9
+
10
+ export function RouterProvider(props: {
11
+ router: DataRouter;
12
+ onError?: ClientOnErrorFunction;
13
+ useTransitions?: boolean;
14
+ }) {
15
+ return createElement(BaseRouterProvider as any, { flushSync, ...props });
16
+ }
package/src/index.ts ADDED
@@ -0,0 +1,267 @@
1
+ // @octanejs/remix-router — react-router for the octane renderer.
2
+ //
3
+ // react-router v7 ships as a single package whose framework-agnostic core has
4
+ // no runtime subpath, so the core (lib/router/* + framework-free lib helpers)
5
+ // is VENDORED byte-close (see scripts/vendor-remix-router.mjs) and only the
6
+ // React layer is transcribed onto octane's hooks. This entry mirrors upstream
7
+ // packages/react-router/index.ts for everything the port has shipped so far —
8
+ // the phased roadmap lives in docs/remix-router-port-plan.md, and
9
+ // tests/conformance/parity.test.ts pins exactly which upstream exports are
10
+ // still expected-missing per phase.
11
+ //
12
+ // Octane-specific: hooks are keyed by a compiler-injected per-call-site
13
+ // Symbol appended as the LAST argument of every `use*` call; the hooks in
14
+ // lib/hooks.ts forward that slot into their composed base hooks. Refs are
15
+ // props (no forwardRef).
16
+
17
+ // ── Vendored core (verbatim re-exports, upstream index.ts order) ───────────
18
+ export { createContext, RouterContextProvider } from './lib/router/utils';
19
+ export { Action as NavigationType, createPath, parsePath } from './lib/router/history';
20
+ export type {
21
+ ServerInstrumentation,
22
+ ClientInstrumentation,
23
+ InstrumentRequestHandlerFunction,
24
+ InstrumentRouterFunction,
25
+ InstrumentRouteFunction,
26
+ InstrumentationHandlerResult,
27
+ } from './lib/router/instrumentation';
28
+ export { IDLE_NAVIGATION, IDLE_FETCHER, IDLE_BLOCKER } from './lib/router/router';
29
+ export {
30
+ data,
31
+ generatePath,
32
+ isRouteErrorResponse,
33
+ matchPath,
34
+ matchRoutes,
35
+ redirect,
36
+ redirectDocument,
37
+ replace,
38
+ resolvePath,
39
+ } from './lib/router/utils';
40
+ export type { InitialEntry, Location, Path, To } from './lib/router/history';
41
+ export type {
42
+ HydrationState,
43
+ GetScrollPositionFunction,
44
+ GetScrollRestorationKeyFunction,
45
+ StaticHandlerContext,
46
+ Fetcher,
47
+ Navigation,
48
+ NavigationStates,
49
+ RelativeRoutingType,
50
+ Blocker,
51
+ BlockerFunction,
52
+ Router as DataRouter,
53
+ RouterState,
54
+ RouterInit,
55
+ RouterSubscriber,
56
+ RouterNavigateOptions,
57
+ RouterFetchOptions,
58
+ RevalidationState,
59
+ } from './lib/router/router';
60
+ export type {
61
+ ActionFunction,
62
+ ActionFunctionArgs,
63
+ DataRouteMatch,
64
+ DataRouteObject,
65
+ DataStrategyFunction,
66
+ DataStrategyFunctionArgs,
67
+ DataStrategyMatch,
68
+ DataStrategyResult,
69
+ ErrorResponse,
70
+ FormEncType,
71
+ FormMethod,
72
+ IndexRouteObject,
73
+ LazyRouteFunction,
74
+ LoaderFunction,
75
+ LoaderFunctionArgs,
76
+ MiddlewareFunction,
77
+ NonIndexRouteObject,
78
+ ParamParseKey,
79
+ Params,
80
+ PathMatch,
81
+ PathParam,
82
+ PathPattern,
83
+ RedirectFunction,
84
+ RouteMatch,
85
+ RouteObject,
86
+ ShouldRevalidateFunction,
87
+ ShouldRevalidateFunctionArgs,
88
+ UIMatch,
89
+ } from './lib/router/utils';
90
+ export { href } from './lib/href';
91
+ export { createSearchParams } from './lib/dom/dom';
92
+ export type {
93
+ FetcherSubmitOptions,
94
+ ParamKeyValuePair,
95
+ SubmitOptions,
96
+ URLSearchParamsInit,
97
+ SubmitTarget,
98
+ } from './lib/dom/dom';
99
+
100
+ // ── React layer (Phases A + B) ─────────────────────────────────────────────
101
+ export type { NavigateOptions, Navigator, ClientOnErrorFunction } from './lib/context';
102
+ export { AwaitContextProvider as UNSAFE_AwaitContextProvider } from './lib/context';
103
+ export { Await } from './lib/Await.tsrx';
104
+ export { Router } from './lib/components/Router.tsrx';
105
+ export { RouterProvider, Outlet } from './lib/components/RouterProvider.tsrx';
106
+ export { createMemoryRouter, renderMatches } from './lib/components/utils';
107
+ export type { MemoryRouterOpts } from './lib/components/utils';
108
+ export { MemoryRouter } from './lib/components/MemoryRouter.tsrx';
109
+ export { Navigate } from './lib/components/Navigate';
110
+ export type { NavigateProps } from './lib/components/Navigate';
111
+ export { Routes } from './lib/components/Routes.tsrx';
112
+ export {
113
+ Route,
114
+ createRoutesFromChildren,
115
+ createRoutesFromElements,
116
+ } from './lib/components/routes-collector';
117
+ export type { NavigateFunction } from './lib/hooks';
118
+ export {
119
+ useActionData,
120
+ useAsyncError,
121
+ useAsyncValue,
122
+ useBlocker,
123
+ useHref,
124
+ useInRouterContext,
125
+ useLoaderData,
126
+ useLocation,
127
+ useMatch,
128
+ useMatches,
129
+ useNavigate,
130
+ useNavigation,
131
+ useNavigationType,
132
+ useOutlet,
133
+ useOutletContext,
134
+ useParams,
135
+ useResolvedPath,
136
+ useRevalidator,
137
+ useRouteError,
138
+ useRouteLoaderData,
139
+ useRoutes,
140
+ useRoute as unstable_useRoute,
141
+ useRouterState as unstable_useRouterState,
142
+ } from './lib/hooks';
143
+ export type {
144
+ unstable_RouterState,
145
+ unstable_RouterStateActiveVariant,
146
+ unstable_RouterStatePendingVariant,
147
+ unstable_RouterStateMatch,
148
+ } from './lib/hooks';
149
+
150
+ // ── DOM layer (Phases A + C + D + E + F) ────────────────────────────────────
151
+ export { Link } from './lib/dom/Link.tsrx';
152
+ export { useLinkClickHandler } from './lib/dom/hooks';
153
+ export { NavLink } from './lib/dom/NavLink.tsrx';
154
+ export type { NavLinkRenderProps } from './lib/dom/NavLink.tsrx';
155
+ export {
156
+ BrowserRouter,
157
+ HashRouter,
158
+ HistoryRouter as unstable_HistoryRouter,
159
+ } from './lib/dom/routers.tsrx';
160
+ export {
161
+ createBrowserRouter,
162
+ createHashRouter,
163
+ useSearchParams,
164
+ useSubmit,
165
+ useFormAction,
166
+ useFetcher,
167
+ useFetchers,
168
+ useViewTransitionState,
169
+ useBeforeUnload,
170
+ usePrompt as unstable_usePrompt,
171
+ ScrollRestoration,
172
+ useScrollRestoration as UNSAFE_useScrollRestoration,
173
+ } from './lib/dom/lib';
174
+ export type {
175
+ DOMRouterOpts,
176
+ SetURLSearchParams,
177
+ SubmitFunction,
178
+ FetcherSubmitFunction,
179
+ FetcherWithComponents,
180
+ } from './lib/dom/lib';
181
+ export { Form } from './lib/dom/Form.tsrx';
182
+ export {
183
+ StaticRouter,
184
+ StaticRouterProvider,
185
+ createStaticHandler,
186
+ createStaticRouter,
187
+ } from './lib/dom/server.tsrx';
188
+
189
+ // ── UNSAFE_ surface (shipped subset) ────────────────────────────────────────
190
+ export {
191
+ createMemoryHistory as UNSAFE_createMemoryHistory,
192
+ createBrowserHistory as UNSAFE_createBrowserHistory,
193
+ createHashHistory as UNSAFE_createHashHistory,
194
+ invariant as UNSAFE_invariant,
195
+ } from './lib/router/history';
196
+ export { createRouter as UNSAFE_createRouter } from './lib/router/router';
197
+ export { ErrorResponseImpl as UNSAFE_ErrorResponseImpl } from './lib/router/utils';
198
+ export {
199
+ DataRouterContext as UNSAFE_DataRouterContext,
200
+ DataRouterStateContext as UNSAFE_DataRouterStateContext,
201
+ FetchersContext as UNSAFE_FetchersContext,
202
+ LocationContext as UNSAFE_LocationContext,
203
+ NavigationContext as UNSAFE_NavigationContext,
204
+ RouteContext as UNSAFE_RouteContext,
205
+ ViewTransitionContext as UNSAFE_ViewTransitionContext,
206
+ } from './lib/context';
207
+ export {
208
+ hydrationRouteProperties as UNSAFE_hydrationRouteProperties,
209
+ mapRouteProperties as UNSAFE_mapRouteProperties,
210
+ } from './lib/components/utils';
211
+ export {
212
+ WithComponentProps as UNSAFE_WithComponentProps,
213
+ withComponentProps as UNSAFE_withComponentProps,
214
+ WithHydrateFallbackProps as UNSAFE_WithHydrateFallbackProps,
215
+ withHydrateFallbackProps as UNSAFE_withHydrateFallbackProps,
216
+ WithErrorBoundaryProps as UNSAFE_WithErrorBoundaryProps,
217
+ withErrorBoundaryProps as UNSAFE_withErrorBoundaryProps,
218
+ } from './lib/components/with-props';
219
+ export type { Register } from './lib/types/register';
220
+ export type { MiddlewareEnabled as UNSAFE_MiddlewareEnabled } from './lib/types/future';
221
+
222
+ // ── Server runtime (vendored cookie/session surface) ────────────────────────
223
+ export { createCookie, isCookie } from './lib/server-runtime/cookies';
224
+ export type {
225
+ Cookie,
226
+ CookieOptions,
227
+ CookieParseOptions,
228
+ CookieSerializeOptions,
229
+ CookieSignatureOptions,
230
+ } from './lib/server-runtime/cookies';
231
+ export { createSession, isSession, createSessionStorage } from './lib/server-runtime/sessions';
232
+ export type {
233
+ Session,
234
+ SessionData,
235
+ SessionStorage,
236
+ SessionIdStorageStrategy,
237
+ FlashSessionData,
238
+ } from './lib/server-runtime/sessions';
239
+ export { createCookieSessionStorage } from './lib/server-runtime/sessions/cookieStorage';
240
+ export { createMemorySessionStorage } from './lib/server-runtime/sessions/memoryStorage';
241
+ export { ServerMode as UNSAFE_ServerMode } from './lib/server-runtime/mode';
242
+
243
+ // ── Framework-mode + RSC surface (throwing stubs — see framework-stubs.ts) ──
244
+ export {
245
+ Meta,
246
+ Links,
247
+ Scripts,
248
+ PrefetchPageLinks,
249
+ ServerRouter,
250
+ createRoutesStub,
251
+ createRequestHandler,
252
+ unstable_setDevServerHooks,
253
+ FrameworkContext as UNSAFE_FrameworkContext,
254
+ RemixErrorBoundary as UNSAFE_RemixErrorBoundary,
255
+ getPatchRoutesOnNavigationFunction as UNSAFE_getPatchRoutesOnNavigationFunction,
256
+ useFogOFWarDiscovery as UNSAFE_useFogOFWarDiscovery,
257
+ getHydrationData as UNSAFE_getHydrationData,
258
+ createClientRoutes as UNSAFE_createClientRoutes,
259
+ createClientRoutesWithHMRRevalidationOptOut as UNSAFE_createClientRoutesWithHMRRevalidationOptOut,
260
+ shouldHydrateRouteLoader as UNSAFE_shouldHydrateRouteLoader,
261
+ SingleFetchRedirectSymbol as UNSAFE_SingleFetchRedirectSymbol,
262
+ decodeViaTurboStream as UNSAFE_decodeViaTurboStream,
263
+ getTurboStreamSingleFetchDataStrategy as UNSAFE_getTurboStreamSingleFetchDataStrategy,
264
+ routeRSCServerRequest as unstable_routeRSCServerRequest,
265
+ RSCStaticRouter as unstable_RSCStaticRouter,
266
+ RSCDefaultRootErrorBoundary as UNSAFE_RSCDefaultRootErrorBoundary,
267
+ } from './lib/framework-stubs';
@@ -0,0 +1,37 @@
1
+ // Slot mechanics shared by the binding's plain-`.ts` hooks (same helper as
2
+ // @octanejs/tanstack-query). The octane compiler injects a per-call-site Symbol slot into
3
+ // every hook call in compiled files; these binding files are NOT compiled, so a
4
+ // hook here receives the caller's slot as its trailing argument and derives a
5
+ // distinct sub-slot for each base hook it composes.
6
+
7
+ // Memoized: subSlot runs on EVERY hook call every render; the cache returns the
8
+ // identical Symbol.for-interned value without the concat + registry lookup.
9
+ const subSlotCache = new Map<symbol, Map<string, symbol>>();
10
+ // Tag-only symbols for the slotless-caller case (see below).
11
+ const bareTagCache = new Map<string, symbol>();
12
+
13
+ export function subSlot(slot: symbol | undefined, tag: string): symbol {
14
+ // No inherited slot (the caller was NOT compiled — e.g. a vendored wrapper
15
+ // hook): return a stable TAG-ONLY symbol rather than undefined. The runtime
16
+ // combines it with the ambient withSlot path, so sibling base hooks inside
17
+ // one composed hook stay DISTINCT per tag. Returning undefined here made
18
+ // them all resolve to the bare path — one shared slot, state collision.
19
+ if (slot === undefined) {
20
+ let bare = bareTagCache.get(tag);
21
+ if (bare === undefined) bareTagCache.set(tag, (bare = Symbol.for(':' + tag)));
22
+ return bare;
23
+ }
24
+ let byTag = subSlotCache.get(slot);
25
+ if (byTag === undefined) subSlotCache.set(slot, (byTag = new Map()));
26
+ let sym = byTag.get(tag);
27
+ if (sym === undefined) byTag.set(tag, (sym = Symbol.for((slot.description ?? '') + ':' + tag)));
28
+ return sym;
29
+ }
30
+
31
+ // Split the compiler-injected trailing slot off a hook's runtime args, returning
32
+ // the user args (everything before it) and the slot.
33
+ export function splitSlot(args: any[]): [any[], symbol | undefined] {
34
+ const tail = args[args.length - 1];
35
+ const slot = typeof tail === 'symbol' ? (tail as symbol) : undefined;
36
+ return [slot !== undefined ? args.slice(0, -1) : args, slot];
37
+ }
@@ -0,0 +1,145 @@
1
+ // Await — port of react-router@7.18.1 lib/components.tsx's Await +
2
+ // AwaitErrorBoundary + ResolveAwait onto octane. The class boundary becomes an
3
+ // octane @try/@catch; the "throw to the suspense boundary" becomes use(promise)
4
+ // (octane's suspend primitive); the TrackedPromise decoration (_tracked/_data/
5
+ // _error defineProperty) is kept verbatim so useAsyncValue/useAsyncError and
6
+ // AwaitContext consumers stay upstream-identical.
7
+ //
8
+ // Boundary layering (upstream parity): a class boundary does NOT catch its own
9
+ // render throws, so the no-errorElement rethrow (bubble to the ROUTE boundary)
10
+ // and the pending suspension live in AwaitBody, OUTSIDE Await's render-error
11
+ // catch; the @try/@catch wraps only the CHILDREN render (what upstream's
12
+ // componentDidCatch actually sees).
13
+ import { use, useCallback, useContext, useRef, isChildrenBlock } from 'octane';
14
+ import { AwaitContext, DataRouterContext, DataRouterStateContext } from './context.ts';
15
+ import { getRoutePattern } from './router/utils.ts';
16
+
17
+ // NOTE: children are forwarded in PROP position (children={...}) through the
18
+ // internal layers — a JSX `{props.children}` child would be re-wrapped into an
19
+ // octane children BLOCK at each hop, destroying the render-prop identity the
20
+ // isChildrenBlock guard in ResolveAwait relies on.
21
+ export function Await(props) @{
22
+ const dataRouterContext = useContext(DataRouterContext);
23
+ const dataRouterStateContext = useContext(DataRouterStateContext);
24
+
25
+ const onError = useCallback((error, errorInfo) => {
26
+ if (dataRouterContext && dataRouterContext.onError && dataRouterStateContext) {
27
+ dataRouterContext.onError(error, {
28
+ location: dataRouterStateContext.location,
29
+ params: dataRouterStateContext.matches[0]?.params || {},
30
+ pattern: getRoutePattern(dataRouterStateContext.matches),
31
+ errorInfo,
32
+ });
33
+ }
34
+ }, [dataRouterContext, dataRouterStateContext]);
35
+
36
+ <AwaitBody
37
+ resolve={props.resolve}
38
+ errorElement={props.errorElement}
39
+ onError={onError}
40
+ children={props.children}
41
+ />
42
+ }
43
+
44
+ function AwaitBody(props) @{
45
+ // Upstream AwaitErrorBoundary.render() tracking logic, verbatim.
46
+ let promise;
47
+ let status = 'pending';
48
+
49
+ if (!(props.resolve instanceof Promise)) {
50
+ // Didn't get a promise - provide as a resolved promise
51
+ status = 'success';
52
+ promise = Promise.resolve();
53
+ Object.defineProperty(promise, '_tracked', { get: () => true });
54
+ Object.defineProperty(promise, '_data', { get: () => props.resolve });
55
+ } else if (props.resolve._tracked) {
56
+ // Already tracked promise - check contents
57
+ promise = props.resolve;
58
+ status = '_error' in promise ? 'error' : '_data' in promise ? 'success' : 'pending';
59
+ } else {
60
+ // Raw (untracked) promise - track it
61
+ status = 'pending';
62
+ const resolve = props.resolve;
63
+ Object.defineProperty(resolve, '_tracked', { get: () => true });
64
+ promise = resolve.then((data) => Object.defineProperty(resolve, '_data', { get: () => data }), (
65
+ error,
66
+ ) => {
67
+ // Log promise rejections
68
+ props.onError?.(error);
69
+ Object.defineProperty(resolve, '_error', { get: () => error });
70
+ });
71
+ }
72
+
73
+ if (status === 'error' && !props.errorElement) {
74
+ // No errorElement, throw to the nearest route-level error boundary.
75
+ // (Outside the children @try below — a boundary never catches its own
76
+ // render throw, upstream parity.)
77
+ throw props.resolve._error;
78
+ }
79
+
80
+ if (status === 'pending') {
81
+ // Suspend to the nearest boundary; the replay re-enters with
82
+ // _data/_error defined. use() is octane's suspend primitive (upstream
83
+ // throws the promise).
84
+ use(promise);
85
+ }
86
+
87
+ @if (status === 'error') {
88
+ <AwaitContext.Provider value={promise}>{props.errorElement}</AwaitContext.Provider>
89
+ } @else {
90
+ @try {
91
+ <AwaitContext.Provider value={promise}>
92
+ <ResolveAwait children={props.children} />
93
+ </AwaitContext.Provider>
94
+ } @catch (caught, reset) {
95
+ <AwaitCaught caught={caught} errorElement={props.errorElement} onError={props.onError} />
96
+ }
97
+ }
98
+ }
99
+
100
+ // A render error from the children — upstream's getDerivedStateFromError turns
101
+ // it into a rejected tracked promise rendered through errorElement (or
102
+ // rethrown when there is none).
103
+ function AwaitCaught(props) @{
104
+ // componentDidCatch parity — once per distinct caught error, during render.
105
+ const reported = useRef(null);
106
+ if (reported.current !== props.caught) {
107
+ reported.current = props.caught;
108
+ if (props.onError) {
109
+ props.onError(props.caught, { componentStack: '' });
110
+ } else {
111
+ console.error('<Await> caught the following error during render', props.caught, {
112
+ componentStack: '',
113
+ });
114
+ }
115
+ }
116
+
117
+ if (!props.errorElement) {
118
+ // Rethrow from the catch branch → propagates to the parent boundary.
119
+ throw props.caught;
120
+ }
121
+
122
+ const renderError = props.caught;
123
+ const promise = Promise.reject().catch(() => {}); // Avoid unhandled rejection warnings
124
+ Object.defineProperty(promise, '_tracked', { get: () => true });
125
+ Object.defineProperty(promise, '_error', { get: () => renderError });
126
+
127
+ <AwaitContext.Provider value={promise}>{props.errorElement}</AwaitContext.Provider>
128
+ }
129
+
130
+ // Indirection to leverage useAsyncValue for a render-prop API on `<Await>`.
131
+ // (useAsyncValue inlined via AwaitContext to keep this file free of a hooks.ts
132
+ // dependency; identical semantics.)
133
+ function ResolveAwait(props) @{
134
+ const value = useContext(AwaitContext);
135
+ const data = value?._data;
136
+ // Render-prop guard: octane children BLOCKS are functions too — only a real
137
+ // user render-prop gets called (isChildrenBlock idiom).
138
+ const toRender =
139
+ typeof props.children === 'function' && !isChildrenBlock(props.children)
140
+ ? props.children(data)
141
+ : props.children;
142
+ <>
143
+ {toRender}
144
+ </>
145
+ }
@@ -0,0 +1,6 @@
1
+ // Type declaration for the .tsrx component (resolved by relative path).
2
+ export declare const Await: <Resolve = unknown>(props: {
3
+ resolve: Resolve;
4
+ errorElement?: unknown;
5
+ children?: unknown | ((data: Awaited<Resolve>) => unknown);
6
+ }) => unknown;