New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@websdk/rhumb

Package Overview
Dependencies
Maintainers
2
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@websdk/rhumb - npm Package Compare versions

Comparing version 0.3.9 to 0.4.0

LICENSE.md

473

lib/rhumb.js

@@ -16,49 +16,53 @@ (function (global, factory) {

function findIn(parts, tree) {
var params = {};
function findIn(path, tree) {
var parsedPath = parse(path);
var find = function (remaining, node) {
var part = remaining.shift();
if (!part) return node.leaf || false;
var find = function (part, node, params) {
var segment = part.segments.shift();
if (node.fixed && part in node.fixed) {
return find(remaining, node.fixed[part]);
if (!segment) {
return node.leaf ? { fn: node.leaf, params: params } : false;
}
switch (segment.type) {
case 'fixed':
break;
case 'var':
throw new InvalidPathException('Must not contain a variable segment', path);
case 'partial':
throw new InvalidPathException('Must not contain a partial variable segment', path);
case 'empty':
throw new InvalidPathException('Must not contain an empty segment', path);
default:
throw new InvalidPathException('Must not contain an optional path', path);
}
if (node.fixed && segment.identifier in node.fixed) {
return find(part, node.fixed[segment.identifier], params);
}
if (node.partial) {
var tests = node.partial.tests,
found = tests.some(function (partial) {
if (partial.ptn.test(part)) {
var match = part.match(partial.ptn);
partial.vars.forEach(function (d, i) {
params[d] = match[i + 1];
});
node = partial;
return true;
found = tests.map(function (partial) {
var params = partial.matchFunction(segment.identifier);
return params ? { partial: partial, params: params } : null;
}).filter(falsy),
matchingPartial = found.length > 0 ? found[0] : null;
if (matchingPartial) {
for (var key in matchingPartial.params) {
params[key] = matchingPartial.params[key];
}
});
if (found) {
return find(remaining, node);
return find(part, matchingPartial.partial, params);
}
}
if (node['var']) {
params[node['var'].name] = part;
return find(remaining, node['var']);
if (node.variable) {
params[node.variable.identifier] = segment.identifier;
return find(part, node.variable, params);
}
return false;
};
var found = find(parts, tree, params);
if (found) {
return {
fn: found,
params: params
};
}
return false;
return find(parsedPath, tree, parsedPath.queryParams);
}

@@ -70,81 +74,113 @@

function updateTree(parts, node, fn) {
var part = parts.shift(),
more = !!parts.length,
function updateTree(part, node, route, fn) {
var segment = part.segments.shift(),
more = !!part.segments.length,
peek;
if (Array.isArray(part)) {
if (!segment) {
if (node.leaf) {
throw new Error('Ambiguity');
}
node.leaf = fn;
updateTree(part, node, fn);
return;
}
if (!part) return;
if (Array.isArray(segment.segments)) {
if (node.leaf) {
throw new Error('Ambiguity');
}
node.leaf = fn;
updateTree(segment, node, route, fn);
return;
}
if (part.type === "fixed") {
node.fixed || (node.fixed = {});
peek = node.fixed[part.input] || (node.fixed[part.input] = {});
switch (segment.type) {
case 'fixed':
if (!node.fixed) {
node.fixed = {};
}
if (!node.fixed[segment.identifier]) {
node.fixed[segment.identifier] = {};
}
peek = node.fixed[segment.identifier];
if (peek.leaf && !more) {
throw new Error("Ambiguity");
}
} else if (part.type === "var") {
if (node['var']) {
if (node['var'].name === part.input) {
peek = node['var'];
if (peek.leaf && !more) {
throw new Error('Ambiguity');
}
break;
case 'var':
if (node.variable) {
if (node.variable.identifier === segment.identifier) {
peek = node.variable;
} else {
throw new Error('Ambiguity');
}
} else {
throw new Error("Ambiguity");
node.variable = { identifier: segment.identifier };
peek = node.variable;
}
} else {
peek = node['var'] = {
name: part.input
};
}
} else if (part.type = "partial") {
if (node.partial) {
if (node.partial.names[part.name]) {
throw new Error("Ambiguity");
break;
case 'partial':
if (node.partial) {
if (node.partial.identifiers[segment.identifier]) {
throw new Error('Ambiguity');
}
} else {
node.partial = { identifiers: {}, tests: [] };
}
}
node.partial || (node.partial = {
names: {},
tests: []
});
peek = {};
peek.ptn = part.input;
peek.vars = part.vars;
node.partial.names[part.name] = peek;
node.partial.tests.push(peek);
peek = { matchFunction: segment.matchFunction };
node.partial.identifiers[segment.identifier] = peek;
node.partial.tests.push(peek);
break;
case 'empty':
throw new InvalidRouteException('Must not contain an empty segment', route);
}
if (!more) {
peek.leaf = fn;
} else {
updateTree(parts, peek, fn);
updateTree(part, peek, route, fn);
}
}
router.add = function (ptn, callback) {
updateTree(parse(ptn), tree, callback);
router.add = function (route, callback) {
var parsedRoute = parse(route);
if (Object.keys(parsedRoute.queryParams).length > 0) {
throw new InvalidRouteException('Must not contain a query string', route);
}
updateTree(parsedRoute, tree, route, callback);
};
router.match = function (path) {
var split = path.split("?").filter(falsy),
parts = ['/'].concat(split[0].split("/").filter(falsy)),
params = parseQueryString(split[1]),
match = findIn(parts, tree);
var match = findIn(path, tree);
if (match) {
for (var prop in match.params) {
params[prop] = match.params[prop];
}
return match.fn.apply(match.fn, [params]);
return match.fn.apply(match.fn, [match.params]);
}
};
return router;
}
function InvalidRouteException(message, route) {
this.message = message;
this.name = 'InvalidRouteException';
this.route = route;
this.toString = function () {
return 'Invalid route: ' + message;
};
}
function InvalidPathException(message, path) {
this.message = message;
this.name = 'InvalidPathException';
this.path = path;
this.toString = function () {
return 'Invalid path: ' + message;
};
}
function falsy(d) {

@@ -163,106 +199,223 @@ return !!d;

function parse(ptn) {
var variable = /^{(\w+)}$/,
partial = /([\w'-]+)?{([\w-]+)}([\w'-]+)?/,
bracks = /^[)]+/;
return ~ptn.indexOf('(') ? parseOptional(ptn) : parsePtn(ptn);
function asEmptySegment(pathSegment) {
return pathSegment === '' ? { type: 'empty' } : null;
}
function parseVar(part) {
var match = part.match(variable);
return {
type: "var",
input: match[1]
};
function asFixedSegment(pathSegment) {
return { type: 'fixed', identifier: pathSegment };
}
function asPartialSegment(pathSegment) {
var partialRegex = /([\w'-]+)?{([\w-]+)}([\w'-]+)?/,
match = pathSegment.match(partialRegex),
vars = [],
ptn = '',
len = pathSegment.length,
index = 0,
identifier = pathSegment.replace(/{([\w-]+)}/g, extractAndTransformVariableName);
if (!match) {
return null;
}
function parseFixed(part) {
return {
type: "fixed",
input: part
};
while (index < len && match) {
index += match[0].length;
if (match[1]) {
ptn += match[1];
}
ptn += '([\\w-]+)';
if (match[3]) {
ptn += match[3];
}
match = pathSegment.substr(index).match(partialRegex);
}
function parsePartial(part) {
var match = part.match(partial),
ptn = "",
len = part.length,
i = 0;
var matchRegex = new RegExp(ptn);
while (i < len && match) {
i += match[0].length;
return {
type: 'partial',
identifier: identifier,
matchFunction: matchFunction,
vars: vars
};
if (match[1]) {
ptn += match[1];
}
function extractAndTransformVariableName(matchedPattern, variableName) {
vars.push(variableName);
return '{var}';
}
ptn += "([\\w-]+)";
function matchFunction(segment) {
var segmentMatches = segment.match(matchRegex);
if (match[3]) {
ptn += match[3];
}
return segmentMatches ? vars.reduce(function (params, variable, index) {
params[variable] = segmentMatches[index + 1];
return params;
}, {}) : null;
}
}
match = part.substr(i).match(partial);
function asVarSegment(pathSegment) {
var match = pathSegment.match(/^{(\w+)}$/);
return match ? { type: 'var', identifier: match[1] } : null;
}
function parsePtn(ptn) {
return ptn.split('/').reduce(function (parsedRecord, pathSegment, index, allSegments) {
if (pathSegment === '' && index === 0) {
parsedRecord.leadingSlash = allSegments.length > 1;
} else if (pathSegment === '' && index === allSegments.length - 1) {
parsedRecord.trailingSlash = true;
} else {
var newSegment = asEmptySegment(pathSegment) || asVarSegment(pathSegment) || asPartialSegment(pathSegment) || asFixedSegment(pathSegment);
parsedRecord.segments.push(newSegment);
}
return parsedRecord;
}, { leadingSlash: false, segments: [], trailingSlash: false });
}
var vars = [],
name = part.replace(/{([\w-]+)}/g, function (p, d) {
vars.push(d);
return "{var}";
});
return {
type: "partial",
input: new RegExp(ptn),
name: name,
vars: vars
};
function parseOptional(ptn) {
var out = '';
var i = 0,
len = ptn.length,
isOptionalSegment = false;
while (!isOptionalSegment && i < len) {
var curr = ptn.charAt(i);
switch (curr) {
case ')':
case '(':
isOptionalSegment = true;
break;
default:
out += curr;
break;
}
i++;
}
function parsePtn(ptn) {
return ['/'].concat(ptn.split("/")).filter(falsy).map(function (d) {
if (variable.test(d)) {
return parseVar(d);
}
var parsedOutput = parsePtn(out);
if (isOptionalSegment) {
var optionalSegment = parseOptional(ptn.substr(i));
if (optionalSegment.segments.length > 0) {
parsedOutput.segments.push(optionalSegment);
parsedOutput.trailingSlash = optionalSegment.trailingSlash;
}
}
if (partial.test(d)) {
return parsePartial(d);
}
return parsedOutput;
}
return parseFixed(d);
});
function parse(route) {
var split = route.split('?'),
parsedPath = split[0].indexOf('(/') > -1 ? parseOptional(split[0]) : parsePtn(split[0]);
parsedPath.queryParams = parseQueryString(split[1]);
parsedPath.queryParamsString = split[1] ? '?' + split[1] : null;
return parsedPath;
}
function tryReadingParamValue(params, key) {
if (!(key in params)) {
throw new Error('Invalid parameter: "' + key + '" is not supplied');
}
function parseOptional(ptn) {
var out = "",
list = [];
var i = 0,
len = ptn.length,
onePart = true;
var value = params[key];
switch (value) {
case '':
throw new Error('Invalid parameter: "' + key + '" is an empty value');
case undefined:
throw new Error('Invalid parameter: "' + key + '" is undefined');
case null:
throw new Error('Invalid parameter: "' + key + '" is null');
default:
return value;
}
}
while (onePart && i < len) {
var curr = ptn.charAt(i);
function joinPaths(path, item) {
var pathStartingWithSlashes = /^\/?(%2F)+$/;
if (path === '' || path.match(pathStartingWithSlashes) || path[path.length - 1] === '/') {
return path + item;
}
return path + '/' + item;
}
switch (curr) {
case ")":
case "(":
onePart = false;
break;
function interpolateEmpty(path) {
return path + '%2F';
}
function interpolateVar(path, segment, params) {
var value = tryReadingParamValue(params, segment.identifier);
return joinPaths(path, value);
}
function interpolateFixed(path, segment) {
return joinPaths(path, segment.identifier);
}
function interpolatePartial(path, segment, params) {
var i = 0,
match = segment.identifier.replace(/\{var\}/g, function () {
var varName = segment.vars[i++],
value = tryReadingParamValue(params, varName);
return value;
});
return joinPaths(path, match);
}
function interpolateOptional(path, optionalSegment, params) {
try {
return joinPaths(path, optionalSegment.segments.reduce(function (optionalPath, segment) {
switch (segment.type) {
case 'empty':
return interpolateEmpty(optionalPath);
case 'var':
return interpolateVar(optionalPath, segment, params);
case 'partial':
return interpolatePartial(optionalPath, segment, params);
case 'fixed':
return interpolateFixed(optionalPath, segment);
default:
out += curr;
break;
return optionalPath ? interpolateOptional(optionalPath, segment, params) : '';
}
}, ''));
} catch (ex) {
return path;
}
}
i++;
function interpolate(route, params) {
var parsedRoute = parse(route),
queryParamsString = parsedRoute.queryParamsString,
interpolatedPath = parsedRoute.segments.reduce(function (path, segment) {
switch (segment.type) {
case 'empty':
return interpolateEmpty(path);
case 'var':
return interpolateVar(path, segment, params);
case 'partial':
return interpolatePartial(path, segment, params);
case 'fixed':
return interpolateFixed(path, segment);
default:
return interpolateOptional(path, segment, params);
}
}, parsedRoute.leadingSlash ? '/' : '');
if (!onePart) {
var next = parseOptional(ptn.substr(i + 1)).slice(1);
if (parsedRoute.trailingSlash) {
interpolatedPath = joinPaths(interpolatedPath, '');
}
if (next.length) {
list.push(next);
}
}
if (parsedRoute.queryParamsString) {
interpolatedPath += queryParamsString;
}
return parsePtn(out).concat(list);
}
return interpolatedPath;
}

@@ -272,4 +425,6 @@

rhumb.create = create;
rhumb.interpolate = interpolate;
rhumb._parse = parse;
rhumb._findInTree = findIn;
module.exports = rhumb;

@@ -276,0 +431,0 @@ });

{
"name": "@websdk/rhumb",
"version": "0.3.9",
"version": "0.4.0",
"description": "URL routing in js",

@@ -28,6 +28,3 @@ "main": "lib/rhumb.js",

},
"homepage": "https://github.com/sammyt/rhumb#readme",
"scripts": {
"prepublish": "make clean build"
}
"homepage": "https://github.com/sammyt/rhumb#readme"
}
Rhumb
=====
routing, this and that.
Rhumb is a highly efficient and flexible router.
Given a URI – where the constituent parts may be fixed or variable – Rhumb will unambiguously and with negligable overhead find a matching function, then apply it with whatever parameters might have been extracted from the URI.
[Read more](RATIONALE.md).
Bells and Whistles

@@ -15,2 +19,3 @@ ------------------

* parameter parsing
* interpolate params to produce paths

@@ -43,2 +48,8 @@

When you need to create a URI from a set of params, the `interpolate` function can be used
```javascript
redShoesUri = rhumb.interpolate("/happy/shoes/{color}", { color: "red" })
```
Route Syntax

@@ -121,8 +132,113 @@ ------------

Have fun!
Parameter Interpolation
-----------------------
Have fun!
Rhumb allows you to take a route and a set of params and produce a path that can be matched against a route.
---
#### fixed paths
[LICENSE](LICENSE)
When you give `.interpolate(...)` a route without any declared variables or partial variable parts, then a valid path will be returned and you will typically not see any changes:
```javascript
rhumb.interpolate("/stories", {})
// returns "/stories"
rhumb.interpolate("/stories?sortBy=publishedDate", {})
// returns "/stories?sortBy=publishedDate"
```
When the route you supplied is not a valid path, Rhumb will step in and escape some of the characters, so that a valid path can be produced.
If your route has empty parts, then some of the slash characters will be encoded to `%2F`:
```javascript
rhumb.interpolate('//sarah/scary', {})
// returns "/%2Fsarah/scary"
rhumb.interpolate('stories//scary', {})
// returns "stories%2F/scary"
```
#### variable parts
When variables are present, Rhumb will interpolate the variables with the params you supply:
```javascript
rhumb.interpolate("/potatoes/{variety}", { variety: "marabel" })
// returns "/potatoes/marabel"
rhumb.interpolate("/shoes/{color}/{size}", { color: "red", size: "6" })
// returns "/shoes/red/6"
```
For interpolation to produce a valid path, it will throw an error when a required variable is absent, `""`, `null` or `undefined`:
```javascript
rhumb.interpolate("/potatoes/{variety}", {})
// throws 'Invalid parameter: "variety" is missing'
rhumb.interpolate("/shoes/{color}/{size}", { color: "red", size: null })
// throws 'Invalid parameter: "size" is null'
```
To mark a variable part as not-required, it has to be wrapped in an optional path, as shown later.
#### partially variable parts
Like variables, Rhumb will interpolate partially variable parts when they are defined and not empty in the supplied params:
```javascript
rhumb.interpolate("/orders/{days}-days-ago", { days: "40" })
// returns "/orders/40-days-ago"
rhumb.interpolate("/author/{forename}-{surname}", { forename: "susan", surname: "smith" })
// returns "/author/susan-smith"
```
It will also throw an error when a required partial variable is absent, `""`, `null` or `undefined`:
```javascript
rhumb.interpolate("/orders/{days}-days-ago", { days: "" })
// throws 'Invalid parameter: "days" is empty'
rhumb.interpolate("/author/{forename}-{surname}", { forename: "susan", surname: undefined })
// throws 'Invalid parameter: "surname" is undefined'
```
To mark a partially variable part as not-required, it has to be wrapped in an optional path, as shown later.
#### optional parts
Rhumb is greedy with how it handles optional paths when interpolating, so expect optional parts to be included whenever possible.
```javascript
rhumb.interpolate("/stories(/bob)", {})
// returns "/stories/bob"
rhumb.interpolate("/stories(/sarah(/scary))", {})
// returns "/stories/sarah/scary"
```
When variables or partially variables in optional parts are absent, `""`, `null` or `undefined` then no error is thrown and the optional part is dropped.
```javascript
rhumb.interpolate("/stories(/by-{name})", {})
// returns "/stories"
rhumb.interpolate("/stories(/{author}(/{genre}))", { author: "sarah", genre: "" })
// returns "/stories/sarah"
```
Found an issue, or want to contribute?
--------------------------------------
If you find an issue, want to start a discussion on something related to this project, or have suggestions on how to improve it? Please [create an issue](../../issues/new)!
See an error and want to fix it? Want to add a file or otherwise make some changes? All contributions are welcome! Please refer to the [contribution guidelines](CONTRIBUTING.md) for more information.
License
-------
Please refer to the [license](LICENSE.md) for more information on licensing and copyright information.

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc