@@ -0,0 +1,256 @@
'use strict';

var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];

var _WeakMap = require('babel-runtime/core-js/weak-map')['default'];

var _Object$keys = require('babel-runtime/core-js/object/keys')['default'];

var _Object$assign = require('babel-runtime/core-js/object/assign')['default'];

var _getIterator = require('babel-runtime/core-js/get-iterator')['default'];

var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];

exports.__esModule = true;

var _invariant = require('invariant');

var _invariant2 = _interopRequireDefault(_invariant);

var _reactRelay = require('react-relay');

var _reactRelay2 = _interopRequireDefault(_reactRelay);

var _getParamsForRoute = require('./getParamsForRoute');

var _getParamsForRoute2 = _interopRequireDefault(_getParamsForRoute);

var DEFAULT_KEY = 'default';

var RouteAggregator = (function () {
function RouteAggregator() {
_classCallCheck(this, RouteAggregator);

// We need to use a map to track route indices instead of throwing them on
// the route itself with a Symbol to ensure that, when rendering on the
// server, each request generates route indices independently.
this._routeIndices = new _WeakMap();
this._lastRouteIndex = 0;

this.route = null;
this._fragmentSpecs = null;

this._failure = null;
this._data = {};
this._readyState = null;
}

RouteAggregator.prototype.updateRoute = function updateRoute(_ref5) {
var _this = this;

var routes = _ref5.routes;
var components = _ref5.components;
var params = _ref5.params;
var location = _ref5.location;

var relayRoute = {
name: null,
queries: {},
params: {}
};
var fragmentSpecs = {};

routes.forEach(function (route, i) {
var _ref, _ref2;

var queries = route.queries;

if (!queries) {
return;
}

var component = components[i];

var isObject = typeof component === 'object';
var componentMap = isObject ? component : (_ref = {}, _ref[DEFAULT_KEY] = component, _ref);
var queryMap = isObject ? queries : (_ref2 = {}, _ref2[DEFAULT_KEY] = queries, _ref2);

_Object$keys(componentMap).forEach(function (key) {
/* eslint-disable no-shadow */
var component = componentMap[key];
var queries = queryMap[key];
/* eslint-enable no-shadow */

if (!queries) {
return;
}

// In principle not all container component routes have to specify
// queries, because some of them might somehow receive fragments from
// their parents, but it would definitely be wrong to specify queries
// for a component that isn't a container.
!_reactRelay2['default'].isContainer(component) ? process.env.NODE_ENV !== 'production' ? _invariant2['default'](false, 'relay-nested-routes: Route with queries specifies component `%s` ' + 'that is not a Relay container.', component.displayName || component.name) : _invariant2['default'](false) : undefined;

var routeParams = _getParamsForRoute2['default']({ route: route, routes: routes, params: params, location: location });
_Object$assign(relayRoute.params, routeParams);

_Object$keys(queries).forEach(function (queryName) {
var query = queries[queryName];
var uniqueQueryName = _this._getUniqueQueryName(route, key, queryName);

// Relay depends on the argument count of the query function, so try to
// preserve it as well as possible.
var wrappedQuery = undefined;
if (query.length === 0) {
// Relay doesn't like using the exact same query in multiple places,
// so wrap it to prevent that when sharing queries between routes.
wrappedQuery = function () {
return query();
};
} else {
// We just need the query function to have > 0 arguments.
/* eslint-disable no-unused-vars */
wrappedQuery = function (_) {
return query(component, routeParams);
};
/* eslint-enable */
}

relayRoute.queries[uniqueQueryName] = wrappedQuery;
fragmentSpecs[uniqueQueryName] = { component: component, queryName: queryName };
});
});
});

relayRoute.name = ['$$_aggregated'].concat(_Object$keys(relayRoute.queries)).join('-');

// RootContainer uses referential equality to check for route change, so
// replace the route object entirely.
this.route = relayRoute;
this._fragmentSpecs = fragmentSpecs;
};

RouteAggregator.prototype._getUniqueQueryName = function _getUniqueQueryName(route, key, queryName) {
// There might be some edge case here where the query changes but the route
// object does not, in which case we'll keep using the old unique name.
// Anybody who does that deserves whatever they get, though.

// Prefer an explicit route name if specified.
if (route.name) {
// The slightly different template here ensures that we can't have
// collisions with the below template.
return '$_' + route.name + '_' + key + '_' + queryName;
}

// Otherwise, use referential equality on the route name to generate a
// unique index.
var routeIndex = this._routeIndices.get(route);
if (routeIndex === undefined) {
routeIndex = ++this._lastRouteIndex;
this._routeIndices.set(route, routeIndex);
}

return '$$_route[' + routeIndex + ']_' + key + '_' + queryName;
};

RouteAggregator.prototype.setFailure = function setFailure(error, retry) {
this._failure = [error, retry];
};

RouteAggregator.prototype.setFetched = function setFetched(data, readyState) {
this._failure = null;
this._data = data;
this._readyState = readyState;
};

RouteAggregator.prototype.setLoading = function setLoading() {
this._failure = null;
};

RouteAggregator.prototype.getData = function getData(route, key, queries, params) {
if (key === undefined) key = DEFAULT_KEY;

// Check that the subset of parameters used for this route match those used
// for the fetched data.
for (var _iterator = _Object$keys(params), _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _getIterator(_iterator);;) {
var _ref3;

if (_isArray) {
if (_i >= _iterator.length) break;
_ref3 = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref3 = _i.value;
}

var paramName = _ref3;

if (this._data[paramName] !== params[paramName]) {
return this._getDataNotFound();
}
}

var fragmentPointers = {};
for (var _iterator2 = _Object$keys(queries), _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _getIterator(_iterator2);;) {
var _ref4;

if (_isArray2) {
if (_i2 >= _iterator2.length) break;
_ref4 = _iterator2[_i2++];
} else {
_i2 = _iterator2.next();
if (_i2.done) break;
_ref4 = _i2.value;
}

var queryName = _ref4;

var uniqueQueryName = this._getUniqueQueryName(route, key, queryName);

var fragmentPointer = this._data[uniqueQueryName];
if (!fragmentPointer) {
return this._getDataNotFound();
}

fragmentPointers[queryName] = fragmentPointer;
}

return {
fragmentPointers: fragmentPointers,
readyState: this._readyState
};
};

RouteAggregator.prototype._getDataNotFound = function _getDataNotFound() {
return { failure: this._failure };
};

RouteAggregator.prototype.getFragmentNames = function getFragmentNames() {
return _Object$keys(this._fragmentSpecs);
};

RouteAggregator.prototype.getFragment = function getFragment(fragmentName, variableMapping) {
var _fragmentSpecs$fragmentName = this._fragmentSpecs[fragmentName];
var component = _fragmentSpecs$fragmentName.component;
var queryName = _fragmentSpecs$fragmentName.queryName;

return component.getFragment(queryName, variableMapping);
};

RouteAggregator.prototype.hasFragment = function hasFragment(fragmentName) {
return this._fragmentSpecs[fragmentName] !== undefined;
};

RouteAggregator.prototype.hasVariable = function hasVariable(variableName) {
// It doesn't matter what the component variables are. The only variables
// we're going to pass down are the ones defined from our route parameters.
return this.route.params.hasOwnProperty(variableName);
};

return RouteAggregator;
})();

