@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.
- package/LICENSE +21 -0
- package/README.md +90 -0
- package/package.json +53 -0
- package/src/dom.ts +16 -0
- package/src/index.ts +267 -0
- package/src/internal.ts +37 -0
- package/src/lib/Await.tsrx +145 -0
- package/src/lib/Await.tsrx.d.ts +6 -0
- package/src/lib/DefaultErrorComponent.tsrx +47 -0
- package/src/lib/DefaultErrorComponent.tsrx.d.ts +2 -0
- package/src/lib/RenderErrorBoundary.tsrx +117 -0
- package/src/lib/RenderErrorBoundary.tsrx.d.ts +14 -0
- package/src/lib/actions.ts +90 -0
- package/src/lib/components/MemoryRouter.tsrx +42 -0
- package/src/lib/components/MemoryRouter.tsrx.d.ts +10 -0
- package/src/lib/components/Navigate.ts +60 -0
- package/src/lib/components/Router.tsrx +88 -0
- package/src/lib/components/Router.tsrx.d.ts +13 -0
- package/src/lib/components/RouterProvider.tsrx +320 -0
- package/src/lib/components/RouterProvider.tsrx.d.ts +21 -0
- package/src/lib/components/Routes.tsrx +53 -0
- package/src/lib/components/Routes.tsrx.d.ts +7 -0
- package/src/lib/components/routes-collector.ts +317 -0
- package/src/lib/components/utils.ts +171 -0
- package/src/lib/components/with-props.ts +72 -0
- package/src/lib/context.ts +129 -0
- package/src/lib/dom/Form.tsrx +76 -0
- package/src/lib/dom/Form.tsrx.d.ts +22 -0
- package/src/lib/dom/Link.tsrx +91 -0
- package/src/lib/dom/Link.tsrx.d.ts +22 -0
- package/src/lib/dom/NavLink.tsrx +118 -0
- package/src/lib/dom/NavLink.tsrx.d.ts +33 -0
- package/src/lib/dom/dom.ts +344 -0
- package/src/lib/dom/hooks.ts +93 -0
- package/src/lib/dom/lib.ts +822 -0
- package/src/lib/dom/routers.tsrx +104 -0
- package/src/lib/dom/routers.tsrx.d.ts +23 -0
- package/src/lib/dom/server.tsrx +350 -0
- package/src/lib/dom/server.tsrx.d.ts +25 -0
- package/src/lib/errors.ts +84 -0
- package/src/lib/framework-stubs.ts +103 -0
- package/src/lib/hooks.ts +1797 -0
- package/src/lib/href.ts +71 -0
- package/src/lib/react-types.ts +10 -0
- package/src/lib/router/history.ts +763 -0
- package/src/lib/router/instrumentation.ts +496 -0
- package/src/lib/router/links.ts +192 -0
- package/src/lib/router/router.ts +7165 -0
- package/src/lib/router/server-runtime-types.ts +12 -0
- package/src/lib/router/url.ts +8 -0
- package/src/lib/router/utils.ts +2179 -0
- package/src/lib/server-runtime/cookies.ts +240 -0
- package/src/lib/server-runtime/crypto.ts +55 -0
- package/src/lib/server-runtime/mode.ts +16 -0
- package/src/lib/server-runtime/sessions/cookieStorage.ts +54 -0
- package/src/lib/server-runtime/sessions/memoryStorage.ts +59 -0
- package/src/lib/server-runtime/sessions.ts +291 -0
- package/src/lib/server-runtime/warnings.ts +10 -0
- package/src/lib/types/future.ts +16 -0
- package/src/lib/types/params.ts +8 -0
- package/src/lib/types/register.ts +39 -0
- package/src/lib/types/route-module.ts +17 -0
- 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';
|
package/src/internal.ts
ADDED
|
@@ -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
|
+
}
|