UNPKG

find-my-way

Version:

Crazy fast http radix based router

229 lines (185 loc) 5.58 kB
'use strict' const HandlerStorage = require('./handler-storage') const NODE_TYPES = { STATIC: 0, PARAMETRIC: 1, WILDCARD: 2 } class Node { constructor () { this.isLeafNode = false this.routes = null this.handlerStorage = null } addRoute (route, constrainer) { if (this.routes === null) { this.routes = [] } if (this.handlerStorage === null) { this.handlerStorage = new HandlerStorage() } this.isLeafNode = true this.routes.push(route) this.handlerStorage.addHandler(constrainer, route) } } class ParentNode extends Node { constructor () { super() this.staticChildren = {} } findStaticMatchingChild (path, pathIndex) { const staticChild = this.staticChildren[path.charAt(pathIndex)] if (staticChild === undefined || !staticChild.matchPrefix(path, pathIndex)) { return null } return staticChild } getStaticChild (path, pathIndex = 0) { if (path.length === pathIndex) { return this } const staticChild = this.findStaticMatchingChild(path, pathIndex) if (staticChild) { return staticChild.getStaticChild(path, pathIndex + staticChild.prefix.length) } return null } createStaticChild (path) { if (path.length === 0) { return this } let staticChild = this.staticChildren[path.charAt(0)] if (staticChild) { let i = 1 for (; i < staticChild.prefix.length; i++) { if (path.charCodeAt(i) !== staticChild.prefix.charCodeAt(i)) { staticChild = staticChild.split(this, i) break } } return staticChild.createStaticChild(path.slice(i)) } const label = path.charAt(0) this.staticChildren[label] = new StaticNode(path) return this.staticChildren[label] } } class StaticNode extends ParentNode { constructor (prefix) { super() this.prefix = prefix this.wildcardChild = null this.parametricChildren = [] this.kind = NODE_TYPES.STATIC this._compilePrefixMatch() } getParametricChild (regex) { const regexpSource = regex && regex.source const parametricChild = this.parametricChildren.find(child => { const childRegexSource = child.regex && child.regex.source return childRegexSource === regexpSource }) if (parametricChild) { return parametricChild } return null } createParametricChild (regex, staticSuffix, nodePath) { let parametricChild = this.getParametricChild(regex) if (parametricChild) { parametricChild.nodePaths.add(nodePath) return parametricChild } parametricChild = new ParametricNode(regex, staticSuffix, nodePath) this.parametricChildren.push(parametricChild) this.parametricChildren.sort((child1, child2) => { if (!child1.isRegex) return 1 if (!child2.isRegex) return -1 if (child1.staticSuffix === null) return 1 if (child2.staticSuffix === null) return -1 if (child2.staticSuffix.endsWith(child1.staticSuffix)) return 1 if (child1.staticSuffix.endsWith(child2.staticSuffix)) return -1 return 0 }) return parametricChild } getWildcardChild () { return this.wildcardChild } createWildcardChild () { this.wildcardChild = this.getWildcardChild() || new WildcardNode() return this.wildcardChild } split (parentNode, length) { const parentPrefix = this.prefix.slice(0, length) const childPrefix = this.prefix.slice(length) this.prefix = childPrefix this._compilePrefixMatch() const staticNode = new StaticNode(parentPrefix) staticNode.staticChildren[childPrefix.charAt(0)] = this parentNode.staticChildren[parentPrefix.charAt(0)] = staticNode return staticNode } getNextNode (path, pathIndex, nodeStack, paramsCount) { let node = this.findStaticMatchingChild(path, pathIndex) let parametricBrotherNodeIndex = 0 if (node === null) { if (this.parametricChildren.length === 0) { return this.wildcardChild } node = this.parametricChildren[0] parametricBrotherNodeIndex = 1 } if (this.wildcardChild !== null) { nodeStack.push({ paramsCount, brotherPathIndex: pathIndex, brotherNode: this.wildcardChild }) } for (let i = this.parametricChildren.length - 1; i >= parametricBrotherNodeIndex; i--) { nodeStack.push({ paramsCount, brotherPathIndex: pathIndex, brotherNode: this.parametricChildren[i] }) } return node } _compilePrefixMatch () { if (this.prefix.length === 1) { this.matchPrefix = () => true return } const lines = [] for (let i = 1; i < this.prefix.length; i++) { const charCode = this.prefix.charCodeAt(i) lines.push(`path.charCodeAt(i + ${i}) === ${charCode}`) } this.matchPrefix = new Function('path', 'i', `return ${lines.join(' && ')}`) // eslint-disable-line } } class ParametricNode extends ParentNode { constructor (regex, staticSuffix, nodePath) { super() this.isRegex = !!regex this.regex = regex || null this.staticSuffix = staticSuffix || null this.kind = NODE_TYPES.PARAMETRIC this.nodePaths = new Set([nodePath]) } getNextNode (path, pathIndex) { return this.findStaticMatchingChild(path, pathIndex) } } class WildcardNode extends Node { constructor () { super() this.kind = NODE_TYPES.WILDCARD } getNextNode () { return null } } module.exports = { StaticNode, ParametricNode, WildcardNode, NODE_TYPES }