exports['default'] = RouteAggregator;
module.exports = exports['default'];
@@ -0,0 +1,130 @@
'use strict';

var _inherits = require('babel-runtime/helpers/inherits')['default'];

var _createClass = require('babel-runtime/helpers/create-class')['default'];

var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];

var _extends = require('babel-runtime/helpers/extends')['default'];

var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];

exports.__esModule = true;

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _reactStaticContainer = require('react-static-container');

var _reactStaticContainer2 = _interopRequireDefault(_reactStaticContainer);

var _getParamsForRoute = require('./getParamsForRoute');

var _getParamsForRoute2 = _interopRequireDefault(_getParamsForRoute);

var _RouteAggregator = require('./RouteAggregator');

var _RouteAggregator2 = _interopRequireDefault(_RouteAggregator);

var RouteContainer = (function (_React$Component) {
_inherits(RouteContainer, _React$Component);

function RouteContainer() {
_classCallCheck(this, RouteContainer);

_React$Component.apply(this, arguments);
}

RouteContainer.prototype.render = function render() {
var _props = this.props;
var Component = _props.Component;
var createElement = _props.createElement;
var queries = _props.queries;
var routerProps = _props.routerProps;
var key = routerProps.key;
var route = routerProps.route;
var routeAggregator = this.context.routeAggregator;

var params = _getParamsForRoute2['default'](routerProps);

var _routeAggregator$getData = routeAggregator.getData(route, key, queries, params);

var failure = _routeAggregator$getData.failure;
var fragmentPointers = _routeAggregator$getData.fragmentPointers;
var readyState = _routeAggregator$getData.readyState;

var shouldUpdate = true;
var element = undefined;

// This is largely copied from RelayRootContainer#render.
if (failure) {
var renderFailure = route.renderFailure;

if (renderFailure) {
var error = failure[0];
var retry = failure[1];

element = renderFailure(error, retry);
} else {
element = null;
}
} else if (fragmentPointers) {
var data = _extends({}, routerProps, params, fragmentPointers);

var renderFetched = route.renderFetched;

if (renderFetched) {
element = renderFetched(data, readyState);
} else {
element = createElement(Component, data);
}
} else {
var renderLoading = route.renderLoading;

if (renderLoading) {
element = renderLoading();
} else {
element = undefined;
}

if (element === undefined) {
element = null;
shouldUpdate = false;
}
}

return _react2['default'].createElement(
_reactStaticContainer2['default'],
{ shouldUpdate: shouldUpdate },
element
);
};

_createClass(RouteContainer, null, [{
key: 'displayName',
value: 'RouteContainer',
enumerable: true
}, {
key: 'propTypes',
value: {
Component: _react2['default'].PropTypes.func.isRequired,
createElement: _react2['default'].PropTypes.func.isRequired,
queries: _react2['default'].PropTypes.object.isRequired,
routerProps: _react2['default'].PropTypes.object.isRequired
},
enumerable: true
}, {
key: 'contextTypes',
value: {
routeAggregator: _react2['default'].PropTypes.instanceOf(_RouteAggregator2['default']).isRequired
},
enumerable: true
}]);

return RouteContainer;
})(_react2['default'].Component);

