UNPKG

shapey

Version:

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

248 lines (214 loc) 9.17 kB
"use strict"; 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); }