@hyperpackai/router
v0.3.0
Published
HyperRouter — framework-agnostic TypeScript router with guards, loaders, redirects, and adapters.
Downloads
593
Maintainers
Readme
@hyperpackai/router — HyperRouter
HyperRouter is a framework-agnostic TypeScript router for SPAs, SSR entry points, docs apps, enterprise dashboards, and future Hyperion applications. The core has no dependency on React, Vue, Svelte, Solid, or Hyperion; framework support lives in adapter entry points.
Install
npm install @hyperpackai/routerReact users install React separately. Hyperion users inject their app's Hyperion primitives into the adapter.
Core Usage
import { createRouter, createBrowserHistory } from "@hyperpackai/router";
const router = createRouter({
history: createBrowserHistory(),
routes: [
{
id: "home",
path: "/",
component: HomePage,
meta: { title: "Home", requiresAuth: false },
},
{
id: "user-details",
path: "/users/:id",
component: UserDetailsPage,
beforeEnter: async ({ params }) => Boolean(params.id),
loader: async ({ params }) => ({ userId: params.id }),
meta: { title: "User Details", permissions: ["USER_VIEW"] },
},
{
id: "not-found",
path: "*",
component: NotFoundPage,
},
],
});
await router.navigate("/users/42?tab=profile#activity");
await router.replace("/users/42");
router.back();
router.forward();
const state = router.getState();
console.log(state.params.id);
console.log(state.query.tab);
console.log(state.matches);Core API
createRouter({ history, routes, base })createBrowserHistory()createMemoryHistory(initialPath?)router.navigate(to, options?)router.replace(to, options?)router.back()router.forward()router.subscribe(listener)router.getState()router.getLoaderData(routeId)router.destroy()
Route matching supports static paths, dynamic params such as /users/:id, optional params, wildcard routes such as * and /files/*rest, query parsing, hash locations, route metadata, nested route branches, route-level redirects, guards, loaders, and not-found state.
Router State
interface RouterState {
location: Location;
matches: RouteMatch[];
params: Record<string, string>;
query: Record<string, string | string[]>;
loaderData: Map<string, unknown>;
pending: boolean;
error: unknown;
notFound: boolean;
}Guards
Guards run before loaders and before the navigation commits.
const routes = [
{
path: "/admin",
component: AdminPage,
beforeEnter: async ({ to, from, params, query, route }) => {
if (!isSignedIn()) return "/login";
if (!canAccess(route.meta?.permissions)) return false;
return true;
},
},
];Guard return values:
trueallows navigationfalseblocks navigation"/login"redirects{ redirect: "/login", replace: true }redirects with options
Loaders
Loaders run after guards pass and before navigation commits.
const routes = [
{
id: "invoice",
path: "/invoices/:id",
loader: async ({ params, query, request, route }) => {
return fetchInvoice(params.id, query.preview === "true");
},
},
];
await router.navigate("/invoices/1001");
const invoice = router.getLoaderData("invoice");If a loader throws, state.error is set and the current URL is preserved.
Redirects
Route-level redirects:
const routes = [
{ path: "/old", redirect: "/new" },
{ path: "/new", component: NewPage },
];Loader redirects:
import { redirect } from "@hyperpackai/router";
const routes = [
{
path: "/private",
loader: async () => {
if (!isSignedIn()) return redirect("/login");
return null;
},
},
];React Adapter
The React adapter is exported separately so React never enters the core bundle.
import { createRouter, createBrowserHistory } from "@hyperpackai/router";
import {
HyperRouterProvider,
HyperOutlet,
HyperLink,
useNavigate,
useRoute,
useRouter,
} from "@hyperpackai/router/adapters/react";
const router = createRouter({
history: createBrowserHistory(),
routes,
});
export function App() {
return (
<HyperRouterProvider router={router}>
<nav>
<HyperLink to="/">Home</HyperLink>
<HyperLink to="/users/42">User</HyperLink>
</nav>
<HyperOutlet />
</HyperRouterProvider>
);
}HyperOutlet renders the deepest matched route component and passes { params, query, data, route } as props.
Hyperion Adapter
For Hyperion apps, use the ready-made runtime. It gives you a provider, links, navigation helpers, reactive route state, and hooks from one import.
/** @jsxImportSource @hyperpackai/hyperion */
import { component, computed, signal } from "@hyperpackai/hyperion";
import { createHyperionRouter } from "@hyperpackai/router/adapters/hyperion";
const {
initRouter,
RouterProvider,
Link,
NavLink,
navigate,
currentPath,
useParams,
} = createHyperionRouter({
hyperion: { component, computed, signal },
routes: [
{ id: "home", path: "/", component: HomePage, meta: { title: "Home" } },
{ id: "docs", path: "/docs/*", component: DocsPage },
{ id: "not-found", path: "*", component: NotFoundPage },
],
title: (state) => String(state.matches.at(-1)?.route.meta?.title ?? "App"),
});
export const App = component(() => (
<>
<nav>
<NavLink href="/" exactMatch>Home</NavLink>
<Link href="/docs/getting-started">Docs</Link>
</nav>
<RouterProvider />
</>
));If routes need page imports from the app entry, initialize later:
import { component, computed, signal } from "@hyperpackai/hyperion";
import { createHyperionRouter } from "@hyperpackai/router/adapters/hyperion";
const routerApi = createHyperionRouter({
hyperion: { component, computed, signal },
});
export const { initRouter, RouterProvider, Link, NavLink } = routerApi;
// app entry
export const router = initRouter(routes);The lower-level adapter still exists for custom renderers. It uses dependency injection so it can stay compatible while Hyperion APIs evolve.
import { createRouter, createBrowserHistory } from "@hyperpackai/router";
import {
createHyperionRouterAdapter,
mountRouter,
renderMatchedComponent,
} from "@hyperpackai/router/adapters/hyperion";
import { signal, effect, onCleanup } from "@hyperpackai/hyperion";
const router = createRouter({
history: createBrowserHistory(),
routes,
});
const adapter = createHyperionRouterAdapter(router, {
signal,
effect,
onCleanup,
});
adapter.currentPath.value;
adapter.currentParams.value;
await adapter.navigate("/docs");
mountRouter({
router,
render(component, { state }) {
renderIntoYourHyperionHost(component, state);
},
});
const component = renderMatchedComponent(router);createHyperionAdapter remains exported as a backward-compatible alias.
Query Utilities
import { parseQuery, stringifyQuery } from "@hyperpackai/router";
parseQuery("?tag=a&tag=b&page=1");
// { tag: ["a", "b"], page: "1" }
stringifyQuery({ tag: ["a", "b"], page: "1" });
// "?tag=a&tag=b&page=1"Metadata
Routes can carry arbitrary metadata for permissions, layouts, titles, feature flags, audit needs, and route documentation.
const routes = [
{
id: "payments",
path: "/payments",
component: PaymentsPage,
meta: {
title: "Payments",
permissions: ["PAYMENT_VIEW"],
featureFlag: "payments-v2",
},
},
];Backward Compatibility
New recommended API:
const router = createRouter({
history: createBrowserHistory(),
routes,
});Compatibility API:
const router = createBrowserRouter(routes);The package name remains @hyperpackai/router. The core route config shape is preserved where practical; the main additive changes are richer state, direct navigation helpers, adapter subpaths, and loader redirect handling.
Architecture
src/
core/
router.ts
history.ts
matcher.ts
guards.ts
loader.ts
redirects.ts
query.ts
types.ts
adapters/
react/
hyperion/
index.tsThe core is intentionally framework-free. Adapters translate router state into framework-specific rendering or reactivity.
Roadmap
- Workflow routing for multi-step enterprise flows
- Permissions matrix integration using route metadata
- Feature flag aware route resolution
- Route documentation generator
- Visual route map for devtools
- AI route assistant for route creation, dead-route detection, and guard audits
- Micro-frontend routing boundaries and handoff contracts
License
MIT
