Socket
Socket
Sign inDemoInstall

@sanity/state-router

Package Overview
Dependencies
1
Maintainers
6
Versions
702
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.0.3 to 0.1.0

.flowconfig

7

lib/components/Link.js

@@ -76,3 +76,2 @@ 'use strict';

value: function render() {
return _react2.default.createElement('a', _extends({}, (0, _omit2.default)(this.props, 'replace'), { onClick: this.handleClick }));

@@ -85,5 +84,2 @@ }

exports.default = Link;
Link.defaultProps = {

@@ -97,2 +93,3 @@ replace: false

__internalRouter: _react.PropTypes.object
};
};
exports.default = Link;

@@ -13,4 +13,2 @@ 'use strict';

var _ = require('../');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

@@ -24,48 +22,24 @@

var didWarn = false;
function validateProps(props) {
if (didWarn) {
return;
}
if (props.state && props.location) {
// eslint-disable-next-line no-console
console.error(new Error("[Warning] You passed both state and location to RouterProvider. If you pass 'state' you don't need to pass 'location' and vice versa"));
didWarn = true;
}
}
var RouterProvider = function (_React$Component) {
_inherits(RouterProvider, _React$Component);
function RouterProvider(props) {
function RouterProvider() {
var _ref;
var _temp, _this, _ret;
_classCallCheck(this, RouterProvider);
for (var _len = arguments.length, rest = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
rest[_key - 1] = arguments[_key];
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var _this = _possibleConstructorReturn(this, (_ref = RouterProvider.__proto__ || Object.getPrototypeOf(RouterProvider)).call.apply(_ref, [this, props].concat(rest)));
_this.navigateUrl = _this.navigateUrl.bind(_this);
_this.navigateState = _this.navigateState.bind(_this);
_this.resolvePathFromState = _this.resolvePathFromState.bind(_this);
validateProps(props);
return _this;
}
_createClass(RouterProvider, [{
key: 'navigateUrl',
value: function navigateUrl(url) {
return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = RouterProvider.__proto__ || Object.getPrototypeOf(RouterProvider)).call.apply(_ref, [this].concat(args))), _this), _this.navigateUrl = function (url) {
var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var replace = _ref2.replace;
var onNavigate = this.props.onNavigate;
var onNavigate = _this.props.onNavigate;
onNavigate(url, { replace: replace });
}
}, {
key: 'navigateState',
value: function navigateState(nextState) {
}, _this.navigateState = function (nextState) {
var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

@@ -75,15 +49,9 @@

this.navigateUrl(this.resolvePathFromState(nextState), { replace: replace });
}
}, {
key: 'resolvePathFromState',
value: function resolvePathFromState(state) {
return (0, _.resolvePathFromState)(this.props.router, state);
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
validateProps(nextProps);
}
}, {
_this.navigateUrl(_this.resolvePathFromState(nextState), { replace: replace });
}, _this.resolvePathFromState = function (state) {
return _this.props.router.encode(state);
}, _temp), _possibleConstructorReturn(_this, _ret);
}
_createClass(RouterProvider, [{
key: 'getChildContext',

@@ -94,3 +62,2 @@ value: function getChildContext() {

var location = _props.location;
var state = _props.state;

@@ -104,3 +71,3 @@ return {

navigate: this.navigateState,
state: state || (0, _.resolveStateFromPath)(router, location.pathname)
state: router.decode(location.pathname)
}

@@ -119,13 +86,12 @@ };

exports.default = RouterProvider;
RouterProvider.propTypes = {
state: _react.PropTypes.object,
children: _react.PropTypes.node,
onNavigate: _react.PropTypes.func,
router: _react.PropTypes.object,
onNavigate: _react.PropTypes.func,
location: _react.PropTypes.shape({
pathname: _react.PropTypes.string
})
}),
children: _react.PropTypes.node
};
exports.default = RouterProvider;
RouterProvider.childContextTypes = {

@@ -132,0 +98,0 @@ __internalRouter: _react.PropTypes.object,

@@ -43,3 +43,4 @@ 'use strict';

resolvePathFromState: function resolvePathFromState(nextState) {
return __internalRouter.resolvePathFromState(getUnscopedState(nextState));
var empty = Object.keys(nextState).length === 0;
return __internalRouter.resolvePathFromState(empty ? {} : addScope(nextState));
},

@@ -50,10 +51,10 @@ navigateUrl: __internalRouter.navigateUrl

navigate: function navigate(nextState, options) {
router.navigate(getUnscopedState(nextState), options);
router.navigate(addScope(nextState), options);
},
state: router.state[scope] || {}
state: router.state[scope]
}
};
function getUnscopedState(nextState) {
return Object.assign({}, router.state, _defineProperty({}, scope, nextState));
function addScope(nextState) {
return nextState && Object.assign({}, router.state, _defineProperty({}, scope, nextState));
}

@@ -71,12 +72,10 @@ }

exports.default = RouteScope;
RouteScope.propTypes = {
scope: _react.PropTypes.string
scope: _react.PropTypes.string,
children: _react.PropTypes.node
};
RouteScope.childContextTypes = RouteScope.contextTypes = {
__internalRouter: _react.PropTypes.object,
router: _react.PropTypes.object
};
};
exports.default = RouteScope;

