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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@xinkjs/xin

v0.3.2

Published

An enhanced javascript URL router

Downloads

392

Readme

@xinkjs/xin

An enhanced javascript URL router.

Like it's forked predecessor, xin is a building block on which you can create other router features. Its main purpose is to register and match routes. We've enhanced it by adding helper functions to extend the matcher route functionality and set/get global middleware.

xin (zen) is a shortened spelling of our API router xink (zinc). It also resembles a Chinese word that's translated as "heart-mind". This is fitting, since xin is the heart of the xink router.

Install

bun add @xinkjs/xin
deno add npm:@xinkjs/xin
[p]npm add @xinkjs/xin

QuickStart

import { Router } from '@xinkjs/xin'

const router = new Router()

router.get('/', () => { /* ... */})
router.get('/user', () => { /* ... */})
router.post('/user', () => { /* ... */})

Route conventions

  • A path must start with a /.
  • parameters - leading colon, e.g. /blog/:slug
  • matcher - equals sign separator followed by a word label, e.g. /user/:id=number
  • wildcard - trailing asterick, e.g. /thing/*

API

new Router(options?)

Constructs a new router instance.

  • options? object
  • Returns a class instance.
import { Router } from '@xinkjs/xin'

const router = new Router()

Options

  • storeFactory? function () => Object.create(null) - Used to create a route store when a route is registered. By default, returns {} with a null prototype (no access to methods like toString(), etc).

    import { Router } from '@xinkjs/xin'
    
    const router = new Router({
      storeFactory() {
        return { handlers: new Map(), middlewares: [] }
      }
    })
    
    const store = router.register('/')
    console.log(store) // { handlers: Map {}, middlewares: [] }

router.[method](path, handler)

Registers a route with the given HTTP method.

  • path string - The route path.
  • handler function - The route handler for the method.

xin supports these methods: get, post, put, patch, delete, head, options, fallback - fallback being returned from a router.find(path) call for any method not defined for the route.

import { Router } from '@xinkjs/xin'
const router = new Router()

router.get('/', () => new Response('Hello xin!'))

router.register(path)

An alternative to router.[method]().

Registers a route and returns it's store for further configuration. If the route has already been registered, it returns the existing route store. Use this method for full control over the store, which is typically used to store route handlers and other information.

  • path string - The route path.
  • Returns: object - The route store.
import { Router } from '@xinkjs/xin'
const router = new Router()

const store = router.register('/user/:id')
console.log(store) // [Object: null prototype] {}
store['GET'] = () => new Response('Hello xin!')

Route and Segment Types

  • Static - an exact match of the path.

    '/'
    '/about'
    '/api/login'
  • Specific - a mix of static content and a parameter.

    '/v:version' // Matches '/v1'
    '/on-:event'     // Matches '/on-click'
  • Matcher - applies a matcher function to the parameter.

    These segments reference a function which the parameter is tested against. So, you can think of the function as a validator for the parameter. For the purpose of the example below, the matcher function tests against a regular expression of /^\d+$/, so it has to be a number.

    '/api/user/:id=number' // Matches  '/api/user/42
    '/api/v:version=number' // Matches '/api/v1', but not '/api/vONE'

    xin provides three built-in matcher functions:

    • letter (param) => /^[a-z]+$/i.test(param)
    • number (param) => /^\d+$/.test(param)
    • word (param) => /^\w+$/.test(param)

    You can override the built-in matchers, or create your own, by registering them with setMatcher().

  • Dynamic - a segment that begins with a :, and will match anything up to the next / or to the end of the path. The text within these bounds defines the name of the parameter.

    '/user/:id' // Matches /user/42
    '/user/:id/post' // Matches /user/42/post
    '/user/:userId/post/:postId' // Matches /user/42/post/1
    import { Router } from '@xinkjs/xin'
    const router = new Router()
    
    router.get('/user/:id', () => {})
    
    const route = router.find('/user/42')
    console.log(route.params) // { id: '42' }

    If multiple routes have a parameter in the same part of the route, the parameter names must be the same. For example, registering the following two routes would be an error because the :id and :userId parameters conflict with each other:

    '/user/:id'
    '/user/:userId/post'
  • Wildcard - path ends with a *. This will match any characters in the rest of the path, including / characters or no characters. The wildcard value will be set in the route params object with '*' as the key.

    For example, the following route:

    '/static/*'

    will match all of these URLs:

    /static/
    /static/favicon.ico
    /static/js/main.js
    /static/css/vendor/bootstrap.css
    import { Router } from '@xinkjs/xin'
    const router = new Router()
    
    router.get('/static/*', () => {})
    
    let route = router.find('/static/favicon.ico')
    console.log(route.params) // { '*': 'favicon.ico' }
    
    route = router.find('/static/')
    console.log(route.params) // { '*': '' }

router.find(path)

Attempts to find a route.

  • path string - The route path to be matched.
  • Returns { store, params } | null
import { Router } from '@xinkjs/xin'
const router = new Router()

router.get('/', () => {})
router.get('/user', () => {})
router.post('/user', () => {})
router.get('/user/:id', () => {})
router.get('/static/*', () => {})

router.find('/')
// {
//   store: { GET: [Function] },
//   params: {}
// }

router.find('/user')
// {
//   store: { GET: [Function], POST: [Function] },
//   params: {}
// }

router.find('/user/42')
// {
//   store: { GET: [Function] },
//   params: { id: '42'}
// }

router.find('/static/js/common.js')
// {
//   store: { GET: [Function] },
//   params: { '*': 'js/common.js' }
// }

router.find('/not-defined')
// null

Path Match Order

The matching order goes from more specific to less specific.

  1. Static
  2. Specific
  3. Matcher
  4. Dynamic
  5. Wildcard

For example, an incoming path of "/static/v2" will be matched in the below order; where the regex for the "word" matcher is /^\w+$/.

'/static/v2'  //static
'/static/v:number'  //specific: because the "v" makes it partially static
'/static/:version=word'  //matcher: aka dynamic with a validator, so it is more specific than dynamic
'/static/:version'  //dynamic
'/static/*'  //wildcard

router.getTree()

Returns the entire route tree.

  • Returns: Node - The router's root node.
import { Router } from '@xinkjs/xin'
const router = new Router()

const tree = router.getTree()

router.setMatcher(type, matcher)

Sets a matcher, to be used with matcher route types.

  • type string - The name of the matcher.
  • matcher (param: string) => boolean - The function which is used to test params against.
import { Router } from '@xinkjs/xin'
const router = new Router()

router.setMatcher('fruit', (param) => {
  const fruits = new Set(['apple', 'orange', 'grape'])
  return fruits.has(param)
})

router.getMatcher()

Returns a specific matcher function.

  • Returns: Matcher | null
import { Router } from '@xinkjs/xin'
const router = new Router()

const matcher = router.getMatcher('word')

router.setMiddleware(function)

Sets the global middleware function. This must be a single function, but in theory it could handle multiple functions itself.

  • function () => Promise<Response> | Response - A global middleware function that runs on every request.
import { Router } from '@xinkjs/xin'
const router = new Router()

router.setMiddleware((req) => {
  return new Response('Hello, from middleware')
})

router.getMiddleware()

Returns the middleware function.

  • Returns: () => Promise<Response> | Response

router.setErrorHandler(function)

Sets a global error handling function.

  • function (error: unknown, event?: any) => Promise<Response | void> | Response | void - A global error handling function that runs when an unexpected error is thrown.
import { Router } from '@xinkjs/xin'
const router = new Router()

router.setErrorHandler((error, request) => {
  /* Handle error */
})

router.getErrorHandler()

Returns the error handler function.

  • Returns: (error: unknown, event?: any) => Promise<Response | void> | Response | void