UNPKG

action-u

Version:

Redux Action Generator (promoting action creators and types)

822 lines (730 loc) 28.5 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define("ActionU", [], factory); else if(typeof exports === 'object') exports["ActionU"] = factory(); else root["ActionU"] = factory(); })(this, function() { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // identity function for calling harmony imports with the correct context /******/ __webpack_require__.i = function(value) { return value; }; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 3); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; exports.__esModule = true; exports.default = generateActions; var _lodash = __webpack_require__(1); var _lodash2 = _interopRequireDefault(_lodash); var _lodash3 = __webpack_require__(2); var _lodash4 = _interopRequireDefault(_lodash3); var _verify = __webpack_require__(4); var _verify2 = _interopRequireDefault(_verify); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } /** * A higher-order function that mirrors the supplied * {{book.api.actionGenesis}} structure, returning an * {{book.api.ActionStruct}}, injecting generated action creators that * are decorated with their coresponding action types. * * {{book.guide.formalTypes}} diagrams this process. Examples can be * found throughout the {{book.guide.devGuide}}, starting with * {{book.guide.basics}}. * * @param {ActionGenesis} actionGenesis - an "organizational" JSON * structure that defines one or more action creators, with implicitly * defined action types (gleaned from the structure itself). * * @returns {ActionStruct} an action structure (a mirror of * {{book.api.actionGenesis}}), with generated action creators that * are decorated with their cooresponding action types. */ function generateActions(actionGenesis) { // validate actionGenesis var check = _verify2.default.prefix('ActionU.generateActions() parameter violation: '); check(actionGenesis, 'actionGenesis argument is required'); check((0, _lodash4.default)(actionGenesis), 'actionGenesis argument is NOT an object literal'); check(!actionGenesis.actionMeta, "actionGenesis argument CANNOT have an actionMeta property in the root (i.e. the root cannot be an ActionNode, because it is unnamed)"); // morph the supplied actionGenesis into an ActionStruct return morph2Runtime(actionGenesis, ''); } /** * The `generateActions.root()` function is identical to * {{book.api.generateActions}}, except it returns the single root * node of the {{book.api.ActionStruct}}, rather than the entire * structure. *This is useful for projects that organize their * actions in seperate JavaScript modules (see * {{book.guide.promotion}})*. * * @param {ActionGenesis} actionGenesis - an "organizational" JSON * structure that defines one or more action creators, with implicitly * defined action types (gleaned from the structure itself). For * `generateActions.root()`, this is **expected** to contain a single * root node (which will be returned). * * @returns {ActionStruct} **the root** action structure (a mirror of * {{book.api.actionGenesis}}), with generated action creators that * are decorated with their cooresponding action types. */ generateActions.root = function (actionGenesis) { // pass-through to generateActions() var actionStruct = generateActions(actionGenesis); // validae only one root node var rootNodeNames = Object.keys(actionGenesis); // ... use actionGenesis so as NOT to get the injected 'toString' of actionStruct var check = _verify2.default.prefix('ActionU.generateActions.root() parameter violation: '); check(rootNodeNames.length === 1, 'actionGenesis argument may ONLY contain a single root node (what will be returned) ... ' + rootNodeNames); // expose the ActionStruct root return actionStruct[rootNodeNames[0]]; }; //*** //*** Internals ... //*** var knownMetaProps = ['traits', 'ratify', 'thunk']; /** * Our default ActionMeta.ratify() function ... simply no-op. * @private */ function ratifyDefault() { for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return args; } /** * Morph the supplied genesisNode into a runtime node ... part of the * ActionStruct. * * @param {JSON-node} genesisNode - a node within the ActionGenisis * structure to be "morphed". * * @param {string} actionType - the accumulative action type * representing this genesisNode (within the overal ActionGenesis). * * @returns {JSON-node} a "morphed" run-time node representation of * the supplied genesisNode. * * @private */ function morph2Runtime(genesisNode, actionType) { // validate genesisNode var check = _verify2.default.prefix('ActionU.generateActions() actionGenesis node ' + actionType); check((0, _lodash4.default)(genesisNode), ' must be an object literal'); check(Object.keys(genesisNode).length > 0, ' must contain at least ONE sub-node (either an app-specific or an actionMeta node)'); // define our actionMeta (if any) var actionMeta = genesisNode.actionMeta; // morph the genesisNode into a runtimeNode (of the ActionStruct) var runtimeNode = null; if (actionMeta) { // *** node is an action creator (i.e. an ActionNode) // insure actionMeta is an object literal check((0, _lodash4.default)(actionMeta), '.actionMeta is NOT an object literal'); // insure all actionMeta properties are known var metaProps = Object.keys(actionMeta); var unknownMetaProps = metaProps.filter(function (prop) { return knownMetaProps.indexOf(prop) < 0; }); check(unknownMetaProps.length === 0, '.actionMeta contains unrecognized properties: ' + unknownMetaProps); // thunk definition if (actionMeta.thunk) { // insure actionMeta.thunk is a function check((0, _lodash2.default)(actionMeta.thunk), '.actionMeta.thunk is NOT a function ... ' + actionMeta.thunk); // insure no other actionMeta properties are provided check(metaProps.length === 1, '.actionMeta.thunk is NOT allowed with any other actionMeta property (found following properties: ' + metaProps + ')'); // thunks are simply injected directly in our ActionStruct (as is) runtimeNode = actionMeta.thunk; } // action creator generation (i.e. NON thunk) else { // insure actionMeta.traits (if supplied) is a string[] var traits = actionMeta.traits || []; check(Array.isArray(traits), '.actionMeta.traits is NOT a string[]'); // consider also lodash.isString() on each elm // insure actionMeta.ratify (if supplied) is a function var ratify = actionMeta.ratify || ratifyDefault; check((0, _lodash2.default)(ratify), '.actionMeta.ratify is NOT a function ... ' + ratify); // *** // *** THIS IS IT ... here is a generated action creator (i.e. an ActionNode) // *** runtimeNode = function runtimeNode() { for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } // apply app-specific action creator parameter validation/defaults args = ratify.apply(undefined, _toConsumableArray(args)); // apply standard validation (insuring correct number of arguments passed in) if (traits.length !== args.length) { // ex: ERROR: action-u action creator: userMsg.display(msg) expecting 1 parameters, but received 0. throw new TypeError('ERROR: action-u action creator: ' + actionType + '(' + traits.toString() + ') expecting ' + traits.length + ' parameters, but received ' + args.length + '.'); } // build/return our action object var action = { type: actionType }; // baseline action with it's type for (var i = 0; i < args.length; i++) { // inject the arguments into our action action[traits[i]] = args[i]; } return action; }; } // overload toString() to promote our action type runtimeNode.toString = function () { return actionType; }; } else { // *** node is an app-specific node (who's sub-structure will contain ActionNodes) // this is an app-specific node (who's sub-structure will contain ActionNodes) runtimeNode = {}; // overload toString() to ERROR (it is NOT an action type) // ex: ERROR: action-u ActionStruct: 'userMsg' is NOT an action type, RATHER an app-specific node. runtimeNode.toString = function () { throw new TypeError('ERROR: action-u ActionStruct: \'' + actionType + '\' is NOT an action type (rather an app-specific node).'); }; } // recurse further into the sub-structure for (var subNodeName in genesisNode) { if (subNodeName !== 'actionMeta') { // do NOT process actionMeta nodes - they are NOT app-specific // ... internal use only (processed by parent node) var subNode = genesisNode[subNodeName]; var delim = actionType ? '.' : ''; var subType = '' + actionType + delim + subNodeName; // morph nested sub-structure runtimeNode[subNodeName] = (0, _lodash4.default)(subNode) ? morph2Runtime(subNode, subType) : subNode; // support app-specific data in struct (just copy as is) // ... currently undocumented (because I'm not sure it is needed) } } // that's all folks :-) return runtimeNode; } //*** //*** Specifications (JSDoc tags only) //*** /** * @typedef {JSON} ActionGenesis * * ActionGenesis is a JSON meta structure (used by * {{book.api.generateActions}}) that provides the master definition * for the generated {{book.api.ActionStruct}}, promoting one or more * action creators and types. * * {{book.guide.formalTypes}} diagrams the action-u formal types. * * - The structure is app-specific and can employ depth to highlight * inner-relationships between various action creators. * * - Ultimately the structure defines one or more action creator * function nodes. Each of these nodes promote: * * * the action creator function itself, and * * * the action type, which is implicitly gleaned from the containing JSON * structure node accumulation (ex: `'widget.fetch'`) * * - Nodes containing an {{book.api.actionMeta}} property define an * {{book.api.ActionNode}} (i.e. an action creator). * * * The resultant corresponding node will be an action creator * function. The characteristics of this function is further * defined by {{book.api.actionMeta}} sub-properties (see * {{book.api.ActionMeta}}). * * * The action type is implied from the containing JSON structure * node accumulation (ex: `'widget.fetch.complete'`) and is * promoted through a string coercion of the action creator * function itself (i.e. the function's toString() has been * overloaded). * * - All other nodes are merely intermediate nodes that organize * (i.e. add meaning) to the overall shape of the promoted actions. * In the example below, `widget` is an intermediate node (i.e. it * is not an action creator). * * - Note that even {{book.api.ActionNodes}} may in turn contain * sub-structure (i.e. subordinate actions). In the example below, * `widget.fetch(selCrit)` is an action creator, an yet contains * subordinate actions: `widget.fetch.complete(widget)`. * * @example <caption>showing a standard set of fetch/complete/fail actions</caption> * { * widget: { * fetch: { * actionMeta: { * traits: ['selCrit'] * }, * complete: { * actionMeta: { * traits: ['widget'] * } * }, * fail: { * actionMeta: { * traits: ['err'] * } * } * } * } * } */ /** * @typedef {JSON} ActionMeta * * An ActionMeta is a sub-node (named `actionMeta`) in the * {{book.api.ActionGenesis}} that identifies it's parent as being an * {{book.api.ActionNode}} (i.e. an action creator). * * {{book.guide.formalTypes}} diagrams the action-u formal types. * * Supported properties of ActionMeta are: * * @property {string[]} traits - An array of names that serve BOTH as the: * <ul> * <li>expected parameter names of the action creator</li> * <li>and the Action property names (returned from the action creator)</li> * When NO `traits` property is supplied, the Action merely has NO properties * (other than it's `type` [of course]). * </ul> * Please refer to the {{book.guide.basics}} discussion for complete examples. * * @property {ratifyFn} ratify - An optional hook to validate and/or * default action creator parameters.<br/> * When NO `ratify` function is supplied, only simple validation is * performed *(ex: the number of arguments supplied)*. Please refer * to {{book.guide.validation}} and {{book.guide.default}} for * complete examples. * * @property {function} thunk - An action creator function that * promotes a thunk. When `thunk` is used, no other ActionMeta * properties are allowed. Please refer to {{book.guide.thunks}} for * a complete description. */ /** * @function ratifyFn * * @description * An optional hook of {{book.api.ActionMeta}} to validate and/or default * action creator parameters. * * - validation is accomplished by app-specific means (typically * thrown exceptions) * * - default parameters are accomplished by applying default semantics * and returning the arguments * * Please refer to {{book.guide.validation}} and * {{book.guide.default}} for complete examples. * * @param {...*} args - the parameters to this function should match * that of the action creator it is defining * * @returns {args} an array of the arguments passed in (potentially * defaulted). **NOTE**: You should never attempt to return the * built-in `arguments` array-like object for two reasons: **1.** * applied defaults are NOT reflected in `arguments`, and **2.** * `arguments` are not bound to arrow functions. */ /** * @typedef {JSON} ActionStruct * * ActionStruct is a JSON stucture which is a key aspect of action-u. * It: * - implicitly defines your action types, * - instinctively groups related actions, * - and seamlessly promotes both action creators and types throughout * your application. * * ActionStruct is a generated JSON run-time structure (output from * {{book.api.generateActions}}) that promotes a series of action * creators and types in an app-specific structure (mirroring the * shape of the {{book.api.ActionGenesis}}). * * {{book.guide.formalTypes}} diagrams the action-u formal types. * * - The structure is app-specific and can employ depth to highlight * inner-relationships between various action creators. * * - The structure defines one or more {{book.api.ActionNodes}} * (i.e. action creator functions). Each {{book.api.ActionNode}} * encapsolates BOTH the action creator and it's type. * * * The action creator function (the node itself) accepts the * desired parameters and returns a newly created action. * * * The action type is implied from the containing JSON structure * node accumulation (ex: `'widget.fetch.complete'`) and is * promoted through a string coercion of the action creator * function itself (i.e. the function's toString() has been * overloaded). * * - All other nodes are merely intermediate nodes that organize * (i.e. add meaning) to the overall shape of the promoted actions. * In the example below, `widget` is an intermediate node (i.e. it * is not an action creator). * * - Note that even {{book.api.ActionNodes}} may in turn contain sub-structure * (i.e. subordinate actions). In the example below, * `widget.fetch(selCrit)` is an action creator, an yet contains * subordinate actions: `widget.fetch.complete(widget)`. * * @example <caption>showing a standard set of fetch/complete/fail actions</caption> * { * widget: { * fetch(selCrit): { // action creator (impl omitted) - type promoted via string coercion of funct * complete(widget): {} // action creator (impl omitted) - type promoted via string coercion of funct * fail(err): {} // action creator (impl omitted) - type promoted via string coercion of funct * } * } * } */ /** * @function ActionNode * * @description * ActionNode is a generated action creator function that lives as a * JSON node in the {{book.api.ActionStruct}}. * * The ActionNode promotes it's action type through a string coercion * of the action creator function itself (i.e. the function's * toString() has been overloaded). * * {{book.guide.formalTypes}} diagrams the action-u formal types. * * @param {...*} args - the parameters are app-specific to this action * type. * * @returns {Action} a standard redux Action, specific to this action * type. */ /***/ }), /* 1 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /** * lodash 3.0.8 (Custom Build) <https://lodash.com/> * Build: `lodash modularize exports="npm" -o ./` * Copyright 2012-2016 The Dojo Foundation <http://dojofoundation.org/> * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE> * Copyright 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license <https://lodash.com/license> */ /** `Object#toString` result references. */ var funcTag = '[object Function]', genTag = '[object GeneratorFunction]'; /** Used for built-in method references. */ var objectProto = Object.prototype; /** * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) * of values. */ var objectToString = objectProto.toString; /** * Checks if `value` is classified as a `Function` object. * * @static * @memberOf _ * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. * @example * * _.isFunction(_); * // => true * * _.isFunction(/abc/); * // => false */ function isFunction(value) { // The use of `Object#toString` avoids issues with the `typeof` operator // in Safari 8 which returns 'object' for typed array constructors, and // PhantomJS 1.9 which returns 'function' for `NodeList` instances. var tag = isObject(value) ? objectToString.call(value) : ''; return tag == funcTag || tag == genTag; } /** * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * * @static * @memberOf _ * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is an object, else `false`. * @example * * _.isObject({}); * // => true * * _.isObject([1, 2, 3]); * // => true * * _.isObject(_.noop); * // => true * * _.isObject(null); * // => false */ function isObject(value) { var type = typeof value === 'undefined' ? 'undefined' : _typeof(value); return !!value && (type == 'object' || type == 'function'); } module.exports = isFunction; /***/ }), /* 2 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /** * lodash (Custom Build) <https://lodash.com/> * Build: `lodash modularize exports="npm" -o ./` * Copyright jQuery Foundation and other contributors <https://jquery.org/> * Released under MIT license <https://lodash.com/license> * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE> * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors */ /** `Object#toString` result references. */ var objectTag = '[object Object]'; /** * Checks if `value` is a host object in IE < 9. * * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a host object, else `false`. */ function isHostObject(value) { // Many host objects are `Object` objects that can coerce to strings // despite having improperly defined `toString` methods. var result = false; if (value != null && typeof value.toString != 'function') { try { result = !!(value + ''); } catch (e) {} } return result; } /** * Creates a unary function that invokes `func` with its argument transformed. * * @private * @param {Function} func The function to wrap. * @param {Function} transform The argument transform. * @returns {Function} Returns the new function. */ function overArg(func, transform) { return function (arg) { return func(transform(arg)); }; } /** Used for built-in method references. */ var funcProto = Function.prototype, objectProto = Object.prototype; /** Used to resolve the decompiled source of functions. */ var funcToString = funcProto.toString; /** Used to check objects for own properties. */ var hasOwnProperty = objectProto.hasOwnProperty; /** Used to infer the `Object` constructor. */ var objectCtorString = funcToString.call(Object); /** * Used to resolve the * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) * of values. */ var objectToString = objectProto.toString; /** Built-in value references. */ var getPrototype = overArg(Object.getPrototypeOf, Object); /** * Checks if `value` is object-like. A value is object-like if it's not `null` * and has a `typeof` result of "object". * * @static * @memberOf _ * @since 4.0.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is object-like, else `false`. * @example * * _.isObjectLike({}); * // => true * * _.isObjectLike([1, 2, 3]); * // => true * * _.isObjectLike(_.noop); * // => false * * _.isObjectLike(null); * // => false */ function isObjectLike(value) { return !!value && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) == 'object'; } /** * Checks if `value` is a plain object, that is, an object created by the * `Object` constructor or one with a `[[Prototype]]` of `null`. * * @static * @memberOf _ * @since 0.8.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. * @example * * function Foo() { * this.a = 1; * } * * _.isPlainObject(new Foo); * // => false * * _.isPlainObject([1, 2, 3]); * // => false * * _.isPlainObject({ 'x': 0, 'y': 0 }); * // => true * * _.isPlainObject(Object.create(null)); * // => true */ function isPlainObject(value) { if (!isObjectLike(value) || objectToString.call(value) != objectTag || isHostObject(value)) { return false; } var proto = getPrototype(value); if (proto === null) { return true; } var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor; return typeof Ctor == 'function' && Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString; } module.exports = isPlainObject; /***/ }), /* 3 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; exports.__esModule = true; exports.generateActions = undefined; var _generateActions = __webpack_require__(0); var _generateActions2 = _interopRequireDefault(_generateActions); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } //*** //*** Promote all library utilities through a single module. //*** // NOTE: This non-default export supports ES6 imports. // Example: // import { generateActions } from 'action-u'; // -or- // import * as ActionU from 'action-u'; exports.generateActions = _generateActions2.default; // NOTE: This default export supports CommonJS modules (otherwise Babel does NOT promote them). // Example: // const { generateActions } = require('action-u'); // -or- // const ActionU = require('action-u'); exports.default = { generateActions: _generateActions2.default }; /***/ }), /* 4 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; exports.__esModule = true; exports.default = verify; /** * A convenience assertion utility, typically used to validate * pre-conditions of a routine. * * **Advanced**: verify.prefix(msgPrefix) returns a higher-order * verify() function where all messages are prefixed. * * @param {truthy} condition - a "truthy" condition which * must be satisfied. * * @param {string} msg - a message clarifying the condition being * checked. * * @throws {Error} an Error is thrown when the supplied condition is * NOT met. * * @private */ function verify(condition, msg) { if (!condition) { throw new Error(msg); } } verify.prefix = function (msgPrefix) { return function (condition, msg) { return verify(condition, msgPrefix + msg); }; }; /***/ }) /******/ ]); });