@@ -58,3 +58,2 @@ 'use strict';

var nextState = toIndex ? EMPTY_STATE : state || EMPTY_STATE;
return this.context.__internalRouter.resolvePathFromState(nextState);

@@ -73,5 +72,7 @@ }

exports.default = StateLink;
StateLink.propTypes = {
state: _react.PropTypes.object,
replace: _react.PropTypes.bool,
toIndex: _react.PropTypes.bool
};
StateLink.defaultProps = {

@@ -81,8 +82,5 @@ replace: false,

};
StateLink.propTypes = {
state: _react.PropTypes.object,
replace: _react.PropTypes.bool
};
StateLink.contextTypes = {
__internalRouter: _react.PropTypes.object
};
};
exports.default = StateLink;

@@ -6,75 +6,24 @@ 'use strict';

});
exports.resolvePathFromState = exports.resolveStateFromPath = exports.route = undefined;
var _resolveStateFromPath = require('./resolveStateFromPath');
var _route = require('./route');
Object.defineProperty(exports, 'resolveStateFromPath', {
Object.defineProperty(exports, 'route', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_resolveStateFromPath).default;
return _interopRequireDefault(_route).default;
}
});
var _resolvePathFromState = require('./resolvePathFromState');
var _resolveStateFromPath = require('./resolveStateFromPath');
Object.defineProperty(exports, 'resolvePathFromState', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_resolvePathFromState).default;
}
});
var _resolveStateFromPath2 = _interopRequireDefault(_resolveStateFromPath);
var _createRoute = require('./createRoute');
var _resolvePathFromState = require('./resolvePathFromState');
Object.defineProperty(exports, 'createRoute', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_createRoute).default;
}
});
var _resolvePathFromState2 = _interopRequireDefault(_resolvePathFromState);
var _createScope = require('./createScope');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
Object.defineProperty(exports, 'createScope', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_createScope).default;
}
});
var _RouterProvider = require('./components/RouterProvider');
Object.defineProperty(exports, 'RouterProvider', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_RouterProvider).default;
}
});
var _Link = require('./components/Link');
Object.defineProperty(exports, 'Link', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_Link).default;
}
});
var _StateLink = require('./components/StateLink');
Object.defineProperty(exports, 'StateLink', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_StateLink).default;
}
});
var _RouteScope = require('./components/RouteScope');
Object.defineProperty(exports, 'RouteScope', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_RouteScope).default;
}
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
exports.resolveStateFromPath = _resolveStateFromPath2.default;
exports.resolvePathFromState = _resolvePathFromState2.default;

