tiny-request-router
Advanced tools
Comparing version 1.1.2 to 1.2.2
@@ -34,346 +34,279 @@ (function (global, factory) { | ||
/** | ||
* Expose `pathToRegexp`. | ||
* Tokenize input string. | ||
*/ | ||
var pathToRegexp_1 = pathToRegexp; | ||
var parse_1 = parse; | ||
var compile_1 = compile; | ||
var tokensToFunction_1 = tokensToFunction; | ||
var tokensToRegExp_1 = tokensToRegExp; | ||
/** | ||
* Default configs. | ||
*/ | ||
var DEFAULT_DELIMITER = '/'; | ||
/** | ||
* The main path matching regexp utility. | ||
* | ||
* @type {RegExp} | ||
*/ | ||
var PATH_REGEXP = new RegExp([ | ||
// Match escaped characters that would otherwise appear in future matches. | ||
// This allows the user to escape special characters that won't transform. | ||
'(\\\\.)', | ||
// Match Express-style parameters and un-named parameters with a prefix | ||
// and optional suffixes. Matches appear as: | ||
// | ||
// ":test(\\d+)?" => ["test", "\d+", undefined, "?"] | ||
// "(\\d+)" => [undefined, undefined, "\d+", undefined] | ||
'(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?' | ||
].join('|'), 'g'); | ||
/** | ||
* Parse a string for the raw tokens. | ||
* | ||
* @param {string} str | ||
* @param {Object=} options | ||
* @return {!Array} | ||
*/ | ||
function parse (str, options) { | ||
var tokens = []; | ||
var key = 0; | ||
var index = 0; | ||
var path = ''; | ||
var defaultDelimiter = (options && options.delimiter) || DEFAULT_DELIMITER; | ||
var whitelist = (options && options.whitelist) || undefined; | ||
var pathEscaped = false; | ||
var res; | ||
while ((res = PATH_REGEXP.exec(str)) !== null) { | ||
var m = res[0]; | ||
var escaped = res[1]; | ||
var offset = res.index; | ||
path += str.slice(index, offset); | ||
index = offset + m.length; | ||
// Ignore already escaped sequences. | ||
if (escaped) { | ||
path += escaped[1]; | ||
pathEscaped = true; | ||
continue | ||
function lexer(str) { | ||
var tokens = []; | ||
var i = 0; | ||
while (i < str.length) { | ||
var char = str[i]; | ||
if (char === "*" || char === "+" || char === "?") { | ||
tokens.push({ type: "MODIFIER", index: i, value: str[i++] }); | ||
continue; | ||
} | ||
if (char === "\\") { | ||
tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] }); | ||
continue; | ||
} | ||
if (char === "{") { | ||
tokens.push({ type: "OPEN", index: i, value: str[i++] }); | ||
continue; | ||
} | ||
if (char === "}") { | ||
tokens.push({ type: "CLOSE", index: i, value: str[i++] }); | ||
continue; | ||
} | ||
if (char === ":") { | ||
var name = ""; | ||
var j = i + 1; | ||
while (j < str.length) { | ||
var code = str.charCodeAt(j); | ||
if ( | ||
// `0-9` | ||
(code >= 48 && code <= 57) || | ||
// `A-Z` | ||
(code >= 65 && code <= 90) || | ||
// `a-z` | ||
(code >= 97 && code <= 122) || | ||
// `_` | ||
code === 95) { | ||
name += str[j++]; | ||
continue; | ||
} | ||
break; | ||
} | ||
if (!name) | ||
throw new TypeError("Missing parameter name at " + i); | ||
tokens.push({ type: "NAME", index: i, value: name }); | ||
i = j; | ||
continue; | ||
} | ||
if (char === "(") { | ||
var count = 1; | ||
var pattern = ""; | ||
var j = i + 1; | ||
if (str[j] === "?") { | ||
throw new TypeError("Pattern cannot start with \"?\" at " + j); | ||
} | ||
while (j < str.length) { | ||
if (str[j] === "\\") { | ||
pattern += str[j++] + str[j++]; | ||
continue; | ||
} | ||
if (str[j] === ")") { | ||
count--; | ||
if (count === 0) { | ||
j++; | ||
break; | ||
} | ||
} | ||
else if (str[j] === "(") { | ||
count++; | ||
if (str[j + 1] !== "?") { | ||
throw new TypeError("Capturing groups are not allowed at " + j); | ||
} | ||
} | ||
pattern += str[j++]; | ||
} | ||
if (count) | ||
throw new TypeError("Unbalanced pattern at " + i); | ||
if (!pattern) | ||
throw new TypeError("Missing pattern at " + i); | ||
tokens.push({ type: "PATTERN", index: i, value: pattern }); | ||
i = j; | ||
continue; | ||
} | ||
tokens.push({ type: "CHAR", index: i, value: str[i++] }); | ||
} | ||
var prev = ''; | ||
var name = res[2]; | ||
var capture = res[3]; | ||
var group = res[4]; | ||
var modifier = res[5]; | ||
if (!pathEscaped && path.length) { | ||
var k = path.length - 1; | ||
var c = path[k]; | ||
var matches = whitelist ? whitelist.indexOf(c) > -1 : true; | ||
if (matches) { | ||
prev = c; | ||
path = path.slice(0, k); | ||
} | ||
} | ||
// Push the current path onto the tokens. | ||
if (path) { | ||
tokens.push(path); | ||
path = ''; | ||
pathEscaped = false; | ||
} | ||
var repeat = modifier === '+' || modifier === '*'; | ||
var optional = modifier === '?' || modifier === '*'; | ||
var pattern = capture || group; | ||
var delimiter = prev || defaultDelimiter; | ||
tokens.push({ | ||
name: name || key++, | ||
prefix: prev, | ||
delimiter: delimiter, | ||
optional: optional, | ||
repeat: repeat, | ||
pattern: pattern | ||
? escapeGroup(pattern) | ||
: '[^' + escapeString(delimiter === defaultDelimiter ? delimiter : (delimiter + defaultDelimiter)) + ']+?' | ||
}); | ||
} | ||
// Push any remaining characters. | ||
if (path || index < str.length) { | ||
tokens.push(path + str.substr(index)); | ||
} | ||
return tokens | ||
tokens.push({ type: "END", index: i, value: "" }); | ||
return tokens; | ||
} | ||
/** | ||
* Compile a string to a template function for the path. | ||
* | ||
* @param {string} str | ||
* @param {Object=} options | ||
* @return {!function(Object=, Object=)} | ||
* Parse a string for the raw tokens. | ||
*/ | ||
function compile (str, options) { | ||
return tokensToFunction(parse(str, options), options) | ||
} | ||
/** | ||
* Expose a method for transforming tokens into the path function. | ||
*/ | ||
function tokensToFunction (tokens, options) { | ||
// Compile all the tokens into regexps. | ||
var matches = new Array(tokens.length); | ||
// Compile all the patterns before compilation. | ||
for (var i = 0; i < tokens.length; i++) { | ||
if (typeof tokens[i] === 'object') { | ||
matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$', flags(options)); | ||
} | ||
} | ||
return function (data, options) { | ||
var path = ''; | ||
var encode = (options && options.encode) || encodeURIComponent; | ||
var validate = options ? options.validate !== false : true; | ||
for (var i = 0; i < tokens.length; i++) { | ||
var token = tokens[i]; | ||
if (typeof token === 'string') { | ||
path += token; | ||
continue | ||
} | ||
var value = data ? data[token.name] : undefined; | ||
var segment; | ||
if (Array.isArray(value)) { | ||
if (!token.repeat) { | ||
throw new TypeError('Expected "' + token.name + '" to not repeat, but got array') | ||
function parse(str, options) { | ||
if (options === void 0) { options = {}; } | ||
var tokens = lexer(str); | ||
var _a = options.prefixes, prefixes = _a === void 0 ? "./" : _a; | ||
var defaultPattern = "[^" + escapeString(options.delimiter || "/#?") + "]+?"; | ||
var result = []; | ||
var key = 0; | ||
var i = 0; | ||
var path = ""; | ||
var tryConsume = function (type) { | ||
if (i < tokens.length && tokens[i].type === type) | ||
return tokens[i++].value; | ||
}; | ||
var mustConsume = function (type) { | ||
var value = tryConsume(type); | ||
if (value !== undefined) | ||
return value; | ||
var _a = tokens[i], nextType = _a.type, index = _a.index; | ||
throw new TypeError("Unexpected " + nextType + " at " + index + ", expected " + type); | ||
}; | ||
var consumeText = function () { | ||
var result = ""; | ||
var value; | ||
// tslint:disable-next-line | ||
while ((value = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR"))) { | ||
result += value; | ||
} | ||
if (value.length === 0) { | ||
if (token.optional) continue | ||
throw new TypeError('Expected "' + token.name + '" to not be empty') | ||
return result; | ||
}; | ||
while (i < tokens.length) { | ||
var char = tryConsume("CHAR"); | ||
var name = tryConsume("NAME"); | ||
var pattern = tryConsume("PATTERN"); | ||
if (name || pattern) { | ||
var prefix = char || ""; | ||
if (prefixes.indexOf(prefix) === -1) { | ||
path += prefix; | ||
prefix = ""; | ||
} | ||
if (path) { | ||
result.push(path); | ||
path = ""; | ||
} | ||
result.push({ | ||
name: name || key++, | ||
prefix: prefix, | ||
suffix: "", | ||
pattern: pattern || defaultPattern, | ||
modifier: tryConsume("MODIFIER") || "" | ||
}); | ||
continue; | ||
} | ||
for (var j = 0; j < value.length; j++) { | ||
segment = encode(value[j], token); | ||
if (validate && !matches[i].test(segment)) { | ||
throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '"') | ||
} | ||
path += (j === 0 ? token.prefix : token.delimiter) + segment; | ||
var value = char || tryConsume("ESCAPED_CHAR"); | ||
if (value) { | ||
path += value; | ||
continue; | ||
} | ||
continue | ||
} | ||
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { | ||
segment = encode(String(value), token); | ||
if (validate && !matches[i].test(segment)) { | ||
throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but got "' + segment + '"') | ||
if (path) { | ||
result.push(path); | ||
path = ""; | ||
} | ||
path += token.prefix + segment; | ||
continue | ||
} | ||
if (token.optional) continue | ||
throw new TypeError('Expected "' + token.name + '" to be ' + (token.repeat ? 'an array' : 'a string')) | ||
var open = tryConsume("OPEN"); | ||
if (open) { | ||
var prefix = consumeText(); | ||
var name_1 = tryConsume("NAME") || ""; | ||
var pattern_1 = tryConsume("PATTERN") || ""; | ||
var suffix = consumeText(); | ||
mustConsume("CLOSE"); | ||
result.push({ | ||
name: name_1 || (pattern_1 ? key++ : ""), | ||
pattern: name_1 && !pattern_1 ? defaultPattern : pattern_1, | ||
prefix: prefix, | ||
suffix: suffix, | ||
modifier: tryConsume("MODIFIER") || "" | ||
}); | ||
continue; | ||
} | ||
mustConsume("END"); | ||
} | ||
return path | ||
} | ||
return result; | ||
} | ||
/** | ||
* Escape a regular expression string. | ||
* | ||
* @param {string} str | ||
* @return {string} | ||
*/ | ||
function escapeString (str) { | ||
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1') | ||
function escapeString(str) { | ||
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1"); | ||
} | ||
/** | ||
* Escape the capturing group by escaping special characters and meaning. | ||
* | ||
* @param {string} group | ||
* @return {string} | ||
*/ | ||
function escapeGroup (group) { | ||
return group.replace(/([=!:$/()])/g, '\\$1') | ||
} | ||
/** | ||
* Get the flags for a regexp from the options. | ||
* | ||
* @param {Object} options | ||
* @return {string} | ||
*/ | ||
function flags (options) { | ||
return options && options.sensitive ? '' : 'i' | ||
function flags(options) { | ||
return options && options.sensitive ? "" : "i"; | ||
} | ||
/** | ||
* Pull out keys from a regexp. | ||
* | ||
* @param {!RegExp} path | ||
* @param {Array=} keys | ||
* @return {!RegExp} | ||
*/ | ||
function regexpToRegexp (path, keys) { | ||
if (!keys) return path | ||
// Use a negative lookahead to match only capturing groups. | ||
var groups = path.source.match(/\((?!\?)/g); | ||
if (groups) { | ||
for (var i = 0; i < groups.length; i++) { | ||
keys.push({ | ||
name: i, | ||
prefix: null, | ||
delimiter: null, | ||
optional: false, | ||
repeat: false, | ||
pattern: null | ||
}); | ||
function regexpToRegexp(path, keys) { | ||
if (!keys) | ||
return path; | ||
// Use a negative lookahead to match only capturing groups. | ||
var groups = path.source.match(/\((?!\?)/g); | ||
if (groups) { | ||
for (var i = 0; i < groups.length; i++) { | ||
keys.push({ | ||
name: i, | ||
prefix: "", | ||
suffix: "", | ||
modifier: "", | ||
pattern: "" | ||
}); | ||
} | ||
} | ||
} | ||
return path | ||
return path; | ||
} | ||
/** | ||
* Transform an array into a regexp. | ||
* | ||
* @param {!Array} path | ||
* @param {Array=} keys | ||
* @param {Object=} options | ||
* @return {!RegExp} | ||
*/ | ||
function arrayToRegexp (path, keys, options) { | ||
var parts = []; | ||
for (var i = 0; i < path.length; i++) { | ||
parts.push(pathToRegexp(path[i], keys, options).source); | ||
} | ||
return new RegExp('(?:' + parts.join('|') + ')', flags(options)) | ||
function arrayToRegexp(paths, keys, options) { | ||
var parts = paths.map(function (path) { return pathToRegexp(path, keys, options).source; }); | ||
return new RegExp("(?:" + parts.join("|") + ")", flags(options)); | ||
} | ||
/** | ||
* Create a path regexp from string input. | ||
* | ||
* @param {string} path | ||
* @param {Array=} keys | ||
* @param {Object=} options | ||
* @return {!RegExp} | ||
*/ | ||
function stringToRegexp (path, keys, options) { | ||
return tokensToRegExp(parse(path, options), keys, options) | ||
function stringToRegexp(path, keys, options) { | ||
return tokensToRegexp(parse(path, options), keys, options); | ||
} | ||
/** | ||
* Expose a function for taking tokens and returning a RegExp. | ||
* | ||
* @param {!Array} tokens | ||
* @param {Array=} keys | ||
* @param {Object=} options | ||
* @return {!RegExp} | ||
*/ | ||
function tokensToRegExp (tokens, keys, options) { | ||
options = options || {}; | ||
var strict = options.strict; | ||
var start = options.start !== false; | ||
var end = options.end !== false; | ||
var delimiter = options.delimiter || DEFAULT_DELIMITER; | ||
var endsWith = [].concat(options.endsWith || []).map(escapeString).concat('$').join('|'); | ||
var route = start ? '^' : ''; | ||
// Iterate over the tokens and create our regexp string. | ||
for (var i = 0; i < tokens.length; i++) { | ||
var token = tokens[i]; | ||
if (typeof token === 'string') { | ||
route += escapeString(token); | ||
} else { | ||
var capture = token.repeat | ||
? '(?:' + token.pattern + ')(?:' + escapeString(token.delimiter) + '(?:' + token.pattern + '))*' | ||
: token.pattern; | ||
if (keys) keys.push(token); | ||
if (token.optional) { | ||
if (!token.prefix) { | ||
route += '(' + capture + ')?'; | ||
} else { | ||
route += '(?:' + escapeString(token.prefix) + '(' + capture + '))?'; | ||
function tokensToRegexp(tokens, keys, options) { | ||
if (options === void 0) { options = {}; } | ||
var _a = options.strict, strict = _a === void 0 ? false : _a, _b = options.start, start = _b === void 0 ? true : _b, _c = options.end, end = _c === void 0 ? true : _c, _d = options.encode, encode = _d === void 0 ? function (x) { return x; } : _d; | ||
var endsWith = "[" + escapeString(options.endsWith || "") + "]|$"; | ||
var delimiter = "[" + escapeString(options.delimiter || "/#?") + "]"; | ||
var route = start ? "^" : ""; | ||
// Iterate over the tokens and create our regexp string. | ||
for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) { | ||
var token = tokens_1[_i]; | ||
if (typeof token === "string") { | ||
route += escapeString(encode(token)); | ||
} | ||
} else { | ||
route += escapeString(token.prefix) + '(' + capture + ')'; | ||
} | ||
else { | ||
var prefix = escapeString(encode(token.prefix)); | ||
var suffix = escapeString(encode(token.suffix)); | ||
if (token.pattern) { | ||
if (keys) | ||
keys.push(token); | ||
if (prefix || suffix) { | ||
if (token.modifier === "+" || token.modifier === "*") { | ||
var mod = token.modifier === "*" ? "?" : ""; | ||
route += "(?:" + prefix + "((?:" + token.pattern + ")(?:" + suffix + prefix + "(?:" + token.pattern + "))*)" + suffix + ")" + mod; | ||
} | ||
else { | ||
route += "(?:" + prefix + "(" + token.pattern + ")" + suffix + ")" + token.modifier; | ||
} | ||
} | ||
else { | ||
route += "(" + token.pattern + ")" + token.modifier; | ||
} | ||
} | ||
else { | ||
route += "(?:" + prefix + suffix + ")" + token.modifier; | ||
} | ||
} | ||
} | ||
} | ||
if (end) { | ||
if (!strict) route += '(?:' + escapeString(delimiter) + ')?'; | ||
route += endsWith === '$' ? '$' : '(?=' + endsWith + ')'; | ||
} else { | ||
var endToken = tokens[tokens.length - 1]; | ||
var isEndDelimited = typeof endToken === 'string' | ||
? endToken[endToken.length - 1] === delimiter | ||
: endToken === undefined; | ||
if (!strict) route += '(?:' + escapeString(delimiter) + '(?=' + endsWith + '))?'; | ||
if (!isEndDelimited) route += '(?=' + escapeString(delimiter) + '|' + endsWith + ')'; | ||
} | ||
return new RegExp(route, flags(options)) | ||
if (end) { | ||
if (!strict) | ||
route += delimiter + "?"; | ||
route += !options.endsWith ? "$" : "(?=" + endsWith + ")"; | ||
} | ||
else { | ||
var endToken = tokens[tokens.length - 1]; | ||
var isEndDelimited = typeof endToken === "string" | ||
? delimiter.indexOf(endToken[endToken.length - 1]) > -1 | ||
: // tslint:disable-next-line | ||
endToken === undefined; | ||
if (!strict) { | ||
route += "(?:" + delimiter + "(?=" + endsWith + "))?"; | ||
} | ||
if (!isEndDelimited) { | ||
route += "(?=" + delimiter + "|" + endsWith + ")"; | ||
} | ||
} | ||
return new RegExp(route, flags(options)); | ||
} | ||
/** | ||
@@ -385,28 +318,28 @@ * Normalize the given path string, returning a regular expression. | ||
* contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. | ||
* | ||
* @param {(string|RegExp|Array)} path | ||
* @param {Array=} keys | ||
* @param {Object=} options | ||
* @return {!RegExp} | ||
*/ | ||
function pathToRegexp (path, keys, options) { | ||
if (path instanceof RegExp) { | ||
return regexpToRegexp(path, keys) | ||
} | ||
if (Array.isArray(path)) { | ||
return arrayToRegexp(/** @type {!Array} */ (path), keys, options) | ||
} | ||
return stringToRegexp(/** @type {string} */ (path), keys, options) | ||
function pathToRegexp(path, keys, options) { | ||
if (path instanceof RegExp) | ||
return regexpToRegexp(path, keys); | ||
if (Array.isArray(path)) | ||
return arrayToRegexp(path, keys, options); | ||
return stringToRegexp(path, keys, options); | ||
} | ||
pathToRegexp_1.parse = parse_1; | ||
pathToRegexp_1.compile = compile_1; | ||
pathToRegexp_1.tokensToFunction = tokensToFunction_1; | ||
pathToRegexp_1.tokensToRegExp = tokensToRegExp_1; | ||
/** | ||
* Tiny request router. Allows overloading of handler type to be fully type safe. | ||
* | ||
* @example | ||
* import { Router, Method, Params } from 'tiny-request-router' | ||
* | ||
* // Let the router know that handlers are async functions returning a Response | ||
* type Handler = (params: Params) => Promise<Response> | ||
* | ||
* const router = new Router<Handler>() | ||
*/ | ||
var Router = /** @class */ (function () { | ||
function Router() { | ||
/** List of all registered routes. */ | ||
this.routes = []; | ||
} | ||
/** Add a route that matches any method. */ | ||
Router.prototype.all = function (path, handler, options) { | ||
@@ -416,2 +349,3 @@ if (options === void 0) { options = {}; } | ||
}; | ||
/** Add a route that matches the GET method. */ | ||
Router.prototype.get = function (path, handler, options) { | ||
@@ -421,2 +355,3 @@ if (options === void 0) { options = {}; } | ||
}; | ||
/** Add a route that matches the POST method. */ | ||
Router.prototype.post = function (path, handler, options) { | ||
@@ -426,2 +361,3 @@ if (options === void 0) { options = {}; } | ||
}; | ||
/** Add a route that matches the PUT method. */ | ||
Router.prototype.put = function (path, handler, options) { | ||
@@ -431,2 +367,3 @@ if (options === void 0) { options = {}; } | ||
}; | ||
/** Add a route that matches the PATCH method. */ | ||
Router.prototype.patch = function (path, handler, options) { | ||
@@ -436,2 +373,3 @@ if (options === void 0) { options = {}; } | ||
}; | ||
/** Add a route that matches the DELETE method. */ | ||
Router.prototype["delete"] = function (path, handler, options) { | ||
@@ -441,2 +379,3 @@ if (options === void 0) { options = {}; } | ||
}; | ||
/** Add a route that matches the HEAD method. */ | ||
Router.prototype.head = function (path, handler, options) { | ||
@@ -446,2 +385,3 @@ if (options === void 0) { options = {}; } | ||
}; | ||
/** Add a route that matches the OPTIONS method. */ | ||
Router.prototype.options = function (path, handler, options) { | ||
@@ -451,2 +391,15 @@ if (options === void 0) { options = {}; } | ||
}; | ||
/** | ||
* Match the provided method and path against the list of registered routes. | ||
* | ||
* @example | ||
* router.get('/foobar', async () => new Response('Hello')) | ||
* | ||
* const match = router.match('GET', '/foobar') | ||
* if (match) { | ||
* // Call the async function of that match | ||
* const response = await match.handler() | ||
* console.log(response) // => Response('Hello') | ||
* } | ||
*/ | ||
Router.prototype.match = function (method, path) { | ||
@@ -459,3 +412,3 @@ for (var _i = 0, _a = this.routes; _i < _a.length; _i++) { | ||
// Speed optimizations for catch all wildcard routes | ||
if (route.path === '*' || route.path === '(.*)') { | ||
if (route.path === '(.*)') { | ||
return __assign(__assign({}, route), { params: { '0': route.path } }); | ||
@@ -476,3 +429,6 @@ } | ||
var keys = []; | ||
var regexp = pathToRegexp_1(path, keys, options); | ||
if (path === '*') { | ||
path = '(.*)'; | ||
} | ||
var regexp = pathToRegexp(path, keys, options); | ||
this.routes.push({ method: method, path: path, handler: handler, keys: keys, options: options, regexp: regexp }); | ||
@@ -498,3 +454,3 @@ return this; | ||
exports.Router = Router; | ||
exports.pathToRegexp = pathToRegexp_1; | ||
exports.pathToRegexp = pathToRegexp; | ||
@@ -501,0 +457,0 @@ Object.defineProperty(exports, '__esModule', { value: true }); |
/*! | ||
* tiny-request-router v1.1.2 by berstend | ||
* tiny-request-router v1.2.2 by berstend | ||
* https://github.com/berstend/tiny-request-router#readme | ||
@@ -37,346 +37,279 @@ * @license MIT | ||
/** | ||
* Expose `pathToRegexp`. | ||
* Tokenize input string. | ||
*/ | ||
var pathToRegexp_1 = pathToRegexp; | ||
var parse_1 = parse; | ||
var compile_1 = compile; | ||
var tokensToFunction_1 = tokensToFunction; | ||
var tokensToRegExp_1 = tokensToRegExp; | ||
/** | ||
* Default configs. | ||
*/ | ||
var DEFAULT_DELIMITER = '/'; | ||
/** | ||
* The main path matching regexp utility. | ||
* | ||
* @type {RegExp} | ||
*/ | ||
var PATH_REGEXP = new RegExp([ | ||
// Match escaped characters that would otherwise appear in future matches. | ||
// This allows the user to escape special characters that won't transform. | ||
'(\\\\.)', | ||
// Match Express-style parameters and un-named parameters with a prefix | ||
// and optional suffixes. Matches appear as: | ||
// | ||
// ":test(\\d+)?" => ["test", "\d+", undefined, "?"] | ||
// "(\\d+)" => [undefined, undefined, "\d+", undefined] | ||
'(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?' | ||
].join('|'), 'g'); | ||
/** | ||
* Parse a string for the raw tokens. | ||
* | ||
* @param {string} str | ||
* @param {Object=} options | ||
* @return {!Array} | ||
*/ | ||
function parse (str, options) { | ||
var tokens = []; | ||
var key = 0; | ||
var index = 0; | ||
var path = ''; | ||
var defaultDelimiter = (options && options.delimiter) || DEFAULT_DELIMITER; | ||
var whitelist = (options && options.whitelist) || undefined; | ||
var pathEscaped = false; | ||
var res; | ||
while ((res = PATH_REGEXP.exec(str)) !== null) { | ||
var m = res[0]; | ||
var escaped = res[1]; | ||
var offset = res.index; | ||
path += str.slice(index, offset); | ||
index = offset + m.length; | ||
// Ignore already escaped sequences. | ||
if (escaped) { | ||
path += escaped[1]; | ||
pathEscaped = true; | ||
continue | ||
function lexer(str) { | ||
var tokens = []; | ||
var i = 0; | ||
while (i < str.length) { | ||
var char = str[i]; | ||
if (char === "*" || char === "+" || char === "?") { | ||
tokens.push({ type: "MODIFIER", index: i, value: str[i++] }); | ||
continue; | ||
} | ||
if (char === "\\") { | ||
tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] }); | ||
continue; | ||
} | ||
if (char === "{") { | ||
tokens.push({ type: "OPEN", index: i, value: str[i++] }); | ||
continue; | ||
} | ||
if (char === "}") { | ||
tokens.push({ type: "CLOSE", index: i, value: str[i++] }); | ||
continue; | ||
} | ||
if (char === ":") { | ||
var name = ""; | ||
var j = i + 1; | ||
while (j < str.length) { | ||
var code = str.charCodeAt(j); | ||
if ( | ||
// `0-9` | ||
(code >= 48 && code <= 57) || | ||
// `A-Z` | ||
(code >= 65 && code <= 90) || | ||
// `a-z` | ||
(code >= 97 && code <= 122) || | ||
// `_` | ||
code === 95) { | ||
name += str[j++]; | ||
continue; | ||
} | ||
break; | ||
} | ||
if (!name) | ||
throw new TypeError("Missing parameter name at " + i); | ||
tokens.push({ type: "NAME", index: i, value: name }); | ||
i = j; | ||
continue; | ||
} | ||
if (char === "(") { | ||
var count = 1; | ||
var pattern = ""; | ||
var j = i + 1; | ||
if (str[j] === "?") { | ||
throw new TypeError("Pattern cannot start with \"?\" at " + j); | ||
} | ||
while (j < str.length) { | ||
if (str[j] === "\\") { | ||
pattern += str[j++] + str[j++]; | ||
continue; | ||
} | ||
if (str[j] === ")") { | ||
count--; | ||
if (count === 0) { | ||
j++; | ||
break; | ||
} | ||
} | ||
else if (str[j] === "(") { | ||
count++; | ||
if (str[j + 1] !== "?") { | ||
throw new TypeError("Capturing groups are not allowed at " + j); | ||
} | ||
} | ||
pattern += str[j++]; | ||
} | ||
if (count) | ||
throw new TypeError("Unbalanced pattern at " + i); | ||
if (!pattern) | ||
throw new TypeError("Missing pattern at " + i); | ||
tokens.push({ type: "PATTERN", index: i, value: pattern }); | ||
i = j; | ||
continue; | ||
} | ||
tokens.push({ type: "CHAR", index: i, value: str[i++] }); | ||
} | ||
var prev = ''; | ||
var name = res[2]; | ||
var capture = res[3]; | ||
var group = res[4]; | ||
var modifier = res[5]; | ||
if (!pathEscaped && path.length) { | ||
var k = path.length - 1; | ||
var c = path[k]; | ||
var matches = whitelist ? whitelist.indexOf(c) > -1 : true; | ||
if (matches) { | ||
prev = c; | ||
path = path.slice(0, k); | ||
} | ||
} | ||
// Push the current path onto the tokens. | ||
if (path) { | ||
tokens.push(path); | ||
path = ''; | ||
pathEscaped = false; | ||
} | ||
var repeat = modifier === '+' || modifier === '*'; | ||
var optional = modifier === '?' || modifier === '*'; | ||
var pattern = capture || group; | ||
var delimiter = prev || defaultDelimiter; | ||
tokens.push({ | ||
name: name || key++, | ||
prefix: prev, | ||
delimiter: delimiter, | ||
optional: optional, | ||
repeat: repeat, | ||
pattern: pattern | ||
? escapeGroup(pattern) | ||
: '[^' + escapeString(delimiter === defaultDelimiter ? delimiter : (delimiter + defaultDelimiter)) + ']+?' | ||
}); | ||
} | ||
// Push any remaining characters. | ||
if (path || index < str.length) { | ||
tokens.push(path + str.substr(index)); | ||
} | ||
return tokens | ||
tokens.push({ type: "END", index: i, value: "" }); | ||
return tokens; | ||
} | ||
/** | ||
* Compile a string to a template function for the path. | ||
* | ||
* @param {string} str | ||
* @param {Object=} options | ||
* @return {!function(Object=, Object=)} | ||
* Parse a string for the raw tokens. | ||
*/ | ||
function compile (str, options) { | ||
return tokensToFunction(parse(str, options), options) | ||
} | ||
/** | ||
* Expose a method for transforming tokens into the path function. | ||
*/ | ||
function tokensToFunction (tokens, options) { | ||
// Compile all the tokens into regexps. | ||
var matches = new Array(tokens.length); | ||
// Compile all the patterns before compilation. | ||
for (var i = 0; i < tokens.length; i++) { | ||
if (typeof tokens[i] === 'object') { | ||
matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$', flags(options)); | ||
} | ||
} | ||
return function (data, options) { | ||
var path = ''; | ||
var encode = (options && options.encode) || encodeURIComponent; | ||
var validate = options ? options.validate !== false : true; | ||
for (var i = 0; i < tokens.length; i++) { | ||
var token = tokens[i]; | ||
if (typeof token === 'string') { | ||
path += token; | ||
continue | ||
} | ||
var value = data ? data[token.name] : undefined; | ||
var segment; | ||
if (Array.isArray(value)) { | ||
if (!token.repeat) { | ||
throw new TypeError('Expected "' + token.name + '" to not repeat, but got array') | ||
function parse(str, options) { | ||
if (options === void 0) { options = {}; } | ||
var tokens = lexer(str); | ||
var _a = options.prefixes, prefixes = _a === void 0 ? "./" : _a; | ||
var defaultPattern = "[^" + escapeString(options.delimiter || "/#?") + "]+?"; | ||
var result = []; | ||
var key = 0; | ||
var i = 0; | ||
var path = ""; | ||
var tryConsume = function (type) { | ||
if (i < tokens.length && tokens[i].type === type) | ||
return tokens[i++].value; | ||
}; | ||
var mustConsume = function (type) { | ||
var value = tryConsume(type); | ||
if (value !== undefined) | ||
return value; | ||
var _a = tokens[i], nextType = _a.type, index = _a.index; | ||
throw new TypeError("Unexpected " + nextType + " at " + index + ", expected " + type); | ||
}; | ||
var consumeText = function () { | ||
var result = ""; | ||
var value; | ||
// tslint:disable-next-line | ||
while ((value = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR"))) { | ||
result += value; | ||
} | ||
if (value.length === 0) { | ||
if (token.optional) continue | ||
throw new TypeError('Expected "' + token.name + '" to not be empty') | ||
return result; | ||
}; | ||
while (i < tokens.length) { | ||
var char = tryConsume("CHAR"); | ||
var name = tryConsume("NAME"); | ||
var pattern = tryConsume("PATTERN"); | ||
if (name || pattern) { | ||
var prefix = char || ""; | ||
if (prefixes.indexOf(prefix) === -1) { | ||
path += prefix; | ||
prefix = ""; | ||
} | ||
if (path) { | ||
result.push(path); | ||
path = ""; | ||
} | ||
result.push({ | ||
name: name || key++, | ||
prefix: prefix, | ||
suffix: "", | ||
pattern: pattern || defaultPattern, | ||
modifier: tryConsume("MODIFIER") || "" | ||
}); | ||
continue; | ||
} | ||
for (var j = 0; j < value.length; j++) { | ||
segment = encode(value[j], token); | ||
if (validate && !matches[i].test(segment)) { | ||
throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '"') | ||
} | ||
path += (j === 0 ? token.prefix : token.delimiter) + segment; | ||
var value = char || tryConsume("ESCAPED_CHAR"); | ||
if (value) { | ||
path += value; | ||
continue; | ||
} | ||
continue | ||
} | ||
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { | ||
segment = encode(String(value), token); | ||
if (validate && !matches[i].test(segment)) { | ||
throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but got "' + segment + '"') | ||
if (path) { | ||
result.push(path); | ||
path = ""; | ||
} | ||
path += token.prefix + segment; | ||
continue | ||
} | ||
if (token.optional) continue | ||
throw new TypeError('Expected "' + token.name + '" to be ' + (token.repeat ? 'an array' : 'a string')) | ||
var open = tryConsume("OPEN"); | ||
if (open) { | ||
var prefix = consumeText(); | ||
var name_1 = tryConsume("NAME") || ""; | ||
var pattern_1 = tryConsume("PATTERN") || ""; | ||
var suffix = consumeText(); | ||
mustConsume("CLOSE"); | ||
result.push({ | ||
name: name_1 || (pattern_1 ? key++ : ""), | ||
pattern: name_1 && !pattern_1 ? defaultPattern : pattern_1, | ||
prefix: prefix, | ||
suffix: suffix, | ||
modifier: tryConsume("MODIFIER") || "" | ||
}); | ||
continue; | ||
} | ||
mustConsume("END"); | ||
} | ||
return path | ||
} | ||
return result; | ||
} | ||
/** | ||
* Escape a regular expression string. | ||
* | ||
* @param {string} str | ||
* @return {string} | ||
*/ | ||
function escapeString (str) { | ||
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1') | ||
function escapeString(str) { | ||
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1"); | ||
} | ||
/** | ||
* Escape the capturing group by escaping special characters and meaning. | ||
* | ||
* @param {string} group | ||
* @return {string} | ||
*/ | ||
function escapeGroup (group) { | ||
return group.replace(/([=!:$/()])/g, '\\$1') | ||
} | ||
/** | ||
* Get the flags for a regexp from the options. | ||
* | ||
* @param {Object} options | ||
* @return {string} | ||
*/ | ||
function flags (options) { | ||
return options && options.sensitive ? '' : 'i' | ||
function flags(options) { | ||
return options && options.sensitive ? "" : "i"; | ||
} | ||
/** | ||
* Pull out keys from a regexp. | ||
* | ||
* @param {!RegExp} path | ||
* @param {Array=} keys | ||
* @return {!RegExp} | ||
*/ | ||
function regexpToRegexp (path, keys) { | ||
if (!keys) return path | ||
// Use a negative lookahead to match only capturing groups. | ||
var groups = path.source.match(/\((?!\?)/g); | ||
if (groups) { | ||
for (var i = 0; i < groups.length; i++) { | ||
keys.push({ | ||
name: i, | ||
prefix: null, | ||
delimiter: null, | ||
optional: false, | ||
repeat: false, | ||
pattern: null | ||
}); | ||
function regexpToRegexp(path, keys) { | ||
if (!keys) | ||
return path; | ||
// Use a negative lookahead to match only capturing groups. | ||
var groups = path.source.match(/\((?!\?)/g); | ||
if (groups) { | ||
for (var i = 0; i < groups.length; i++) { | ||
keys.push({ | ||
name: i, | ||
prefix: "", | ||
suffix: "", | ||
modifier: "", | ||
pattern: "" | ||
}); | ||
} | ||
} | ||
} | ||
return path | ||
return path; | ||
} | ||
/** | ||
* Transform an array into a regexp. | ||
* | ||
* @param {!Array} path | ||
* @param {Array=} keys | ||
* @param {Object=} options | ||
* @return {!RegExp} | ||
*/ | ||
function arrayToRegexp (path, keys, options) { | ||
var parts = []; | ||
for (var i = 0; i < path.length; i++) { | ||
parts.push(pathToRegexp(path[i], keys, options).source); | ||
} | ||
return new RegExp('(?:' + parts.join('|') + ')', flags(options)) | ||
function arrayToRegexp(paths, keys, options) { | ||
var parts = paths.map(function (path) { return pathToRegexp(path, keys, options).source; }); | ||
return new RegExp("(?:" + parts.join("|") + ")", flags(options)); | ||
} | ||
/** | ||
* Create a path regexp from string input. | ||
* | ||
* @param {string} path | ||
* @param {Array=} keys | ||
* @param {Object=} options | ||
* @return {!RegExp} | ||
*/ | ||
function stringToRegexp (path, keys, options) { | ||
return tokensToRegExp(parse(path, options), keys, options) | ||
function stringToRegexp(path, keys, options) { | ||
return tokensToRegexp(parse(path, options), keys, options); | ||
} | ||
/** | ||
* Expose a function for taking tokens and returning a RegExp. | ||
* | ||
* @param {!Array} tokens | ||
* @param {Array=} keys | ||
* @param {Object=} options | ||
* @return {!RegExp} | ||
*/ | ||
function tokensToRegExp (tokens, keys, options) { | ||
options = options || {}; | ||
var strict = options.strict; | ||
var start = options.start !== false; | ||
var end = options.end !== false; | ||
var delimiter = options.delimiter || DEFAULT_DELIMITER; | ||
var endsWith = [].concat(options.endsWith || []).map(escapeString).concat('$').join('|'); | ||
var route = start ? '^' : ''; | ||
// Iterate over the tokens and create our regexp string. | ||
for (var i = 0; i < tokens.length; i++) { | ||
var token = tokens[i]; | ||
if (typeof token === 'string') { | ||
route += escapeString(token); | ||
} else { | ||
var capture = token.repeat | ||
? '(?:' + token.pattern + ')(?:' + escapeString(token.delimiter) + '(?:' + token.pattern + '))*' | ||
: token.pattern; | ||
if (keys) keys.push(token); | ||
if (token.optional) { | ||
if (!token.prefix) { | ||
route += '(' + capture + ')?'; | ||
} else { | ||
route += '(?:' + escapeString(token.prefix) + '(' + capture + '))?'; | ||
function tokensToRegexp(tokens, keys, options) { | ||
if (options === void 0) { options = {}; } | ||
var _a = options.strict, strict = _a === void 0 ? false : _a, _b = options.start, start = _b === void 0 ? true : _b, _c = options.end, end = _c === void 0 ? true : _c, _d = options.encode, encode = _d === void 0 ? function (x) { return x; } : _d; | ||
var endsWith = "[" + escapeString(options.endsWith || "") + "]|$"; | ||
var delimiter = "[" + escapeString(options.delimiter || "/#?") + "]"; | ||
var route = start ? "^" : ""; | ||
// Iterate over the tokens and create our regexp string. | ||
for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) { | ||
var token = tokens_1[_i]; | ||
if (typeof token === "string") { | ||
route += escapeString(encode(token)); | ||
} | ||
} else { | ||
route += escapeString(token.prefix) + '(' + capture + ')'; | ||
} | ||
else { | ||
var prefix = escapeString(encode(token.prefix)); | ||
var suffix = escapeString(encode(token.suffix)); | ||
if (token.pattern) { | ||
if (keys) | ||
keys.push(token); | ||
if (prefix || suffix) { | ||
if (token.modifier === "+" || token.modifier === "*") { | ||
var mod = token.modifier === "*" ? "?" : ""; | ||
route += "(?:" + prefix + "((?:" + token.pattern + ")(?:" + suffix + prefix + "(?:" + token.pattern + "))*)" + suffix + ")" + mod; | ||
} | ||
else { | ||
route += "(?:" + prefix + "(" + token.pattern + ")" + suffix + ")" + token.modifier; | ||
} | ||
} | ||
else { | ||
route += "(" + token.pattern + ")" + token.modifier; | ||
} | ||
} | ||
else { | ||
route += "(?:" + prefix + suffix + ")" + token.modifier; | ||
} | ||
} | ||
} | ||
} | ||
if (end) { | ||
if (!strict) route += '(?:' + escapeString(delimiter) + ')?'; | ||
route += endsWith === '$' ? '$' : '(?=' + endsWith + ')'; | ||
} else { | ||
var endToken = tokens[tokens.length - 1]; | ||
var isEndDelimited = typeof endToken === 'string' | ||
? endToken[endToken.length - 1] === delimiter | ||
: endToken === undefined; | ||
if (!strict) route += '(?:' + escapeString(delimiter) + '(?=' + endsWith + '))?'; | ||
if (!isEndDelimited) route += '(?=' + escapeString(delimiter) + '|' + endsWith + ')'; | ||
} | ||
return new RegExp(route, flags(options)) | ||
if (end) { | ||
if (!strict) | ||
route += delimiter + "?"; | ||
route += !options.endsWith ? "$" : "(?=" + endsWith + ")"; | ||
} | ||
else { | ||
var endToken = tokens[tokens.length - 1]; | ||
var isEndDelimited = typeof endToken === "string" | ||
? delimiter.indexOf(endToken[endToken.length - 1]) > -1 | ||
: // tslint:disable-next-line | ||
endToken === undefined; | ||
if (!strict) { | ||
route += "(?:" + delimiter + "(?=" + endsWith + "))?"; | ||
} | ||
if (!isEndDelimited) { | ||
route += "(?=" + delimiter + "|" + endsWith + ")"; | ||
} | ||
} | ||
return new RegExp(route, flags(options)); | ||
} | ||
/** | ||
@@ -388,28 +321,28 @@ * Normalize the given path string, returning a regular expression. | ||
* contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. | ||
* | ||
* @param {(string|RegExp|Array)} path | ||
* @param {Array=} keys | ||
* @param {Object=} options | ||
* @return {!RegExp} | ||
*/ | ||
function pathToRegexp (path, keys, options) { | ||
if (path instanceof RegExp) { | ||
return regexpToRegexp(path, keys) | ||
} | ||
if (Array.isArray(path)) { | ||
return arrayToRegexp(/** @type {!Array} */ (path), keys, options) | ||
} | ||
return stringToRegexp(/** @type {string} */ (path), keys, options) | ||
function pathToRegexp(path, keys, options) { | ||
if (path instanceof RegExp) | ||
return regexpToRegexp(path, keys); | ||
if (Array.isArray(path)) | ||
return arrayToRegexp(path, keys, options); | ||
return stringToRegexp(path, keys, options); | ||
} | ||
pathToRegexp_1.parse = parse_1; | ||
pathToRegexp_1.compile = compile_1; | ||
pathToRegexp_1.tokensToFunction = tokensToFunction_1; | ||
pathToRegexp_1.tokensToRegExp = tokensToRegExp_1; | ||
/** | ||
* Tiny request router. Allows overloading of handler type to be fully type safe. | ||
* | ||
* @example | ||
* import { Router, Method, Params } from 'tiny-request-router' | ||
* | ||
* // Let the router know that handlers are async functions returning a Response | ||
* type Handler = (params: Params) => Promise<Response> | ||
* | ||
* const router = new Router<Handler>() | ||
*/ | ||
var Router = /** @class */ (function () { | ||
function Router() { | ||
/** List of all registered routes. */ | ||
this.routes = []; | ||
} | ||
/** Add a route that matches any method. */ | ||
Router.prototype.all = function (path, handler, options) { | ||
@@ -419,2 +352,3 @@ if (options === void 0) { options = {}; } | ||
}; | ||
/** Add a route that matches the GET method. */ | ||
Router.prototype.get = function (path, handler, options) { | ||
@@ -424,2 +358,3 @@ if (options === void 0) { options = {}; } | ||
}; | ||
/** Add a route that matches the POST method. */ | ||
Router.prototype.post = function (path, handler, options) { | ||
@@ -429,2 +364,3 @@ if (options === void 0) { options = {}; } | ||
}; | ||
/** Add a route that matches the PUT method. */ | ||
Router.prototype.put = function (path, handler, options) { | ||
@@ -434,2 +370,3 @@ if (options === void 0) { options = {}; } | ||
}; | ||
/** Add a route that matches the PATCH method. */ | ||
Router.prototype.patch = function (path, handler, options) { | ||
@@ -439,2 +376,3 @@ if (options === void 0) { options = {}; } | ||
}; | ||
/** Add a route that matches the DELETE method. */ | ||
Router.prototype["delete"] = function (path, handler, options) { | ||
@@ -444,2 +382,3 @@ if (options === void 0) { options = {}; } | ||
}; | ||
/** Add a route that matches the HEAD method. */ | ||
Router.prototype.head = function (path, handler, options) { | ||
@@ -449,2 +388,3 @@ if (options === void 0) { options = {}; } | ||
}; | ||
/** Add a route that matches the OPTIONS method. */ | ||
Router.prototype.options = function (path, handler, options) { | ||
@@ -454,2 +394,15 @@ if (options === void 0) { options = {}; } | ||
}; | ||
/** | ||
* Match the provided method and path against the list of registered routes. | ||
* | ||
* @example | ||
* router.get('/foobar', async () => new Response('Hello')) | ||
* | ||
* const match = router.match('GET', '/foobar') | ||
* if (match) { | ||
* // Call the async function of that match | ||
* const response = await match.handler() | ||
* console.log(response) // => Response('Hello') | ||
* } | ||
*/ | ||
Router.prototype.match = function (method, path) { | ||
@@ -462,3 +415,3 @@ for (var _i = 0, _a = this.routes; _i < _a.length; _i++) { | ||
// Speed optimizations for catch all wildcard routes | ||
if (route.path === '*' || route.path === '(.*)') { | ||
if (route.path === '(.*)') { | ||
return __assign(__assign({}, route), { params: { '0': route.path } }); | ||
@@ -479,3 +432,6 @@ } | ||
var keys = []; | ||
var regexp = pathToRegexp_1(path, keys, options); | ||
if (path === '*') { | ||
path = '(.*)'; | ||
} | ||
var regexp = pathToRegexp(path, keys, options); | ||
this.routes.push({ method: method, path: path, handler: handler, keys: keys, options: options, regexp: regexp }); | ||
@@ -501,3 +457,3 @@ return this; | ||
exports.Router = Router; | ||
exports.pathToRegexp = pathToRegexp_1; | ||
exports.pathToRegexp = pathToRegexp; | ||
//# sourceMappingURL=router.js.map |
/*! | ||
* tiny-request-router v1.1.2 by berstend | ||
* tiny-request-router v1.2.2 by berstend | ||
* https://github.com/berstend/tiny-request-router#readme | ||
@@ -20,3 +20,3 @@ * @license MIT | ||
and limitations under the License. | ||
***************************************************************************** */var t=function(){return(t=Object.assign||function(e){for(var t,r=1,n=arguments.length;r<n;r++)for(var o in t=arguments[r])Object.prototype.hasOwnProperty.call(t,o)&&(e[o]=t[o]);return e}).apply(this,arguments)},r=v,n=f,o=function(e,t){return s(f(e,t),t)},i=s,p=d,a="/",u=new RegExp(["(\\\\.)","(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?"].join("|"),"g");function f(e,t){for(var r,n=[],o=0,i=0,p="",f=t&&t.delimiter||a,s=t&&t.whitelist||void 0,l=!1;null!==(r=u.exec(e));){var d=r[0],v=r[1],g=r.index;if(p+=e.slice(i,g),i=g+d.length,v)p+=v[1],l=!0;else{var y="",m=r[2],x=r[3],E=r[4],w=r[5];if(!l&&p.length){var T=p.length-1,b=p[T];(!s||s.indexOf(b)>-1)&&(y=b,p=p.slice(0,T))}p&&(n.push(p),p="",l=!1);var R="+"===w||"*"===w,_="?"===w||"*"===w,A=x||E,j=y||f;n.push({name:m||o++,prefix:y,delimiter:j,optional:_,repeat:R,pattern:A?c(A):"[^"+h(j===f?j:j+f)+"]+?"})}}return(p||i<e.length)&&n.push(p+e.substr(i)),n}function s(e,t){for(var r=new Array(e.length),n=0;n<e.length;n++)"object"==typeof e[n]&&(r[n]=new RegExp("^(?:"+e[n].pattern+")$",l(t)));return function(t,n){for(var o="",i=n&&n.encode||encodeURIComponent,p=!n||!1!==n.validate,a=0;a<e.length;a++){var u=e[a];if("string"!=typeof u){var f,s=t?t[u.name]:void 0;if(Array.isArray(s)){if(!u.repeat)throw new TypeError('Expected "'+u.name+'" to not repeat, but got array');if(0===s.length){if(u.optional)continue;throw new TypeError('Expected "'+u.name+'" to not be empty')}for(var h=0;h<s.length;h++){if(f=i(s[h],u),p&&!r[a].test(f))throw new TypeError('Expected all "'+u.name+'" to match "'+u.pattern+'"');o+=(0===h?u.prefix:u.delimiter)+f}}else if("string"!=typeof s&&"number"!=typeof s&&"boolean"!=typeof s){if(!u.optional)throw new TypeError('Expected "'+u.name+'" to be '+(u.repeat?"an array":"a string"))}else{if(f=i(String(s),u),p&&!r[a].test(f))throw new TypeError('Expected "'+u.name+'" to match "'+u.pattern+'", but got "'+f+'"');o+=u.prefix+f}}else o+=u}return o}}function h(e){return e.replace(/([.+*?=^!:${}()[\]|/\\])/g,"\\$1")}function c(e){return e.replace(/([=!:$/()])/g,"\\$1")}function l(e){return e&&e.sensitive?"":"i"}function d(e,t,r){for(var n=(r=r||{}).strict,o=!1!==r.start,i=!1!==r.end,p=r.delimiter||a,u=[].concat(r.endsWith||[]).map(h).concat("$").join("|"),f=o?"^":"",s=0;s<e.length;s++){var c=e[s];if("string"==typeof c)f+=h(c);else{var d=c.repeat?"(?:"+c.pattern+")(?:"+h(c.delimiter)+"(?:"+c.pattern+"))*":c.pattern;t&&t.push(c),c.optional?c.prefix?f+="(?:"+h(c.prefix)+"("+d+"))?":f+="("+d+")?":f+=h(c.prefix)+"("+d+")"}}if(i)n||(f+="(?:"+h(p)+")?"),f+="$"===u?"$":"(?="+u+")";else{var v=e[e.length-1],g="string"==typeof v?v[v.length-1]===p:void 0===v;n||(f+="(?:"+h(p)+"(?="+u+"))?"),g||(f+="(?="+h(p)+"|"+u+")")}return new RegExp(f,l(r))}function v(e,t,r){return e instanceof RegExp?function(e,t){if(!t)return e;var r=e.source.match(/\((?!\?)/g);if(r)for(var n=0;n<r.length;n++)t.push({name:n,prefix:null,delimiter:null,optional:!1,repeat:!1,pattern:null});return e}(e,t):Array.isArray(e)?function(e,t,r){for(var n=[],o=0;o<e.length;o++)n.push(v(e[o],t,r).source);return new RegExp("(?:"+n.join("|")+")",l(r))}(e,t,r):function(e,t,r){return d(f(e,r),t,r)}(e,t,r)}r.parse=n,r.compile=o,r.tokensToFunction=i,r.tokensToRegExp=p;var g=function(){function e(){this.routes=[]}return e.prototype.all=function(e,t,r){return void 0===r&&(r={}),this._push("ALL",e,t,r)},e.prototype.get=function(e,t,r){return void 0===r&&(r={}),this._push("GET",e,t,r)},e.prototype.post=function(e,t,r){return void 0===r&&(r={}),this._push("POST",e,t,r)},e.prototype.put=function(e,t,r){return void 0===r&&(r={}),this._push("PUT",e,t,r)},e.prototype.patch=function(e,t,r){return void 0===r&&(r={}),this._push("PATCH",e,t,r)},e.prototype.delete=function(e,t,r){return void 0===r&&(r={}),this._push("DELETE",e,t,r)},e.prototype.head=function(e,t,r){return void 0===r&&(r={}),this._push("HEAD",e,t,r)},e.prototype.options=function(e,t,r){return void 0===r&&(r={}),this._push("OPTIONS",e,t,r)},e.prototype.match=function(e,r){for(var n=0,o=this.routes;n<o.length;n++){var i=o[n];if(i.method===e||"ALL"===i.method){if("*"===i.path||"(.*)"===i.path)return t(t({},i),{params:{0:i.path}});if("/"===i.path&&!1===i.options.end)return t(t({},i),{params:{}});var p=i.regexp.exec(r);if(p&&p.length)return t(t({},i),{matches:p,params:y(p,i.keys)})}}return null},e.prototype._push=function(e,t,n,o){var i=[],p=r(t,i,o);return this.routes.push({method:e,path:t,handler:n,keys:i,options:o,regexp:p}),this},e}(),y=function(e,t){for(var r={},n=1;n<e.length;n++){var o=t[n-1].name,i=e[n];void 0!==i&&(r[o]=i)}return r};e.Router=g,e.pathToRegexp=r,Object.defineProperty(e,"__esModule",{value:!0})})); | ||
***************************************************************************** */var t=function(){return(t=Object.assign||function(e){for(var t,r=1,n=arguments.length;r<n;r++)for(var i in t=arguments[r])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)};function r(e,t){void 0===t&&(t={});for(var r=function(e){for(var t=[],r=0;r<e.length;){var n=e[r];if("*"!==n&&"+"!==n&&"?"!==n)if("\\"!==n)if("{"!==n)if("}"!==n)if(":"!==n)if("("!==n)t.push({type:"CHAR",index:r,value:e[r++]});else{var i=1,o="";if("?"===e[p=r+1])throw new TypeError('Pattern cannot start with "?" at '+p);for(;p<e.length;)if("\\"!==e[p]){if(")"===e[p]){if(0===--i){p++;break}}else if("("===e[p]&&(i++,"?"!==e[p+1]))throw new TypeError("Capturing groups are not allowed at "+p);o+=e[p++]}else o+=e[p++]+e[p++];if(i)throw new TypeError("Unbalanced pattern at "+r);if(!o)throw new TypeError("Missing pattern at "+r);t.push({type:"PATTERN",index:r,value:o}),r=p}else{for(var u="",p=r+1;p<e.length;){var f=e.charCodeAt(p);if(!(f>=48&&f<=57||f>=65&&f<=90||f>=97&&f<=122||95===f))break;u+=e[p++]}if(!u)throw new TypeError("Missing parameter name at "+r);t.push({type:"NAME",index:r,value:u}),r=p}else t.push({type:"CLOSE",index:r,value:e[r++]});else t.push({type:"OPEN",index:r,value:e[r++]});else t.push({type:"ESCAPED_CHAR",index:r++,value:e[r++]});else t.push({type:"MODIFIER",index:r,value:e[r++]})}return t.push({type:"END",index:r,value:""}),t}(e),i=t.prefixes,o=void 0===i?"./":i,u="[^"+n(t.delimiter||"/#?")+"]+?",p=[],f=0,a=0,s="",h=function(e){if(a<r.length&&r[a].type===e)return r[a++].value},d=function(e){var t=h(e);if(void 0!==t)return t;var n=r[a],i=n.type,o=n.index;throw new TypeError("Unexpected "+i+" at "+o+", expected "+e)},v=function(){for(var e,t="";e=h("CHAR")||h("ESCAPED_CHAR");)t+=e;return t};a<r.length;){var l=h("CHAR"),c=h("NAME"),y=h("PATTERN");if(c||y){var E=l||"";-1===o.indexOf(E)&&(s+=E,E=""),s&&(p.push(s),s=""),p.push({name:c||f++,prefix:E,suffix:"",pattern:y||u,modifier:h("MODIFIER")||""})}else{var x=l||h("ESCAPED_CHAR");if(x)s+=x;else if(s&&(p.push(s),s=""),h("OPEN")){E=v();var g=h("NAME")||"",m=h("PATTERN")||"",A=v();d("CLOSE"),p.push({name:g||(m?f++:""),pattern:g&&!m?u:m,prefix:E,suffix:A,modifier:h("MODIFIER")||""})}else d("END")}}return p}function n(e){return e.replace(/([.+*?=^!:${}()[\]|/\\])/g,"\\$1")}function i(e){return e&&e.sensitive?"":"i"}function o(e,t,o){return function(e,t,r){void 0===r&&(r={});for(var o=r.strict,u=void 0!==o&&o,p=r.start,f=void 0===p||p,a=r.end,s=void 0===a||a,h=r.encode,d=void 0===h?function(e){return e}:h,v="["+n(r.endsWith||"")+"]|$",l="["+n(r.delimiter||"/#?")+"]",c=f?"^":"",y=0,E=e;y<E.length;y++){var x=E[y];if("string"==typeof x)c+=n(d(x));else{var g=n(d(x.prefix)),m=n(d(x.suffix));if(x.pattern)if(t&&t.push(x),g||m)if("+"===x.modifier||"*"===x.modifier){var A="*"===x.modifier?"?":"";c+="(?:"+g+"((?:"+x.pattern+")(?:"+m+g+"(?:"+x.pattern+"))*)"+m+")"+A}else c+="(?:"+g+"("+x.pattern+")"+m+")"+x.modifier;else c+="("+x.pattern+")"+x.modifier;else c+="(?:"+g+m+")"+x.modifier}}if(s)u||(c+=l+"?"),c+=r.endsWith?"(?="+v+")":"$";else{var T=e[e.length-1],R="string"==typeof T?l.indexOf(T[T.length-1])>-1:void 0===T;u||(c+="(?:"+l+"(?="+v+"))?"),R||(c+="(?="+l+"|"+v+")")}return new RegExp(c,i(r))}(r(e,o),t,o)}function u(e,t,r){return e instanceof RegExp?function(e,t){if(!t)return e;var r=e.source.match(/\((?!\?)/g);if(r)for(var n=0;n<r.length;n++)t.push({name:n,prefix:"",suffix:"",modifier:"",pattern:""});return e}(e,t):Array.isArray(e)?function(e,t,r){var n=e.map((function(e){return u(e,t,r).source}));return new RegExp("(?:"+n.join("|")+")",i(r))}(e,t,r):o(e,t,r)}var p=function(){function e(){this.routes=[]}return e.prototype.all=function(e,t,r){return void 0===r&&(r={}),this._push("ALL",e,t,r)},e.prototype.get=function(e,t,r){return void 0===r&&(r={}),this._push("GET",e,t,r)},e.prototype.post=function(e,t,r){return void 0===r&&(r={}),this._push("POST",e,t,r)},e.prototype.put=function(e,t,r){return void 0===r&&(r={}),this._push("PUT",e,t,r)},e.prototype.patch=function(e,t,r){return void 0===r&&(r={}),this._push("PATCH",e,t,r)},e.prototype.delete=function(e,t,r){return void 0===r&&(r={}),this._push("DELETE",e,t,r)},e.prototype.head=function(e,t,r){return void 0===r&&(r={}),this._push("HEAD",e,t,r)},e.prototype.options=function(e,t,r){return void 0===r&&(r={}),this._push("OPTIONS",e,t,r)},e.prototype.match=function(e,r){for(var n=0,i=this.routes;n<i.length;n++){var o=i[n];if(o.method===e||"ALL"===o.method){if("(.*)"===o.path)return t(t({},o),{params:{0:o.path}});if("/"===o.path&&!1===o.options.end)return t(t({},o),{params:{}});var u=o.regexp.exec(r);if(u&&u.length)return t(t({},o),{matches:u,params:f(u,o.keys)})}}return null},e.prototype._push=function(e,t,r,n){var i=[];"*"===t&&(t="(.*)");var o=u(t,i,n);return this.routes.push({method:e,path:t,handler:r,keys:i,options:n,regexp:o}),this},e}(),f=function(e,t){for(var r={},n=1;n<e.length;n++){var i=t[n-1].name,o=e[n];void 0!==o&&(r[i]=o)}return r};e.Router=p,e.pathToRegexp=u,Object.defineProperty(e,"__esModule",{value:!0})})); | ||
//# sourceMappingURL=router.min.js.map |
@@ -1,3 +0,36 @@ | ||
import pathToRegexp, { Key, RegExpOptions } from 'path-to-regexp'; | ||
import { Key as TokenKey, pathToRegexp, TokensToRegexpOptions } from 'path-to-regexp'; | ||
export { pathToRegexp }; | ||
/** Valid HTTP methods for matching. */ | ||
export declare type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS'; | ||
export declare type MethodWildcard = 'ALL'; | ||
export interface Params { | ||
[key: string]: string; | ||
} | ||
/** | ||
* Optional route options. | ||
* | ||
* @example | ||
* // When `true` the regexp will be case sensitive. (default: `false`) | ||
* sensitive?: boolean; | ||
* | ||
* // When `true` the regexp allows an optional trailing delimiter to match. (default: `false`) | ||
* strict?: boolean; | ||
* | ||
* // When `true` the regexp will match to the end of the string. (default: `true`) | ||
* end?: boolean; | ||
* | ||
* // When `true` the regexp will match from the beginning of the string. (default: `true`) | ||
* start?: boolean; | ||
* | ||
* // Sets the final character for non-ending optimistic matches. (default: `/`) | ||
* delimiter?: string; | ||
* | ||
* // List of characters that can also be "end" characters. | ||
* endsWith?: string; | ||
* | ||
* // Encode path tokens for use in the `RegExp`. | ||
* encode?: (value: string) => string; | ||
*/ | ||
export interface RouteOptions extends TokensToRegexpOptions { | ||
} | ||
export interface Route<HandlerType> { | ||
@@ -11,2 +44,19 @@ method: Method | MethodWildcard; | ||
} | ||
/** | ||
* The object returned when a route matches. | ||
* | ||
* The handler can then be used to execute the relevant function. | ||
* | ||
* @example | ||
* { | ||
* params: Params | ||
* matches?: RegExpExecArray | ||
* method: Method | MethodWildcard | ||
* path: string | ||
* regexp: RegExp | ||
* options: RouteOptions | ||
* keys: Keys | ||
* handler: HandlerType | ||
* } | ||
*/ | ||
export interface RouteMatch<HandlerType> extends Route<HandlerType> { | ||
@@ -16,23 +66,49 @@ params: Params; | ||
} | ||
export declare type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS'; | ||
export declare type MethodWildcard = 'ALL'; | ||
export interface Params { | ||
[key: string]: string; | ||
} | ||
export interface RouteOptions extends RegExpOptions { | ||
} | ||
export declare type Key = Key; | ||
export declare type Key = TokenKey; | ||
export declare type Keys = Array<Key>; | ||
/** | ||
* Tiny request router. Allows overloading of handler type to be fully type safe. | ||
* | ||
* @example | ||
* import { Router, Method, Params } from 'tiny-request-router' | ||
* | ||
* // Let the router know that handlers are async functions returning a Response | ||
* type Handler = (params: Params) => Promise<Response> | ||
* | ||
* const router = new Router<Handler>() | ||
*/ | ||
export declare class Router<HandlerType = any> { | ||
/** List of all registered routes. */ | ||
routes: Array<Route<HandlerType>>; | ||
/** Add a route that matches any method. */ | ||
all(path: string, handler: HandlerType, options?: RouteOptions): this; | ||
/** Add a route that matches the GET method. */ | ||
get(path: string, handler: HandlerType, options?: RouteOptions): this; | ||
/** Add a route that matches the POST method. */ | ||
post(path: string, handler: HandlerType, options?: RouteOptions): this; | ||
/** Add a route that matches the PUT method. */ | ||
put(path: string, handler: HandlerType, options?: RouteOptions): this; | ||
/** Add a route that matches the PATCH method. */ | ||
patch(path: string, handler: HandlerType, options?: RouteOptions): this; | ||
/** Add a route that matches the DELETE method. */ | ||
delete(path: string, handler: HandlerType, options?: RouteOptions): this; | ||
/** Add a route that matches the HEAD method. */ | ||
head(path: string, handler: HandlerType, options?: RouteOptions): this; | ||
/** Add a route that matches the OPTIONS method. */ | ||
options(path: string, handler: HandlerType, options?: RouteOptions): this; | ||
/** | ||
* Match the provided method and path against the list of registered routes. | ||
* | ||
* @example | ||
* router.get('/foobar', async () => new Response('Hello')) | ||
* | ||
* const match = router.match('GET', '/foobar') | ||
* if (match) { | ||
* // Call the async function of that match | ||
* const response = await match.handler() | ||
* console.log(response) // => Response('Hello') | ||
* } | ||
*/ | ||
match(method: Method, path: string): RouteMatch<HandlerType> | null; | ||
private _push; | ||
} |
{ | ||
"name": "tiny-request-router", | ||
"version": "1.1.2", | ||
"version": "1.2.2", | ||
"description": "Fast, generic and type safe router (match method and path).", | ||
@@ -28,3 +28,4 @@ "author": "berstend", | ||
"test": "ava-ts -v", | ||
"prepublish": "npm run build" | ||
"prepublish": "npm run build", | ||
"docs": "documentation readme --quiet --shallow --github --markdown-theme transitivebs --readme-file readme.md --section API ./src/router.ts && npx prettier --write readme.md && npx prettier --write readme.md" | ||
}, | ||
@@ -51,7 +52,8 @@ "prettier": { | ||
"devDependencies": { | ||
"@types/node": "^12.11.7", | ||
"@types/node": "^12.12.14", | ||
"ava": "^2.4.0", | ||
"ava-ts": "^0.25.1", | ||
"documentation-markdown-themes": "^12.1.5", | ||
"rimraf": "^3.0.0", | ||
"rollup": "^1.25.2", | ||
"rollup": "^1.27.5", | ||
"rollup-plugin-commonjs": "^10.1.0", | ||
@@ -61,11 +63,11 @@ "rollup-plugin-node-resolve": "^5.2.0", | ||
"rollup-plugin-typescript": "^1.0.1", | ||
"ts-node": "^8.4.1", | ||
"tslint": "^5.11.0", | ||
"ts-node": "^8.5.4", | ||
"tslint": "^5.20.1", | ||
"tslint-config-prettier": "^1.15.0", | ||
"tslint-config-standard": "^8.0.1", | ||
"typescript": "^3.0.3" | ||
"tslint-config-standard": "^9.0.0", | ||
"typescript": "^3.7.2" | ||
}, | ||
"dependencies": { | ||
"path-to-regexp": "^3.1.0" | ||
"path-to-regexp": "^6.1.0" | ||
} | ||
} |
241
readme.md
@@ -7,14 +7,14 @@ # tiny-request-router [![ ](https://travis-ci.org/berstend/tiny-request-router.svg?branch=master)](https://travis-ci.org/berstend/tiny-request-router) [![ ](https://img.shields.io/npm/v/tiny-request-router.svg)](https://www.npmjs.com/package/tiny-request-router) | ||
* Minimal and opinionless router, can be used in any script and environment. | ||
* Matches a request method (e.g. `GET`) and a path (e.g. `/foobar`) against a list of routes | ||
* Uses [path-to-regexp](https://github.com/pillarjs/path-to-regexp), which is used by express and therefore familiar | ||
* Allows wildcards (e.g. `/user/(.*)/age`) and named parameters (e.g. `/info/:username/:age`) | ||
* Will not call your handlers automatically, as it only cares about matching | ||
* No magic, no assumptions, no fluff, tested | ||
- Minimal and opinionless router, can be used in any script and environment. | ||
- Matches a request method (e.g. `GET`) and a path (e.g. `/foobar`) against a list of routes | ||
- Uses [path-to-regexp](https://github.com/pillarjs/path-to-regexp), which is used by express and therefore familiar | ||
- Allows wildcards (e.g. `/user/(.*)/age`) and named parameters (e.g. `/info/:username/:age`) | ||
- Will not call your handlers automatically, as it only cares about matching | ||
- Battle hardened in production ([Cloudflare Worker](https://www.cloudflare.com/products/cloudflare-workers/) with 10M requests per day) | ||
- No magic, no assumptions, no fluff, type safe, tested | ||
### Route testing | ||
* You can use the [Express Route Tester](https://forbeslindesay.github.io/express-route-tester/) (select `2.0.0`) to debug your path patterns quickly | ||
- You can use the [Express Route Tester](https://forbeslindesay.github.io/express-route-tester/) (select `2.0.0`) to debug your path patterns quickly | ||
## Installation | ||
@@ -75,5 +75,5 @@ | ||
const router = new Router() | ||
router.get("/worker", async () => new Response('Hi from worker!')) | ||
router.get("/hello/:name", async (params) => new Response(`Hello ${params.name}!`)) | ||
router.post("/test", async () => new Response('Post received!')) | ||
router.get('/worker', async () => new Response('Hi from worker!')) | ||
router.get('/hello/:name', async params => new Response(`Hello ${params.name}!`)) | ||
router.post('/test', async () => new Response('Post received!')) | ||
@@ -90,11 +90,226 @@ // Main entry point in workers | ||
}) | ||
``` | ||
--- | ||
## API | ||
The API is extremely minimal and what you would expect (`.get()`, `.all()`, etc). Please check out the [tiny source code](src/router.ts) or [tests](test/functionality.ts) for more info. | ||
<!-- Generated by documentation.js. Update this documentation by updating the source code. --> | ||
#### Table of Contents | ||
- [Method()](#method) | ||
- [RouteOptions()](#routeoptions) | ||
- [RouteMatch()](#routematch) | ||
- [class: Router](#class-router) | ||
- [.routes](#routes) | ||
- [.all(path, handler, options)](#allpath-handler-options) | ||
- [.get(path, handler, options)](#getpath-handler-options) | ||
- [.post(path, handler, options)](#postpath-handler-options) | ||
- [.put(path, handler, options)](#putpath-handler-options) | ||
- [.patch(path, handler, options)](#patchpath-handler-options) | ||
- [.delete(path, handler, options)](#deletepath-handler-options) | ||
- [.head(path, handler, options)](#headpath-handler-options) | ||
- [.options(path, handler, options)](#optionspath-handler-options) | ||
- [.match(method, path)](#matchmethod-path) | ||
### [Method()](https://github.com/berstend/tiny-request-router/blob/5e7d69be1e37a6d2d14c611efe77ba2ef6ea9f83/src/router.ts#L6-L6) | ||
Type: **(`"GET"` \| `"POST"` \| `"PUT"` \| `"PATCH"` \| `"DELETE"` \| `"HEAD"` \| `"OPTIONS"`)** | ||
Valid HTTP methods for matching. | ||
--- | ||
### [RouteOptions()](https://github.com/berstend/tiny-request-router/blob/5e7d69be1e37a6d2d14c611efe77ba2ef6ea9f83/src/router.ts#L39-L39) | ||
**Extends: TokensToRegexpOptions** | ||
Optional route options. | ||
Example: | ||
```javascript | ||
// When `true` the regexp will be case sensitive. (default: `false`) | ||
sensitive?: boolean; | ||
// When `true` the regexp allows an optional trailing delimiter to match. (default: `false`) | ||
strict?: boolean; | ||
// When `true` the regexp will match to the end of the string. (default: `true`) | ||
end?: boolean; | ||
// When `true` the regexp will match from the beginning of the string. (default: `true`) | ||
start?: boolean; | ||
// Sets the final character for non-ending optimistic matches. (default: `/`) | ||
delimiter?: string; | ||
// List of characters that can also be "end" characters. | ||
endsWith?: string; | ||
// Encode path tokens for use in the `RegExp`. | ||
encode?: (value: string) => string; | ||
``` | ||
--- | ||
### [RouteMatch()](https://github.com/berstend/tiny-request-router/blob/5e7d69be1e37a6d2d14c611efe77ba2ef6ea9f83/src/router.ts#L67-L70) | ||
**Extends: Route<HandlerType>** | ||
The object returned when a route matches. | ||
The handler can then be used to execute the relevant function. | ||
Example: | ||
```javascript | ||
{ | ||
params: Params | ||
matches?: RegExpExecArray | ||
method: Method | MethodWildcard | ||
path: string | ||
regexp: RegExp | ||
options: RouteOptions | ||
keys: Keys | ||
handler: HandlerType | ||
} | ||
``` | ||
--- | ||
### class: [Router](https://github.com/berstend/tiny-request-router/blob/5e7d69be1e37a6d2d14c611efe77ba2ef6ea9f83/src/router.ts#L86-L168) | ||
Tiny request router. Allows overloading of handler type to be fully type safe. | ||
Example: | ||
```javascript | ||
import { Router, Method, Params } from 'tiny-request-router' | ||
// Let the router know that handlers are async functions returning a Response | ||
type Handler = (params: Params) => Promise<Response> | ||
const router = new Router<Handler>() | ||
``` | ||
--- | ||
#### .[routes](https://github.com/berstend/tiny-request-router/blob/5e7d69be1e37a6d2d14c611efe77ba2ef6ea9f83/src/router.ts#L88-L88) | ||
List of all registered routes. | ||
--- | ||
#### .[all(path, handler, options)](https://github.com/berstend/tiny-request-router/blob/5e7d69be1e37a6d2d14c611efe77ba2ef6ea9f83/src/router.ts#L91-L93) | ||
- `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** | ||
- `handler` **HandlerType** | ||
- `options` **[RouteOptions](#routeoptions)** (optional, default `{}`) | ||
Add a route that matches any method. | ||
--- | ||
#### .[get(path, handler, options)](https://github.com/berstend/tiny-request-router/blob/5e7d69be1e37a6d2d14c611efe77ba2ef6ea9f83/src/router.ts#L95-L97) | ||
- `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** | ||
- `handler` **HandlerType** | ||
- `options` **[RouteOptions](#routeoptions)** (optional, default `{}`) | ||
Add a route that matches the GET method. | ||
--- | ||
#### .[post(path, handler, options)](https://github.com/berstend/tiny-request-router/blob/5e7d69be1e37a6d2d14c611efe77ba2ef6ea9f83/src/router.ts#L99-L101) | ||
- `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** | ||
- `handler` **HandlerType** | ||
- `options` **[RouteOptions](#routeoptions)** (optional, default `{}`) | ||
Add a route that matches the POST method. | ||
--- | ||
#### .[put(path, handler, options)](https://github.com/berstend/tiny-request-router/blob/5e7d69be1e37a6d2d14c611efe77ba2ef6ea9f83/src/router.ts#L103-L105) | ||
- `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** | ||
- `handler` **HandlerType** | ||
- `options` **[RouteOptions](#routeoptions)** (optional, default `{}`) | ||
Add a route that matches the PUT method. | ||
--- | ||
#### .[patch(path, handler, options)](https://github.com/berstend/tiny-request-router/blob/5e7d69be1e37a6d2d14c611efe77ba2ef6ea9f83/src/router.ts#L107-L109) | ||
- `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** | ||
- `handler` **HandlerType** | ||
- `options` **[RouteOptions](#routeoptions)** (optional, default `{}`) | ||
Add a route that matches the PATCH method. | ||
--- | ||
#### .[delete(path, handler, options)](https://github.com/berstend/tiny-request-router/blob/5e7d69be1e37a6d2d14c611efe77ba2ef6ea9f83/src/router.ts#L111-L113) | ||
- `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** | ||
- `handler` **HandlerType** | ||
- `options` **[RouteOptions](#routeoptions)** (optional, default `{}`) | ||
Add a route that matches the DELETE method. | ||
--- | ||
#### .[head(path, handler, options)](https://github.com/berstend/tiny-request-router/blob/5e7d69be1e37a6d2d14c611efe77ba2ef6ea9f83/src/router.ts#L115-L117) | ||
- `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** | ||
- `handler` **HandlerType** | ||
- `options` **[RouteOptions](#routeoptions)** (optional, default `{}`) | ||
Add a route that matches the HEAD method. | ||
--- | ||
#### .[options(path, handler, options)](https://github.com/berstend/tiny-request-router/blob/5e7d69be1e37a6d2d14c611efe77ba2ef6ea9f83/src/router.ts#L119-L121) | ||
- `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** | ||
- `handler` **HandlerType** | ||
- `options` **[RouteOptions](#routeoptions)** (optional, default `{}`) | ||
Add a route that matches the OPTIONS method. | ||
--- | ||
#### .[match(method, path)](https://github.com/berstend/tiny-request-router/blob/5e7d69be1e37a6d2d14c611efe77ba2ef6ea9f83/src/router.ts#L135-L152) | ||
- `method` **[Method](#method)** | ||
- `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** | ||
Returns: **([RouteMatch](#routematch)<HandlerType> | null)** | ||
Match the provided method and path against the list of registered routes. | ||
Example: | ||
```javascript | ||
router.get('/foobar', async () => new Response('Hello')) | ||
const match = router.match('GET', '/foobar') | ||
if (match) { | ||
// Call the async function of that match | ||
const response = await match.handler() | ||
console.log(response) // => Response('Hello') | ||
} | ||
``` | ||
--- | ||
## More info | ||
Please check out the [tiny source code](src/router.ts) or [tests](test/functionality.ts) for more info. | ||
## License | ||
MIT |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
303296
1898
313
15
+ Addedpath-to-regexp@6.3.0(transitive)
- Removedpath-to-regexp@3.3.0(transitive)
Updatedpath-to-regexp@^6.1.0