JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 30
  • Score
    100M100P100Q43640F
  • License MIT

Package Exports

  • reminist

Readme

Reminist

license-info stars-info

last-commit commit-activity code-size

top-language bundle-size

💡 About

Blazing fast, zero-dependency, TypeScript-native router for any environment.

Reminist is a high-performance routing library built with TypeScript. It uses an optimized Radix Tree structure to deliver O(1) (constant time) lookups for static routes and extremely fast resolution for dynamic and wildcard routes. It's designed for high-throughput environments where every microsecond counts.


🚀 Key Features

  * Exceptional Performance: Radix tree implementation with O(1) lookups for static nodes and optimized path resolution.   * Type-Safe by Design: Written entirely in TypeScript for a great developer experience.   * Zero Dependencies: Lightweight and easy to integrate into any project.   * Flexible Route Patterns: Full support for static, dynamic, wildcard, and catch-all routes.   * Environment Agnostic: Works seamlessly in Node.js, Bun, Deno, and modern browsers.   * Path Caching: Automatically caches processed URL paths to avoid redundant string manipulation.


🏁 Benchmarks

The following benchmarks compare Reminist against Memoirist, another high-performance router, to showcase its performance.

Lower latency is better. Higher throughput is better.

Addition Test (Setup)

This test measures the performance of adding a large set of routes.

Task Name Latency avg (ns) Throughput avg (ops/s)
Reminist: Add All Routes 3,667.2 286,944
Memoirist: Add All Routes 6,403.7 170,519

Search Test (Runtime)

This test measures the lookup performance for various route types.

Task Name Latency avg (ns) Throughput avg (ops/s)
Reminist: Find Static Route 76.38 13,525,457
Memoirist: Find Static Route 85.04 13,019,877
Reminist: Find Dynamic Route 71.51 14,356,322
Memoirist: Find Dynamic Route 138.14 7,578,509
Reminist: Find Catch-All Route 69.76 16,160,417
Memoirist: Find Wildcard Route 71.95 14,428,133
Reminist: Find Non-Existent Route 59.85 17,778,347
Memoirist: Find Non-Existent Route 49.20 20,804,685

Analysis

  • Setup Performance: Reminist is approximately 75% faster at adding routes, resulting in a 68% higher throughput during the setup phase.
  • Dynamic Routes: For dynamic routes (e.g., /users/:id), Reminist is nearly twice as fast, which is crucial for modern applications.
  • Static & Wildcard Routes: Reminist consistently outperforms in lookups for static and wildcard/catch-all routes.
  • Non-Existent Routes: Memoirist shows a faster lookup for non-existent paths, indicating a different internal optimization for failed searches. However, Reminist remains exceptionally fast, with an average lookup time of less than 60 nanoseconds.

Overall, the benchmarks demonstrate Reminist's superior performance in the most critical and frequent operations: adding routes and finding existing paths.


📦 Installation

# Using npm
npm install reminist

# Using bun
bun add reminist

# Using yarn
yarn add reminist

# Using pnpm
pnpm add reminist

🏁 Getting Started

Here's a quick example to get you up and running:

import { Reminist } from 'reminist'

/**
 * Defines the structure for the data stored with each route.
 */
interface MyRouteData {
  id: string
  handler: (req: any, res: any) => void
}

/**
 * A typed router instance created using Reminist.
 * For comparison, here is how you would create a router that is not typed:
 * const router = new Reminist({ keys: ['GET', 'POST'] })
 */
const router = Reminist.create({ keys: ['GET', 'POST'] }).withData<MyRouteData>()

/**
 * Adding routes
 */
router.add('GET', '/', { id: 'home', handler: (_, res) => res.send('Welcome!') })
router.add('GET', '/users/:id', { id: 'getUser', handler: (req, res) => res.send(`User: ${req.params.id}`) })
router.add('POST', '/users', { id: 'createUser', handler: (_, res) => res.send('User created') })
router.add('GET', '/assets/*', { id: 'static', handler: (_, res) => res.send('Serving asset') })

