@sirou/core
v1.1.3
Published
Universal type-safe route schema, Trie matcher, guard system, and data loaders
Downloads
164
Maintainers
Readme
@sirou/core
The engine room of Sirou. This package provides the route schema definition, Trie-based matcher, universal router interface, guard system, data loaders, and all shared TypeScript types.
Installation
npm install @sirou/coreAPI Reference
defineRoutes(config)
An identity function that provides full TypeScript inference for your route configuration. Always wrap your routes object with this.
import { defineRoutes } from "@sirou/core";
export const routes = defineRoutes({
home: { path: "/" },
user: {
path: "/user/:id",
params: { id: "string" },
meta: { title: "User Profile", requiresAuth: true },
guards: ["auth"],
},
products: {
path: "/products",
children: {
details: {
path: "/:productId",
params: { productId: "string" },
loader: async ({ params }) => {
return fetch(`/api/products/${params.productId}`).then((r) =>
r.json(),
);
},
},
},
},
notFound: { path: "*" },
});createRouter(config, adapter)
Creates a platform-agnostic router instance. In most cases you'll use an adapter-specific factory (e.g., SirouRouterProvider in React), but you can use this directly.
import { createRouter } from "@sirou/core";
const router = createRouter(routes, myAdapter);SirouRouter Interface
All Sirou routers implement this interface:
| Method | Description |
| --------------------------------- | ------------------------------------------------------ |
| go(routeName, params?) | Navigate to a route. Runs guards & loader first. |
| replace(routeName, params?) | Replace current history entry. |
| back() | Navigate back in history. |
| current() | Returns RouteInfo for the active route. |
| build(routeName, params?) | Build a URL string without navigating. |
| subscribe(listener) | Subscribe to location changes. Returns unsubscribe fn. |
| registerGuard(guard) | Register a named route guard. |
| checkGuards(routeName, params?) | Run guards without navigating. |
| getHistory() | Returns the navigation history array. |
| getLoaderData(routeName) | Returns pre-fetched data for a route. |
| getMeta(routeName) | Returns the metadata object for a route. |
| canGoBack() | Returns true if there is history to go back to. |
Route Definition
Each route in your config can have the following properties:
type RouteDefinition = {
path: string; // URL path. Use :param for dynamic segments, * for wildcard.
params?: {
// Param type definitions for validation & inference.
[key: string]:
| "string"
| "number"
| "boolean"
| "date"
| {
type: ParamType;
optional?: boolean;
validate?: (value: any) => boolean;
transform?: (value: string) => any;
};
};
meta?: Record<string, any>; // Arbitrary metadata (title, requiresAuth, etc.)
guards?: string[]; // Names of guards to run before this route.
loader?: (context: GuardContext) => Promise<any>; // Data fetcher.
children?: RouteConfig; // Nested routes.
};Route Guards
Guards are async functions registered on the router. They run before navigation commits.
router.registerGuard({
name: "auth",
execute: async ({ route, params, meta, context }) => {
const token = localStorage.getItem("token");
if (!token) {
return { allowed: false, redirect: "/login" };
}
return { allowed: true };
},
});Guard Context:
type GuardContext = {
route: string; // The target route path
params: Record<string, any>; // Resolved params
meta?: Record<string, any>; // Route metadata
context?: any; // Optional custom context
};Data Loaders
Add a loader to any route to fetch data before the component renders. The data is stored in the router and accessible via router.getLoaderData(routeName) or the useRouteData() hook in React.
const routes = defineRoutes({
products: {
path: "/products",
loader: async () => {
const res = await fetch("/api/products");
return res.json();
},
},
});Loaders run before navigation commits, ensuring the component always has data on first render.
Wildcard / 404 Routes
Use path: '*' to create a catch-all route. The Trie matcher uses priority order:
- Exact match —
/aboutmatches/about - Param match —
/user/:idmatches/user/123 - Wildcard match —
*matches anything else
const routes = defineRoutes({
home: { path: "/" },
notFound: { path: "*" },
});SchemaValidator
Validates a route config at runtime. Useful in CLI tools or dynamic configs.
import { SchemaValidator } from "@sirou/core";
const validator = new SchemaValidator();
const result = validator.validate(routes);
if (!result.valid) {
console.error(result.errors);
}Checks performed:
- Duplicate paths
- Param name mismatches between path string and
paramsconfig - Invalid guard references
JSONExporter
Serializes a route config to a plain JSON object. Used by the CLI export command and the Flutter bridge.
import { JSONExporter } from "@sirou/core";
const exporter = new JSONExporter();
const json = exporter.export(routes);
fs.writeFileSync("routes.json", JSON.stringify(json, null, 2));URLBuilder
Builds URLs from a route definition and params object. Handles param interpolation.
import { URLBuilder } from "@sirou/core";
const builder = new URLBuilder();
const url = builder.build({ path: "/user/:id" }, { id: "42" });
// => '/user/42'TypeScript Utilities
InferParams<Config>
Infers the params object type from a RouteParamConfig.
type MyParams = InferParams<{
id: "string";
page: { type: "number"; optional: true };
}>;
// => { id: string; page?: number }FlatRouteMap<Config>
Flattens a nested route config into a single-level map with dotted keys.
type Routes = FlatRouteMap<typeof routes>;
// => { 'home': ..., 'products': ..., 'products.details': ... }License
MIT
