UNPKG

wayfarer

Version:

Composable trie based router

140 lines (118 loc) 3.69 kB
/* eslint-disable node/no-deprecated-api */ var assert = require('assert') module.exports = Trie // create a new trie // null -> obj function Trie () { if (!(this instanceof Trie)) return new Trie() this.trie = { nodes: {} } } // create a node on the trie at route // and return a node // str -> obj Trie.prototype.create = function (route) { assert.equal(typeof route, 'string', 'route should be a string') // strip leading '/' and split routes var routes = route.replace(/^\//, '').split('/') function createNode (index, trie) { var thisRoute = (has(routes, index) && routes[index]) if (thisRoute === false) return trie var node = null if (/^:|^\*/.test(thisRoute)) { // if node is a name match, set name and append to ':' node if (!has(trie.nodes, '$$')) { node = { nodes: {} } trie.nodes.$$ = node } else { node = trie.nodes.$$ } if (thisRoute[0] === '*') { trie.wildcard = true } trie.name = thisRoute.replace(/^:|^\*/, '') } else if (!has(trie.nodes, thisRoute)) { node = { nodes: {} } trie.nodes[thisRoute] = node } else { node = trie.nodes[thisRoute] } // we must recurse deeper return createNode(index + 1, node) } return createNode(0, this.trie) } // match a route on the trie // and return the node // str -> obj Trie.prototype.match = function (route) { assert.equal(typeof route, 'string', 'route should be a string') var routes = route.replace(/^\//, '').split('/') var params = {} function search (index, trie) { // either there's no match, or we're done searching if (trie === undefined) return undefined var thisRoute = routes[index] if (thisRoute === undefined) return trie if (has(trie.nodes, thisRoute)) { // match regular routes first return search(index + 1, trie.nodes[thisRoute]) } else if (trie.name) { // match named routes try { params[trie.name] = decodeURIComponent(thisRoute) } catch (e) { return search(index, undefined) } return search(index + 1, trie.nodes.$$) } else if (trie.wildcard) { // match wildcards try { params.wildcard = decodeURIComponent(routes.slice(index).join('/')) } catch (e) { return search(index, undefined) } // return early, or else search may keep recursing through the wildcard return trie.nodes.$$ } else { // no matches found return search(index + 1) } } var node = search(0, this.trie) if (!node) return undefined node = Object.assign({}, node) node.params = params return node } // mount a trie onto a node at route // (str, obj) -> null Trie.prototype.mount = function (route, trie) { assert.equal(typeof route, 'string', 'route should be a string') assert.equal(typeof trie, 'object', 'trie should be a object') var split = route.replace(/^\//, '').split('/') var node = null var key = null if (split.length === 1) { key = split[0] node = this.create(key) } else { var head = split.join('/') key = split[0] node = this.create(head) } Object.assign(node.nodes, trie.nodes) if (trie.name) node.name = trie.name // delegate properties from '/' to the new node // '/' cannot be reached once mounted if (node.nodes['']) { Object.keys(node.nodes['']).forEach(function (key) { if (key === 'nodes') return node[key] = node.nodes[''][key] }) Object.assign(node.nodes, node.nodes[''].nodes) delete node.nodes[''].nodes } } function has (object, property) { return Object.prototype.hasOwnProperty.call(object, property) }