/**
 * Attempts to find a route that matches the 'GET' method and '/users/10' path.
 */
const result = router.find('GET', '/users/10')

if (result.node) {
  console.log('Route found!')
  console.log('ID:', result.node.store?.id)
  console.log('Params:', result.params)
}

📖 API Reference

new Reminist<Data, Keys>(options)

Creates a new router instance.

  * Data: A generic type for the data you want to store in each route's endpoint.   * Keys: A const array of strings representing the top-level keys (e.g., HTTP methods).   * options: An object containing:       * keys: Keys. The array of top-level keys.

.add(key, path, store)

Adds a route to the tree.

  * key: Keys[number]. The top-level key (e.g., 'GET').   * path: string. The URL path for the route.   * store: Data. The data to store at this endpoint.

.find(key, path)

Finds a node in the tree that matches the given path. This is the primary method for routing.

  * key: Keys[number]. The top-level key to search within.   * path: string. The URL path to look up.   * Returns: An object { node: Node | null; params: Record<string, string> }.       * node: The matching Node object if found, otherwise null. Check node.endpoint to see if it's a usable route.       * params: An object containing any dynamic parameters extracted from the path.

.has(key, path)

Checks if a route exists and is a valid endpoint.

  * key: Keys[number].   * path: string.   * Returns: boolean.

.delete(key, path)

Removes a route from the tree. This method will also perform "pruning" by removing orphan nodes up the tree.

  * key: Keys[number].   * path: string. The exact literal path to remove (e.g., '/users/:id').   * Returns: boolean. true if the route was successfully deleted, false if it was not found.


🧠 How It Works: The Radix Tree

At its core, Reminist uses a Radix Tree (a highly efficient type of Trie) to store and retrieve routes.

The Problem

A simple array of routes would require iterating through every single route for each incoming request (O(N)), which is extremely slow. A basic hash map (Map) could find static routes quickly (O(1)), but it can't handle dynamic parameters like /users/:id.

The Solution: A Tree of Prefixes

A Radix Tree solves this by breaking paths down into segments and storing them in a tree structure. Each node in the tree represents a part of a URL.

For example, the routes /users/profile and /users/settings would be stored like this:

(GET)
  └── "users"
      ├── "profile"  (endpoint)
      └── "settings" (endpoint)

When you search for /users/profile, the router doesn't compare against all routes. It traverses the tree:

1.  Finds the "users" child. 2.  From there, finds the "profile" child. 3.  The search depth is proportional to the number of segments in the URL, not the total number of routes in the system.

The Reminist Optimization ✨

Reminist takes this a step further. Instead of storing a node's children in an array and iterating to find a match (which is O(N)), we do the following:

  * Static children are stored in a Map. This makes finding the next segment an O(1) operation.   * Dynamic, wildcard, and catch-all children are stored in dedicated properties on the parent node. Since a node can only have one dynamic child, this lookup is also an O(1) property access.

This structure eliminates loops in the critical path of the find method, making it one of the fastest possible router implementations.


🛣️ Route Syntax

Reminist supports several common routing patterns.

Type Syntax Example Description
Static /path/to/page /about/contact Matches the exact path. The fastest type of route.
Dynamic /:param /users/:id Matches any segment and captures its value in params.
Wildcard * /assets/* A standalone * consumes the rest of the URL. The captured value is available in params['*'].
Catch-All /[...param] /files/[...filePath] Captures all remaining segments as a single value in params.
Optional Catch-All /[[...param]] /docs/[[...slug]] Behaves like a catch-all but also matches the route if no further segments are provided.

🤝 Contributing

Contributions are welcome! If you find a bug or have a feature request, please open an issue. If you want to contribute code, please open a pull request.


📜 License

This project is licensed under the MIT License. See the LICENSE file for details.