npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sirou/core

v1.1.3

Published

Universal type-safe route schema, Trie matcher, guard system, and data loaders

Downloads

164

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/core

API 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:

  1. Exact match/about matches /about
  2. Param match/user/:id matches /user/123
  3. 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 params config
  • 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