UNPKG

shapey

Version:

A simple syntax for remapping objects, inspired by several of Ramda's spec based functions

191 lines (165 loc) 9.22 kB
"use strict"; 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;