paramPrefix: ':',
};
+/**
+ * A naïve router which maps incoming requests to handler functions
+ * by way of url path and request method.
+ *
+ * Regex parsing of paths was eschewed, as a design decision.
+ *
+ * Instead, each path to be searched for is deconstructed into a list
+ * of its constituent parts as strings or objects, for invariant or
+ * parameterized parts respectively. Each search path is assigned a
+ * mapping of methods to handler functions.
+ *
+ * @property {Object} pathsByLength index to registered paths by number of parts
+ * @property {Symbol} METHODS key to method:handler map on search paths
+ * @property {Symbol} PARAM key to parameter name in search path parts
+ */
class Router {
/**
* @param {Object} options
- * @param {Boolean} options.ignoreTrailingSlash
- * @param {Boolean} options.paramPrefix
+ * @param {Boolean} options.ignoreTrailingSlash discard any trailing slashes when registering and comparing paths (default: false)
+ * @param {String} options.paramPrefix prefix of a path part denoting a named parameter when registering paths (default: ':')
*/
constructor(options = {}) {
common.setOptions(this, defaultOptions, options);
this.PARAM = PARAM;
}
-
+ /**
+ * @typedef {Object} Router~ParamPart
+ * @property {String} PARAM (symbol key)
+ */
+ /**
+ * @typedef {Array<String|Router~ParamPart>} Router~SearchPath
+ * @property {Object} METHODS (symbol key)
+ */
/**
* Prepare a path for insertion into search list.
- * A searchable path is a list of path parts, with a property of method handlers.
- * @param {string} pathDefinition
+ * A searchable path is a list of path parts, with a property of an object of method handlers.
+ * @param {String} pathDefinition
+ * @returns {Router~SearchPath}
+ * @private
*/
_pathDefinitionToPathMatch(pathDefinition) {
- const pathMatch = pathDefinition.split('/').map((p) => p.startsWith(this.paramPrefix) ? { [PARAM]: p.slice(this.paramPrefix.length) } : p);
+ const pathMatch = pathDefinition
+ .split('/')
+ .map((p) => p.startsWith(this.paramPrefix) ? { [PARAM]: p.slice(this.paramPrefix.length) } : p);
if (this.ignoreTrailingSlash
&& pathMatch[pathMatch.length - 1] === '') {
pathMatch.pop();
/**
* Compare checkPath to fixedPath, no param substitution, params must match.
- * @param {*} fixedPath
- * @param {*} checkPath
+ * @param {Router~SearchPath} fixedPath
+ * @param {Router~SearchPath} checkPath
+ * @returns {Boolean}
+ * @private
*/
static _pathCompareExact(fixedPath, checkPath) {
if (fixedPath.length !== checkPath.length) {
/**
* Compare checkPath to fixedPath, populating params.
- * @param {*} fixedPath
- * @param {*} checkPath
- * @param {*} returnParams
+ * @param {Router~SearchPath} fixedPath
+ * @param {Array<String>} checkPath
+ * @param {Object} returnParams
+ * @returns {Boolean}
+ * @private
*/
static _pathCompareParam(fixedPath, checkPath, returnParams = {}) {
const params = {};
}
+ /**
+ * @typedef Router~MatchedPath
+ * @property {Object} pathParams populated param fields
+ * @property {Router~SearchPath} matchedPath
+ */
/**
* Search for an existing path, return matched path and path parameters.
- * @param {Array} matchParts
+ * @param {Array<String>} matchParts
+ * @returns {Router~MatchedPath}
+ * @private
*/
_pathFind(matchParts) {
const result = {
/**
* Return a matching path, no param substitution, params must match
- * @param {*} matchParts
+ * @param {Router~SearchPath} matchParts
+ * @returns {Router~SearchPath=}
+ * @private
*/
_pathFindExact(matchParts) {
const pathsByLength = this.pathsByLength[matchParts.length];
}
+ /**
+ * @callback Router~HandlerFn
+ */
+ /**
+ * @typedef {Object} Router~PathHandler
+ * @property {Router~HandlerFn} handler
+ * @property {any[]} handlerArgs
+ */
/**
* Insert a new path handler.
* @param {string|string[]} methods
* @param {string} urlPath
- * @param {fn} handler
- * @param {*[]} handlerArgs
+ * @param {Router~HandlerFn} handler
+ * @param {any[]} handlerArgs
*/
on(methods, urlPath, handler, handlerArgs = []) {
const matchParts = this._pathDefinitionToPathMatch(urlPath);
/**
* Return an object, which contains a matching handler and any extra
* arguments, for a requested url.
- * Also sets path parameters on context.
+ * Also sets path named-parameters as #params and the matched path as
+ * #matchedPath on the context.
* @param {string} method
* @param {string[]} urlPath
* @param {object} ctx
- * @returns {object}
+ * @returns {Router~PathHandler}
*/
lookup(method, urlPath, ctx = {}) {
const pathParts = urlPath.split('/').map((part) => decodeURIComponent(part));