UNPKG

hurt

Version:

HTTP and SPA routing using RFC 6570 URI templates

140 lines (123 loc) 3.12 kB
import { add, match } from './trie'; import handler from './handler'; import uriTemplate from 'uri-templates'; function templatePrefix(template) { const index = template.indexOf('{'); const prefix = index < 0 ? template : template.substr(0, index); return prefix; } function templateParams(template) { const t = uriTemplate(template); return url => { const match = t.fromUri(url); return match; }; } function regExpPrefix(regexp) { const source = regexp.source.replace(/(^\^|\$$)/g, ''); const match = /(^|[^\\])([[({.]|\\[dDsSwWxu])/.exec(source); const prefix = match ? source.substr(0, match.index + match[1].length) : source; return prefix.replace(/\\([/\\().*\[\]])/g, '$1'); } function regExpParams(regexp) { return url => { const match = regexp.exec(url); return match && (match[0] === url) && match; }; } function routeHandler(params, fn) { return function (req, ...args) { const next = args.pop(); const match = params(req.url); if (match) { req.params = { ...req.params, ...match }; fn.call(this, req, ...args, function (err) { if (!err) { req.handled = true; } next(err); }); } else { next(); } }; } export function mixin({ base = '' } = {}) { const EMPTY = {}; const tries = [EMPTY]; const notfound = []; const handle = handler(notfound); return { post: [ function (req, ...args) { if (!req.handled) { handle.call(this, req, ...args); } else { const next = args[ args.length - 1 ]; next(); } } ], base(url) { if (typeof url === 'string') { base = url; return this; } return base; }, use(options, ...stack) { let url; let prefix; let params; if (typeof options === 'string' || options instanceof RegExp) { url = options; options = { url }; } else { url = options.url; } if (typeof url === 'string') { prefix = templatePrefix(url); params = templateParams(url); } else if (url instanceof RegExp) { prefix = regExpPrefix(url); params = regExpParams(url); } else { if (tries[tries.length - 1] !== EMPTY) { tries.push(EMPTY); } return this.use(options, ...stack); } const route = this.route(options, ...stack) const trie = tries.pop(); const index = tries.length; tries.push(add(trie, prefix, [ routeHandler(params, route) ])); if (trie === EMPTY) { return this.use(function (req, ...args) { const stack = match(tries[ index ], req.url, []); if (stack.length) { handler(stack).call(this, req, ...args); } else { const next = args[ args.length - 1 ]; next(); } }); } else { return this; } }, notfound(...stack) { notfound.push(...stack); return this; } }; }