shapey
Version:
A simple syntax for remapping objects, inspired by several of Ramda's spec based functions
248 lines (214 loc) • 9.17 kB
JavaScript
;
exports.__esModule = true;
exports.safeSpecTransforms = safeSpecTransforms;
exports.applyNonTransformProps = exports.removeMagicProps = exports.onlySpecTransforms = exports.makePruningSpec = exports.makeFunctionUnlessObject = exports.alwaysFunction = exports.objectify = void 0;
var _isObject = _interopRequireDefault(require("vanillas/isObject"));
var _mapObject = _interopRequireDefault(require("vanillas/mapObject"));
var _filter = _interopRequireDefault(require("vanillas/filter"));
var _curry = _interopRequireDefault(require("vanillas/curry"));
var _merge = _interopRequireDefault(require("vanillas/merge"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
/**
* Turns any non-Object values into empty objects
*
* @function
* @name objectify
* @sig * -> {k: v}
* @param {*} val A value of any type
* @returns {Object} If the original value was an Object, it is returned as-is, otherwise an empty object is returned
*/
var objectify = function objectify(val) {
return (0, _isObject.default)(val) ? val : {};
};
/**
* If the provided value is NOT a function, transform it into a function that always returns that value
*
* @function
* @name alwaysFunction
* @sig * -> (* -> *)
* @param {*} val A value of any type
* @returns {Function} If the original value was a function, that function is returned, otherwise a function that always returns the original value is returned function that always returns that value OR
*/
exports.objectify = objectify;
var alwaysFunction = function alwaysFunction(val) {
return typeof val === 'function' ? val : function () {
return val;
};
};
/**
* If the provided value is neither an Object nor a Function, transform it into a function that always returns that provided value.
*
* @function
* @name makeFunctionUnlessObject
* @sig * -> (* -> *)|{k: v}
* @param {*} val A value of any type
* @returns {Function|Object} A function that always returns the provided value OR the original value (if it was already a Function or an Object)
*/
exports.alwaysFunction = alwaysFunction;
var makeFunctionUnlessObject = function makeFunctionUnlessObject(val) {
return typeof val === 'function' || (0, _isObject.default)(val) ? val : function () {
return val;
};
};
/**
* Makes sure a given spec is acceptable for one of the pruning modes (ie, "remove" or "keep").
* Specs in this mode are treated more strictly, meaning the prop must be given a value of "true" or the key and value can be identical.
* Functions are acceptable values in the spec of course.
*
* @function
* @name makePruningSpec
* @sig {k: v} -> {k: v}
* @param {Object} spec A spec to be coerced into an acceptable pruning spec
* @returns {Object} A spec that is acceptable to be used in pruning mode.
*/
exports.makeFunctionUnlessObject = makeFunctionUnlessObject;
var makePruningSpec = function makePruningSpec(val) {
var obj = objectify(val);
return Object.fromEntries(Object.entries(obj).filter(function (_ref) {
var key = _ref[0],
v = _ref[1];
return key === v || v === true || typeof v === 'function';
}));
};
/**
* Filters a spec (object) to only the props that are transform functions.
* If the input is not an object, this is just an identity function.
*
* @function
* @name onlySpecTransforms
* @sig {k: v} -> {k: v}
* @param {Object} spec An object whose values (may) be transform functions
* @returns {Object} The input object with only the props that are functions retained
*/
exports.makePruningSpec = makePruningSpec;
var onlySpecTransforms = function onlySpecTransforms(val) {
if (!(0, _isObject.default)(val)) {
return val;
}
return Object.fromEntries(Object.entries(val).filter(function (_ref2) {
var key = _ref2[0],
v = _ref2[1];
return key === 'shapeyDebug' || typeof v === 'function';
}));
};
/**
* Removes the reserved "shapey*" prefixed props from a spec.
*
* @function
* @name removeMagicProps
* @sig {k: v} -> {k: v}
* @param {Object} spec A shapey spec to be pruned of magic props
* @returns {Object} A shapey spec cleaned of magic props
*/
exports.onlySpecTransforms = onlySpecTransforms;
var removeMagicProps = function removeMagicProps(val) {
return Object.fromEntries(Object.entries(val).filter(function (_ref3) {
var key = _ref3[0];
return !/^shapey/i.test(key);
}));
};
/**
* Takes an non-transform props (non-functions) in a given spec and merges them onto the input object.
*
* @function
* @name applyNonTransformProps
* @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 only the pass-through props applied to it
*/
exports.removeMagicProps = removeMagicProps;
var applyNonTransformProps = (0, _curry.default)(function (spec, value) {
return [spec, value].every(function (v) {
return (0, _isObject.default)(v);
}) ? (0, _merge.default)(value, (0, _filter.default)(function (v) {
return typeof v !== 'function';
}, removeMagicProps(spec))) : value;
});
/**
* Logs to the console any failed transform functions, along with the field name and the value that was fed into the transform function.
* Plus, the exception itself is also logged.
*
* @function
* @name defaultErrorHandler
* @sig String -> * -> Error -> *
* @param {String} fieldName The field name for the failed transform function
* @param {Object} value The value that was fed into the transform function
* @param {Object} exception The exception thrown by the failed transform
*/
exports.applyNonTransformProps = applyNonTransformProps;
var defaultErrorHandler = (0, _curry.default)(function (fieldName, value, exception) {
return (// eslint-disable-next-line no-console
console.error("\n Transform failed on field: \"" + fieldName + "\"\n value:", value, '\n', exception)
);
});
/**
* A wrapper around an (optional) error handler that the may be passed in as the "shapeyDebug" value.
* It is curried, but will feed their handler a more standard signature of: (err, field, value)
*
* @function
* @name wrapperForTheirErrorHandler
* @sig ((Error, String, *) -> *) -> String -> * -> Error -> *
* @param {Function} errHandler The custom error handler provided by the consumer
* @param {String} fieldName The field name for the failed transform function
* @param {Object} value The value that was fed into the transform function
* @param {Object} exception The exception thrown by the failed transform
* @returns {*} Could be anything, or nothing (it's up to the consumer)
*/
var wrapperForTheirErrorHandler = (0, _curry.default)(function (errHandler, fieldName, value, exception) {
return errHandler(exception, fieldName, value);
});
/**
* Wraps every function on a given spec in a try/catch that catches any exception.
*
* What it _does_ with the exception is up to the consumer:
* - ignores it (if "shapeyDebug" isn't set)
* - logs it along with the corresponding field name,
* using `console.error` (if "shapeyDebug" is set to `true`)
* - uses a custom handler supplied by the consumer as the value for "shapeyDebug"
*
* Note that a custom error handler will be passed the following params (in order):
* - The exception
* - The field name
* - The value that was fed into the transform function
*
* @function
* @name safeSpecTransforms
* @sig {k: (a -> b)} -> {k: (a -> b)}
* @param {Object} spec An object whose values may be transform functions that need to be wrapped in try/catch
* @returns {Object} The same spec object but whose functions are safely wrapped in in try/catch handlers
*/
function safeSpecTransforms(spec) {
var handleError = function handleError() {
return function () {
return undefined;
};
};
var _objectify = objectify(spec),
shapeyDebug = _objectify.shapeyDebug,
obj = _objectWithoutPropertiesLoose(_objectify, ["shapeyDebug"]);
if (shapeyDebug === true) {
handleError = defaultErrorHandler;
} else if (typeof shapeyDebug === 'function') {
handleError = wrapperForTheirErrorHandler(shapeyDebug);
} else if (/^skip$/i.test(shapeyDebug)) {
handleError = function handleError(_, originalValue) {
return function () {
return originalValue;
};
};
}
return (0, _mapObject.default)(function (transform, fieldName) {
if (typeof transform !== 'function') {
return transform;
}
return (0, _curry.default)(function (fn, val) {
try {
return fn(val);
} catch (err) {
return handleError(fieldName, val)(err);
}
})(transform);
}, obj);
}