exports['default'] = RouteContainer;
module.exports = exports['default'];
@@ -0,0 +1,76 @@
'use strict';

var _getIterator = require('babel-runtime/core-js/get-iterator')['default'];

var _Object$assign = require('babel-runtime/core-js/object/assign')['default'];

var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];

exports.__esModule = true;
exports['default'] = getParamsForRoute;

var _invariant = require('invariant');

var _invariant2 = _interopRequireDefault(_invariant);

var _reactRouterLibGetRouteParams = require('react-router/lib/getRouteParams');

var _reactRouterLibGetRouteParams2 = _interopRequireDefault(_reactRouterLibGetRouteParams);

function getLocationParams(paramNames, paramSource) {
if (!paramNames) {
return null;
}

var paramsForRoute = {};
paramNames.forEach(function (name) {
var param = paramSource ? paramSource[name] : null;
paramsForRoute[name] = param !== undefined ? param : null;
});

return paramsForRoute;
}

function getParamsForRoute(_ref2) {
var route = _ref2.route;
var routes = _ref2.routes;
var params = _ref2.params;
var location = _ref2.location;

var paramsForRoute = {};

// Extract route params for current route and all ancestors.
for (var _iterator = routes, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _getIterator(_iterator);;) {
var _ref;

if (_isArray) {
if (_i >= _iterator.length) break;
_ref = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref = _i.value;
}

var ancestorRoute = _ref;

_Object$assign(paramsForRoute, _reactRouterLibGetRouteParams2['default'](ancestorRoute, params));
if (ancestorRoute === route) {
break;
}
}

_Object$assign(paramsForRoute, getLocationParams(route.queryParams, location.query), getLocationParams(route.stateParams, location.state));

var prepareParams = route.prepareParams;

if (prepareParams) {
!(typeof prepareParams === 'function') ? process.env.NODE_ENV !== 'production' ? _invariant2['default'](false, 'react-router-relay: Expected `prepareParams` to be a function.') : _invariant2['default'](false) : undefined;
paramsForRoute = prepareParams(paramsForRoute, route);
!(typeof paramsForRoute === 'object' && paramsForRoute !== null) ? process.env.NODE_ENV !== 'production' ? _invariant2['default'](false, 'react-router-relay: Expected `prepareParams` to return an object.') : _invariant2['default'](false) : undefined;
}

return paramsForRoute;
}

module.exports = exports['default'];
@@ -0,0 +1,17 @@
'use strict';

var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];

exports.__esModule = true;

var _RelayRouter2 = require('./RelayRouter');

var _RelayRouter3 = _interopRequireDefault(_RelayRouter2);

exports.RelayRouter = _RelayRouter3['default'];

var _RelayRouterContext2 = require('./RelayRouterContext');

var _RelayRouterContext3 = _interopRequireDefault(_RelayRouterContext2);

exports.RelayRouterContext = _RelayRouterContext3['default'];
@@ -45,6 +45,7 @@
"babel-loader": "5.x",
"babel-plugin-dev-expression": "^0.1.0",
"babel-relay-plugin": "0.6.0",
"chai": "^3.5.0",
"eslint": "^1.10.3",
"eslint-config-airbnb": "^2.1.1",
"eslint-plugin-react": "^3.13.1",
@@ -58,13 +59,16 @@
"karma-sinon-chai": "^1.1.0",
"karma-sourcemap-loader": "^0.3.6",
"karma-webpack": "^1.7.0",
"lolex": "^1.4.0",
"mocha": "^2.3.4",
"react": "^0.14.5",
"react-dom": "^0.14.5",
"react-relay": "^0.6.0",
"react-router": "^2.0.0-rc4",
"relay-local-schema": "^0.3.1",
"rimraf": "^2.5.0",
"sinon": "^1.17.3",
"sinon-chai": "^2.8.0",
"webpack": "^1.12.9"
}
}