@@ -6,57 +6,49 @@ 'use strict';

});
exports.default = resolvePathFromState;
var _flatten = require('lodash/flatten');
var _flatten2 = require('lodash/flatten');
var _flatten2 = _interopRequireDefault(_flatten);
var _flatten3 = _interopRequireDefault(_flatten2);
var _difference = require('lodash/difference');
exports.default = resolvePathFromState;
var _difference2 = _interopRequireDefault(_difference);
var _findMatchingNodes = require('./findMatchingNodes');
var _extractParams = require('./utils/extractParams');
var _findMatchingNodes2 = _interopRequireDefault(_findMatchingNodes);
var _extractParams2 = _interopRequireDefault(_extractParams);
var _debug = require('./utils/debug');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function insertParams(pattern, params, splat) {
if (!pattern) {
return [];
}
return (0, _flatten2.default)(pattern.split('/').filter(Boolean).map(function (segment) {
if (segment.startsWith(':')) {
var paramName = segment.substring(1);
return params[paramName];
}
if (segment === '*') {
return splat;
}
return segment;
})).filter(Boolean);
}
function resolvePathFromState(node, state) {
(0, _debug.debug)('Resolving path from state %o', state);
function _resolvePath(routeNode, state) {
if (routeNode.isScope) {
return _resolvePath(routeNode.node, state[routeNode.name]);
var match = (0, _findMatchingNodes2.default)(node, state);
if (match.remaining.length > 0) {
var formatted = match.remaining.map(function (key) {
return key + ' (=' + JSON.stringify(state[key]) + ')';
}).join(', ');
throw new Error('State key' + (match.remaining.length == 1 ? '' : 's') + ' not mapped to url params: ' + formatted);
}
var requiredParams = (0, _extractParams2.default)(routeNode.pattern);
var missingParams = (0, _difference2.default)(requiredParams, Object.keys(state || {}));
if (missingParams.length > 0) {
return null;
if (match.nodes.length === 0) {
throw new Error('Unable to resolve path from given state: ' + JSON.stringify(state));
}
var childPath = null;
routeNode.children(state).some(function (childNode) {
childPath = _resolvePath(childNode, state);
return !!childPath;
});
var scopedState = state;
var relative = (0, _flatten3.default)(match.nodes.map(function (matchNode) {
if (matchNode.scope) {
scopedState = scopedState[matchNode.scope];
}
return matchNode.route.segments.map(function (segment) {
if (segment.type === 'dir') {
return segment.name;
}
var transform = node.transform && node.transform[segment.name];
return transform ? transform.toPath(scopedState[segment.name]) : scopedState[segment.name];
});
})).join('/');
return insertParams(routeNode.pattern, state).concat(childPath || []).join('/');
}
(0, _debug.debug)('Resolved to /%s', relative);
function resolvePathFromState(routeNode, state) {
return '/' + (_resolvePath(routeNode, state) || '');
return '/' + relative;
}

@@ -6,45 +6,57 @@ 'use strict';

});
exports.default = resolveStateFromPath;
var _httpHash = require('http-hash');
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _httpHash2 = _interopRequireDefault(_httpHash);
exports.default = resolveStateFromPath;
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _debug = require('./utils/debug');
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function resolveStateFromPath(routeNode, path) {
var result = {};
var hash = (0, _httpHash2.default)();
hash.set(routeNode.pattern, routeNode);
var match = hash.get(path);
function matchPath(node, path) {
var parts = path.split('/').filter(Boolean);
var segmentsLength = node.route.segments.length;
if (parts.length < segmentsLength) {
return null;
}
if (!match.handler) {
return {};
var state = {};
var isMatching = node.route.segments.every(function (segment, i) {
if (segment.type === 'dir') {
return segment.name === parts[i];
}
var transform = node.transform && node.transform[segment.name];
state[segment.name] = transform ? transform.toState(parts[i]) : parts[i];
return true;
});
if (!isMatching) {
return null;
}
Object.assign(result, match.params);
var rest = parts.slice(segmentsLength);
var childState = null;
var children = typeof node.children === 'function' ? node.children(state) : node.children;
if (children) {
children.some(function (childNode) {
childState = matchPath(childNode, rest.join('/'));
return childState;
});
}
if (match.splat != null) {
(function () {
// get matching child routes
var childRoutes = (0, _httpHash2.default)();
var childRouteNodes = routeNode.children(match.params);
childRouteNodes.forEach(function (childRouteNode) {
var pattern = (childRouteNode.isScope ? childRouteNode.node : childRouteNode).pattern;
childRoutes.set(pattern, childRouteNode);
});
var childMatch = childRoutes.get(match.splat || '/');
if (rest.length > 0 && !childState) {
return null;
}
if (childMatch.handler) {
var childNode = childMatch.handler;
var mergedState = _extends({}, state, childState);
return node.scope ? _defineProperty({}, node.scope, mergedState) : mergedState;
}
var childState = childNode.isScope ? _defineProperty({}, childNode.name, resolveStateFromPath(childNode.node, match.splat)) : resolveStateFromPath(childNode, match.splat);
function resolveStateFromPath(node, path) {
(0, _debug.debug)('resolving state from path %s', path);
Object.assign(result, childState);
}
})();
}
return result;
var pathMatch = matchPath(node, path.split('?')[0]);
(0, _debug.debug)('resolved: %o', pathMatch || null);
return pathMatch || null;
}
{
"name": "@sanity/state-router",
"version": "0.0.3",
"version": "0.1.0",
"description": "A path pattern => state object bidirectional mapper",

@@ -9,4 +9,8 @@ "main": "lib/index.js",

"test": "tap test/*.test.js --node-arg --require --node-arg ./.babel-init.js",
"compile": "babel --out-dir lib/ src/",
"test:lib:compile": "babel . -q --ignore node_modules,demo-server,perf --out-dir test-compiled",
"test:lib": "npm run test:lib:compile && cd test-compiled && tap test/**.test.js && cd - && npm run test:lib:clean",
"test:lib:clean": "rimraf test-compiled",
"compile": "npm run clean && babel --out-dir lib/ src/",
"compile:watch": "babel --watch --out-dir lib/ src/",
"benchmark": "npm run compile && node --prof --logfile=benchmarks.log perf/benchmark.js",
"prepublish": "in-publish && npm run compile || not-in-publish",

@@ -20,3 +24,2 @@ "postpublish": "in-publish && npm run clean || not-in-publish",

"dependencies": {
"http-hash": "^2.0.0",
"lodash": "^4.15.0"

@@ -27,4 +30,9 @@ },

"babel-core": "^6.14.0",
"babel-eslint": "^6.1.2",
"babel-eslint": "^7.0.0",
"babel-plugin-lodash": "^3.2.9",
"babel-plugin-syntax-class-properties": "^6.13.0",
"babel-plugin-syntax-flow": "^6.13.0",
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
"babel-plugin-transform-class-properties": "^6.16.0",
"babel-plugin-transform-flow-strip-types": "^6.14.0",
"babel-plugin-transform-object-rest-spread": "^6.8.0",

@@ -40,8 +48,9 @@ "babel-polyfill": "^6.13.0",

"eslint-config-bengler": "^2.0.0",
"eslint-plugin-import": "^1.14.0",
"eslint-plugin-flowtype-errors": "^1.4.1",
"eslint-plugin-import": "^2.0.1",
"eslint-plugin-react": "^6.2.0",
"express": "^4.14.0",
"history": "^3.2.1",
"flow-bin": "^0.33.0",
"history": "^4.3.0",
"in-publish": "^2.0.0",
"jest": "^15.1.1",
"object-inspect": "^1.2.1",

@@ -48,0 +57,0 @@ "quickreload": "^2.1.2",

@@ -13,2 +13,12 @@ import React, {PropTypes} from 'react'

export default class Link extends React.Component {
static defaultProps = {
replace: false,
}
static propTypes = {
replace: PropTypes.bool
}
static contextTypes = {
__internalRouter: PropTypes.object
}
constructor() {

@@ -40,15 +50,4 @@ super()

render() {
return <a {...omit(this.props, 'replace')} onClick={this.handleClick} />
}
}
Link.defaultProps = {
replace: false,
}
Link.propTypes = {
replace: PropTypes.bool
}
Link.contextTypes = {
__internalRouter: PropTypes.object
}
import React, {PropTypes} from 'react'
import {resolvePathFromState, resolveStateFromPath} from '../'
let didWarn = false
function validateProps(props) {
if (didWarn) {
return
}
if (props.state && props.location) {
// eslint-disable-next-line no-console
console.error(new Error(
"[Warning] You passed both state and location to RouterProvider. If you pass 'state' you don't need to pass 'location' and vice versa"
))
didWarn = true
}
}
export default class RouterProvider extends React.Component {
constructor(props, ...rest) {
super(props, ...rest)
this.navigateUrl = this.navigateUrl.bind(this)
this.navigateState = this.navigateState.bind(this)
this.resolvePathFromState = this.resolvePathFromState.bind(this)
validateProps(props)
static propTypes = {
onNavigate: PropTypes.func,
router: PropTypes.object,
location: PropTypes.shape({
pathname: PropTypes.string
}),
children: PropTypes.node
}
navigateUrl(url, {replace} = {}) {
navigateUrl = (url, {replace} = {}) => {
const {onNavigate} = this.props

@@ -32,16 +18,12 @@ onNavigate(url, {replace})

navigateState(nextState, {replace} = {}) {
navigateState = (nextState, {replace} = {}) => {
this.navigateUrl(this.resolvePathFromState(nextState), {replace})
}
resolvePathFromState(state) {
return resolvePathFromState(this.props.router, state)
resolvePathFromState = state => {
return this.props.router.encode(state)
}
componentWillReceiveProps(nextProps) {
validateProps(nextProps)
}
getChildContext() {
const {router, location, state} = this.props
const {router, location} = this.props
return {

@@ -54,3 +36,3 @@ __internalRouter: {

navigate: this.navigateState,
state: state || resolveStateFromPath(router, location.pathname)
state: router.decode(location.pathname)
}

@@ -64,11 +46,2 @@ }

}
RouterProvider.propTypes = {
state: PropTypes.object,
children: PropTypes.node,
router: PropTypes.object,
onNavigate: PropTypes.func,
location: PropTypes.shape({
pathname: PropTypes.string
})
}
RouterProvider.childContextTypes = {

@@ -75,0 +48,0 @@ __internalRouter: PropTypes.object,

import React, {PropTypes} from 'react'
export default class RouteScope extends React.Component {
static propTypes = {
scope: PropTypes.string,
children: PropTypes.node
}
static childContextTypes = RouteScope.contextTypes = {
__internalRouter: PropTypes.object,
router: PropTypes.object
}
getChildContext() {

@@ -10,3 +20,4 @@ const {scope} = this.props

resolvePathFromState: nextState => {
return __internalRouter.resolvePathFromState(getUnscopedState(nextState))
const empty = Object.keys(nextState).length === 0
return __internalRouter.resolvePathFromState(empty ? {} : addScope(nextState))
},

@@ -16,11 +27,11 @@ navigateUrl: __internalRouter.navigateUrl

router: {
navigate: (nextState, options)=> {
router.navigate(getUnscopedState(nextState), options)
navigate: (nextState, options) => {
router.navigate(addScope(nextState), options)
},
state: router.state[scope] || {}
state: router.state[scope]
}
}
function getUnscopedState(nextState) {
return Object.assign({}, router.state, {[scope]: nextState})
function addScope(nextState) {
return nextState && Object.assign({}, router.state, {[scope]: nextState})
}

@@ -33,10 +44,1 @@

}
RouteScope.propTypes = {
scope: PropTypes.string
}
RouteScope.childContextTypes = RouteScope.contextTypes = {
__internalRouter: PropTypes.object,
router: PropTypes.object
}

@@ -8,2 +8,17 @@ import React, {PropTypes} from 'react'

export default class StateLink extends React.Component {
static propTypes = {
state: PropTypes.object,
replace: PropTypes.bool,
toIndex: PropTypes.bool
}
static defaultProps = {
replace: false,
toIndex: false,
}
static contextTypes = {
__internalRouter: PropTypes.object
}
resolveUrl() {

@@ -20,3 +35,2 @@ const {toIndex, state} = this.props

const nextState = toIndex ? EMPTY_STATE : (state || EMPTY_STATE)
return this.context.__internalRouter.resolvePathFromState(nextState)

@@ -29,13 +43,1 @@ }

}
StateLink.defaultProps = {
replace: false,
toIndex: false,
}
StateLink.propTypes = {
state: PropTypes.object,
replace: PropTypes.bool
}
StateLink.contextTypes = {
__internalRouter: PropTypes.object
}

@@ -1,8 +0,7 @@

export {default as resolveStateFromPath} from './resolveStateFromPath'
export {default as resolvePathFromState} from './resolvePathFromState'
export {default as createRoute} from './createRoute'
export {default as createScope} from './createScope'
export {default as RouterProvider} from './components/RouterProvider'
export {default as Link} from './components/Link'
export {default as StateLink} from './components/StateLink'
export {default as RouteScope} from './components/RouteScope'
import resolveStateFromPath from './resolveStateFromPath'
import resolvePathFromState from './resolvePathFromState'
export {default as route} from './route'
export {resolveStateFromPath}
export {resolvePathFromState}

@@ -1,50 +0,39 @@

import flatten from 'lodash/flatten'
import difference from 'lodash/difference'
import extractParams from './utils/extractParams'
// @flow
import type {Node, MatchResult} from './types'
import findMatchingNodes from './findMatchingNodes'
import {flatten} from 'lodash'
import {debug} from './utils/debug'
function insertParams(pattern, params, splat) {
if (!pattern) {
return []
}
return flatten(
pattern.split('/')
.filter(Boolean)
.map(segment => {
if (segment.startsWith(':')) {
const paramName = segment.substring(1)
return params[paramName]
}
if (segment === '*') {
return splat
}
return segment
}))
.filter(Boolean)
}
export default function resolvePathFromState(node: Node, state: Object): string {
debug('Resolving path from state %o', state)
function _resolvePath(routeNode, state) {
if (routeNode.isScope) {
return _resolvePath(routeNode.node, state[routeNode.name])
const match: MatchResult = findMatchingNodes(node, state)
if (match.remaining.length > 0) {
const formatted = match.remaining.map(key => `${key} (=${JSON.stringify(state[key])})`).join(', ')
throw new Error(`State key${match.remaining.length == 1 ? '' : 's'} not mapped to url params: ${formatted}`)
}
const requiredParams = extractParams(routeNode.pattern)
const missingParams = difference(requiredParams, Object.keys(state || {}))
if (missingParams.length > 0) {
return null
if (match.nodes.length === 0) {
throw new Error(`Unable to resolve path from given state: ${JSON.stringify(state)}`)
}
let childPath = null
routeNode.children(state).some(childNode => {
childPath = _resolvePath(childNode, state)
return !!childPath
})
let scopedState = state
const relative = flatten(
match.nodes.map(matchNode => {
if (matchNode.scope) {
scopedState = scopedState[matchNode.scope]
}
return matchNode.route.segments.map(segment => {
if (segment.type === 'dir') {
return segment.name
}
const transform = node.transform && node.transform[segment.name]
return transform ? transform.toPath(scopedState[segment.name]) : scopedState[segment.name]
})
})
).join('/')
return insertParams(routeNode.pattern, state)
.concat(childPath || [])
.join('/')
}
debug('Resolved to /%s', relative)
export default function resolvePathFromState(routeNode, state) {
return `/${_resolvePath(routeNode, state) || ''}`
return `/${relative}`
}

@@ -1,36 +0,52 @@

import HttpHash from 'http-hash'
// @flow
import type {Node} from './types'
import {debug} from './utils/debug'
export default function resolveStateFromPath(routeNode, path) {
const result = {}
const hash = HttpHash()
hash.set(routeNode.pattern, routeNode)
const match = hash.get(path)
function matchPath(node : Node, path : string) : ?{[key: string]: string} {
const parts = path.split('/').filter(Boolean)
const segmentsLength = node.route.segments.length
if (parts.length < segmentsLength) {
return null
}
if (!match.handler) {
return {}
const state = {}
const isMatching = node.route.segments.every((segment, i) => {
if (segment.type === 'dir') {
return segment.name === parts[i]
}
const transform = node.transform && node.transform[segment.name]
state[segment.name] = transform ? transform.toState(parts[i]) : parts[i]
return true
})
if (!isMatching) {
return null
}
Object.assign(result, match.params)
if (match.splat != null) {
// get matching child routes
const childRoutes = HttpHash()
const childRouteNodes = routeNode.children(match.params)
childRouteNodes.forEach(childRouteNode => {
const pattern = (childRouteNode.isScope ? childRouteNode.node : childRouteNode).pattern
childRoutes.set(pattern, childRouteNode)
const rest = parts.slice(segmentsLength)
let childState = null
const children = typeof node.children === 'function' ? node.children(state) : node.children
if (children) {
children.some(childNode => {
childState = matchPath(childNode, rest.join('/'))
return childState
})
const childMatch = childRoutes.get(match.splat || '/')
}
if (childMatch.handler) {
const childNode = childMatch.handler
if (rest.length > 0 && !childState) {
return null
}
const childState = childNode.isScope
? {[childNode.name]: resolveStateFromPath(childNode.node, match.splat)}
: resolveStateFromPath(childNode, match.splat)
const mergedState = {...state, ...childState}
return node.scope ? {[node.scope]: mergedState} : mergedState
}
Object.assign(result, childState)
}
}
return result
export default function resolveStateFromPath(node : Node, path : string) : ?Object {
debug('resolving state from path %s', path)
const pathMatch = matchPath(node, path.split('?')[0])
debug('resolved: %o', pathMatch || null)
return pathMatch || null
}

@@ -0,10 +1,11 @@

// @flow
import test from './_util/test'
import createRoute from '../src/createRoute'
import createScope from '../src/createScope'
import route from '../src/route'
import type {Router} from '../src/types'
import resolvePathFromState from '../src/resolvePathFromState'
test('resolves empty state to fixed base path', t => {
const rootRoute = createRoute('/root/*', [
createRoute('/:page', [
createRoute('/:productId')
const rootRoute : Router = route('/root', [
route('/:page', [
route('/:productId')
])

@@ -15,20 +16,20 @@ ])

test('throws on unresolvable state', {todo: true}, t => {
const rootRoute = createRoute('/root/*', [
createRoute('/:page', [
createRoute('/:productId')
test('throws on unresolvable state', {todo: false}, t => {
const rootRoute = route('/root', [
route('/:page', [
route('/:productId')
])
])
t.throws(() => resolvePathFromState(rootRoute, {foo: 'bar'}), new Error('Unmappable state keys remaining: foo'))
t.throws(() => resolvePathFromState(rootRoute, {foo: 'bar'}), /.*not mapped .* params.*foo.*/)
})
test('Resolves this', t => {
const routesDef = createRoute('/:dataset/*', [
createRoute('/settings/:setting'),
createRoute('/tools/:tool/*', params => {
const routesDef = route('/:dataset', [
route('/settings/:setting'),
route('/tools/:tool', params => {
if (params.tool === 'desk') {
return createScope('desk', createRoute('/collections/:collection'))
return [route.scope('desk', '/collections/:collection')]
}
if (params.tool === 'another-tool') {
return createScope('foo', createRoute('/omg/:nope'))
return [route.scope('foo', '/omg/:nope')]
}

@@ -40,3 +41,3 @@ })

tool: 'another-tool',
'foo': {
foo: {
nope: 'foo'

@@ -43,0 +44,0 @@ },

@@ -0,48 +1,115 @@

// @flow
import type {Node} from '../src/types'
import test from './_util/test'
import createRoute from '../src/createRoute'
import createScope from '../src/createScope'
import resolveStateFromPath from '../src/resolveStateFromPath'
test('matches a flat route', t => {
const rootRoute = createRoute('/products/:productSlug')
t.same(resolveStateFromPath(rootRoute, '/products/nyan-cat'), {productSlug: 'nyan-cat'})
t.same(resolveStateFromPath(rootRoute, '/'), {})
})
const node : Node = {
route: {
raw: '/foo/:bar',
segments: [
{type: 'dir', name: 'foo'},
{type: 'param', name: 'bar'},
],
},
transform: {},
children: [
{
route: {
raw: '/dynamic/:foo',
segments: [
{type: 'dir', name: 'dynamic'},
{type: 'param', name: 'foo'},
],
},
transform: {},
children(state) {
if (state.foo === 'foo') {
return [{
route: {
raw: '/:whenfoo',
segments: [
{type: 'param', name: 'whenfoo'},
]
},
transform: {},
children: []
}]
}
return [{
route: {
raw: '/:notfoo',
segments: [
{type: 'param', name: 'notfoo'},
],
},
transform: {},
children: []
}]
}
},
{
route: {
raw: '/nix/:animal',
segments: [
{type: 'dir', name: 'nix'},
{type: 'param', name: 'animal'},
],
},
transform: {},
children: []
},
{
route: {
raw: '/qux/:animal',
segments: [
{type: 'dir', name: 'qux'},
{type: 'param', name: 'animal'},
]
},
transform: {
animal: {
toState: value => ({name: value.toUpperCase()}),
toPath: animal => animal.name.toLowerCase()
}
},
children: []
},
]
}
test('matches all levels in a nested route definition', t => {
const rootRoute = createRoute('/*', [
createRoute('/products/:productSlug')
])
t.same(resolveStateFromPath(rootRoute, '/products/nyan-cat'), {productSlug: 'nyan-cat'})
t.same(resolveStateFromPath(rootRoute, '/'), {})
})
const examples = [
['/foo/bar', {bar: 'bar'}],
['foo/bar', {bar: 'bar'}],
['foo/bar/baz', null],
['/foo/bar/qux/cat', {
animal: {name: 'CAT'},
bar: 'bar'
}],
['/foo/bar/nix/cat', {
animal: 'cat',
bar: 'bar'
}],
['/foo/bar/nix/cat', {
animal: 'cat',
bar: 'bar'
}],
['/nope/bar', null],
['/foo/bar/dynamic/foo/thisisfoo', {
bar: 'bar',
foo: 'foo',
whenfoo: 'thisisfoo',
}],
['/foo', null]
].filter(Boolean)
test('fails if two routes defines the same parameter', {todo: true}, t => {
const rootRoute = createRoute('/products/:foo/:foo')
t.throws(resolveStateFromPath(rootRoute, '/products/foo/foo'))
examples.forEach(([path, state]) => {
test(`path ${path} => ${JSON.stringify(state)}`, t => {
t.deepEqual(resolveStateFromPath(node, path), state)
})
})
test('matches according to router scope', t => {
const rootRoute = createRoute('/:category/*', [
createScope('product', createRoute('/products/:productSlug/*', [
createRoute('/:section')
]))
])
t.same(resolveStateFromPath(rootRoute, '/'), {})
t.same(resolveStateFromPath(rootRoute, '/imaginary-pets/products/nyan-cat/purchase'), {
category: 'imaginary-pets',
product: {
productSlug: 'nyan-cat',
section: 'purchase'
}
})
t.same(resolveStateFromPath(rootRoute, '/imaginary-pets/products/nyan-cat'), {
category: 'imaginary-pets',
product: {
productSlug: 'nyan-cat'
}
})
})
// IDEA! Can/should not map from a global param space to state because conflicts
// assert.deepEqual(resolveStateFromPath(node, '/foo/bar;flabla=flarb/nix/cat;family=mammal'), {
//
// })

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

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

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc