shapey
Version:
A simple syntax for remapping objects, inspired by several of Ramda's spec based functions
191 lines (165 loc) • 9.22 kB
JavaScript
;
exports.__esModule = true;
exports.default = exports.shapeline = exports.shapeStrictly = exports.shapeLoosely = exports.applyWholeObjectTransforms = void 0;
var _curried = require("vanillas/curried");
var _difference = _interopRequireDefault(require("vanillas/difference"));
var _merge = _interopRequireDefault(require("vanillas/merge"));
var _omit = _interopRequireDefault(require("vanillas/omit"));
var _pick = _interopRequireDefault(require("vanillas/pick"));
var _isEmpty = _interopRequireDefault(require("vanillas/isEmpty"));
var _isObject = _interopRequireDefault(require("vanillas/isObject"));
var _curry = _interopRequireDefault(require("vanillas/curry"));
var _prune = require("./prune");
var _util = require("./util");
var _alwaysEvolve = _interopRequireDefault(require("./alwaysEvolve"));
var _evolveSpec = _interopRequireDefault(require("./evolveSpec"));
var _mapSpec = _interopRequireDefault(require("./mapSpec"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Applies transform functions in a given spec to a given object, BUT only the
* trasnforms that DON'T match key names on the input object are applied.
*
* @function
* @name applyWholeObjectTransforms
* @sig {k: v} -> {k: v} -> {k: v}
* @param {Object} spec An object which contains transform functions (only those which do not correspond to keys on the input object will be applied to the input object)
* @param {Object} input An object which will be passed through the re-shaping transform functions defined by the spec
* @returns {Object} The input object with the spec transforms applied to it (only those that do not correspond to key names on the input object)
*/
var applyWholeObjectTransforms = (0, _curry.default)(function (spec, value) {
var nonFunctionPropNames = (0, _difference.default)(Object.keys((0, _curried.filter)(function (v) {
return typeof v === 'function';
}, spec)), Object.keys(value));
var nonFunctionProps = (0, _pick.default)(nonFunctionPropNames, spec);
return (0, _mapSpec.default)(nonFunctionProps);
});
exports.applyWholeObjectTransforms = applyWholeObjectTransforms;
var baseShaper = (0, _curry.default)(function (evolver, spec, value) {
var baseObject = (0, _util.applyNonTransformProps)(spec)(value);
var baseTransformedObject = evolver((0, _util.onlySpecTransforms)(spec))(baseObject);
var transformFn = applyWholeObjectTransforms(spec)(baseTransformedObject);
return (0, _merge.default)(baseTransformedObject, transformFn((0, _isEmpty.default)(baseTransformedObject) ? value : baseTransformedObject));
});
/**
* This function allows one to blend an object with a clone that is taken through prop transformations. Unlike object mappers/transformers, not every value in the spec need be a function.
* Non-function values are simply added to the result.
* If a given value in the spec IS a function however, it will be used to transform OR to create a prop.
* If the prop specified on the spec exists on the value (object) being mapped, a prop-level transform will be perform (just like [Ramda's evolve()](http://ramdajs.com/docs/#evolve)).
* But if a prop in the spec doesn't exist in the object and if that prop is a transformer function, then the transformer will be used to create that prop on the resulting object BUT the entire object will be passed into the transform function (just like [Ramda's applySpec()](http://ramdajs.com/docs/#applySpec), rather than just a single prop.
*
* @function
* @name shapeLoosely
* @sig {k: v} -> {k: v} -> {k: v}
* @param {Object} spec An object whose values are either transform functions or pass-through values to be added to the return object
* @param {Object} input An object which will be passed through the re-shaping transform functions defined by the spec
* @returns {Object} The input object with the spec transforms applied to it
*/
var shapeLoosely = baseShaper(_evolveSpec.default);
/**
* Applies a shaping spec to an input object, but will NOT pass through any props unless they are named in the spec.
* In other words, you're providing it template for creating a brand new object from some raw input.
*
* @function
* @name shapeStrictly
* @sig {k: v} -> {k: v} -> {k: v}
* @param {Object} spec An object whose values are either transform functions or pass-through values to be added to the return object
* @param {Object} input An object which will be passed through the re-shaping transform functions defined by the spec
* @returns {Object} The input object with the spec transforms applied to it
*/
exports.shapeLoosely = shapeLoosely;
var shapeStrictly = (0, _curry.default)(function (spec, input) {
var reshapedObj = shapeLoosely(spec, input);
return (0, _pick.default)(Object.keys(spec), reshapedObj);
});
exports.shapeStrictly = shapeStrictly;
var pruneOutput = (0, _curry.default)(function (spec, input) {
if (!(0, _isObject.default)(input)) {
return input;
}
if (typeof spec === 'function') {
return spec(input);
} else if ((0, _isObject.default)(spec)) {
switch ((spec.shapeyMode || '').toLowerCase()) {
case 'remove':
return (0, _prune.remover)(spec, input);
case 'keep':
return (0, _prune.keeper)(spec, input);
case 'strict':
return (0, _util.applyNonTransformProps)(spec)((0, _prune.impliedRemove)(spec, input));
default:
return (0, _util.applyNonTransformProps)(spec)(input);
}
} else {
return spec;
}
});
var applyTransforms = (0, _curry.default)(function (spec, input) {
if (typeof spec === 'function') {
return spec(input);
} else if ((0, _isObject.default)(spec)) {
switch ((spec.shapeyTransforms || '').toLowerCase()) {
case 'prop':
return (0, _alwaysEvolve.default)((0, _util.onlySpecTransforms)(spec))(input);
case 'whole':
return (0, _mapSpec.default)((0, _util.onlySpecTransforms)(spec))(input);
default:
return shapeLoosely((0, _util.onlySpecTransforms)(spec))(input);
}
} else {
return spec;
}
});
/**
* A single function that selects and applies one of the available re-shaping functions in the shapey library,
* depending on what you've set the `shapeyMode` prop to in the `spec` you provide as the first argument to this function.
*
* Think of it like a case statement in a Redux reducer, however since you most likely _don't_ want to sacrifice
* the meanining associated with the "type" property to internals of shapey, a prop called "shapeyMode" is used instead.
*
* If for some reason you have some prop on the input (the object you're transforming) already named "shapeyMode", um . . . don't.
*
* Available modes (case & space in-sensitive):
* "strict" - uses `shapeStrictly`, where _only_ the props named in your spec are included in the output
* "keep" - uses `keepAndShape`, where all the props you name in your spec are kept.
* "remove" - uses `removeAndShape`, where all the props you name in your spec are removed.
*
* In addition to controlling the mode for Shapey, you can control how the transforms are applied.
* This is controlled via the reserved prop in your spec called "shapeyTransforms", and the available options for it are:
* "prop" - All transforms are applied at the prop-level, regardless if they exist on the input object
* "whole" - All transforms are given the _entire_ input object as input
* (regardless if a prop matching the name of the transform exists on the input object)
*
* @function
* @name makeShaper
* @sig {k: v} -> {k: v} -> {k: v}
* @param {Object} spec An object whose values are either transform functions or pass-through values to be added to the return object
* @param {Object} input An object which will be passed through the re-shaping transform functions defined by the spec
* @returns {Object} The input object with the spec transforms applied to it
*/
var makeShaper = (0, _curry.default)(function (spec, input) {
var transformedObj = applyTransforms(spec, input);
var prunedObj = pruneOutput(spec, transformedObj);
return (0, _isObject.default)(prunedObj) ? (0, _omit.default)(['shapeyTransforms', 'shapeyMode'], prunedObj) : prunedObj;
});
/**
* Applies a list of functions (in sequence) to a single input, passing
* the transformed output as the input value to the next function in the chain.
* If a spec object is included in the transforms, the shaper that corresponds
* to the "shapeyMode" prop is invoked (otherwise `shapeLoosely()` is used).
* This creates a transform function for the pipeline.
*
* @function
* @name shapeline
* @sig [a -> b, b -> c, c -> d, ...] -> * -> *
* @param {Function[]} transforms A list of transform functions
* @param {*} input The input value to pass through the enhancer pipeline
* @returns {*} The output of the original input passed through the chain of transform functions
*/
var shapeline = (0, _curry.default)(function (transforms, input) {
return (0, _curried.reduce)(function (inputObj, fn) {
return fn(inputObj);
}, input, (0, _curried.map)(makeShaper, transforms || []));
});
exports.shapeline = shapeline;
var _default = makeShaper;
exports.default = _default;