ladda-cache
Advanced tools
Comparing version 0.1.1 to 0.1.2
1042
dist/bundle.js
@@ -1,1 +0,1041 @@ | ||
module.exports=function(r){function t(e){if(n[e])return n[e].exports;var o=n[e]={exports:{},id:e,loaded:!1};return r[e].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=r,t.c=n,t.p="",t(0)}([function(r,t,n){"use strict";var e=n(5);r.exports={build:e.build}},function(r,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function r(r,t){var n=[],e=!0,o=!1,u=void 0;try{for(var i,a=r[Symbol.iterator]();!(e=(i=a.next()).done)&&(n.push(i.value),!t||n.length!==t);e=!0);}catch(c){o=!0,u=c}finally{try{!e&&a["return"]&&a["return"]()}finally{if(o)throw u}}return n}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return r(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),e=(t.debug=function(r){return console.log(r),r},t.identity=function(r){return r}),o=t.curry=function O(r){return function(){for(var t=arguments.length,n=Array(t),e=0;t>e;e++)n[e]=arguments[e];var o=r.length;return n.length<o?O(r.bind.apply(r,[null].concat(n))):r.apply(void 0,n)}},u=t.passThrough=o(function(r,t){return r(t),t}),i=(t.startsWith=o(function(r,t){return 0===t.indexOf(r)}),t.join=o(function(r,t,n){return t+r+n}),t.on=o(function(r,t,n,e){return r(t(e),n(e))}),t.isEqual=o(function(r,t){return r===t}),t.on2=o(function(r,t,n,e,o){return r(t(e),n(o))})),a=(t.init=function(r){return r.slice(0,r.length-1)},t.tail=function(r){return r.slice(1,r.length)}),c=t.last=function(r){return r[r.length-1]},f=(t.head=function(r){return r[0]},t.map=o(function(r,t){return t.map(r)})),l=t.map_=o(function(r,t){f(r,t)}),s=t.reverse=function(r){return r.slice().reverse()},p=t.reduce=o(function(r,t,n){return l(function(n){t=r(t,n)},n),t}),y=(t.compose=function(){for(var r=arguments.length,t=Array(r),n=0;r>n;n++)t[n]=arguments[n];return function(){return p(function(r,t){return t(r)},c(t).apply(void 0,arguments),a(s(t)))}},t.prop=o(function(r,t){return t[r]})),v=t.zip=function(r,t){for(var n=Math.min(r.length,t.length),e=[],o=0;n>o;o++)e.push([r[o],t[o]]);return e},d=t.flip=function(r){return o(function(t,n){return r(n,t)})},h=t.toPairs=function(r){var t=Object.keys(r),n=d(y)(r);return v(t,f(n,t))},m=(t.fromPairs=function(r){var t=function(r,t){var e=n(t,2),o=e[0],i=e[1];return u(function(){return r[o]=i},r)};return p(t,{},r)},t.mapObject=i(f,e,h),o(function(r,t,n){return r[t]=n,r})),b=(t.mapValues=o(function(r,t){var n=Object.keys(t);return p(function(n,e){return n[e]=r(t[e]),n},{},n)}),t.toObject=o(function(r,t){return p(function(t,n){return m(t,r(n),n)},{},t)}),o(function(r,t,n){return r(n)&&t.push(n),t}));t.filter=o(function(r,t){return p(b(r),[],t)})},function(r,t,n){"use strict";function e(r){if(Array.isArray(r)){for(var t=0,n=Array(r.length);t<r.length;t++)n[t]=r[t];return n}return Array.from(r)}Object.defineProperty(t,"__esModule",{value:!0}),t.createEntityStore=t.contains=t.get=t.put=t.remove=void 0;var o=function(){function r(r,t){var n=[],e=!0,o=!1,u=void 0;try{for(var i,a=r[Symbol.iterator]();!(e=(i=a.next()).done)&&(n.push(i.value),!t||n.length!==t);e=!0);}catch(c){o=!0,u=c}finally{try{!e&&a["return"]&&a["return"]()}finally{if(o)throw u}}return n}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return r(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),u=n(12),i=n(1),a=function(r){return{value:r,timestamp:Date.now()}},c=function(r,t){var n=o(r,2),e=(n[0],n[1]);return e[t]},f=function(r,t,n){var e=o(r,2),u=(e[0],e[1]);return u[t]=a(n)},l=(0,i.curry)(function(r,t){var n=o(r,2),e=(n[0],n[1]);return delete e[t]}),s=function(r){return r.viewOf||r.name},p=function(r,t){var n=o(r,2),u=n[0],a=n[1],c=s(t),f=[].concat(e(u[c]));(0,i.map_)(l([u,a]),f)},y=function(r,t){return s(r)+t.id},v=function(r,t){return r.name+t.id},d=function(r){return!!r.viewOf},h=(t.remove=function(r,t,n){l(r,y(t,{id:n})),p(r,t)},(0,i.curry)(function(r,t,n,e,o){return d(e)?r(n,e,o):t(n,e,o)})),m=function(r,t,n){return!!c(r,y(t,n))},b=function(r,t,n){if(!n.id)throw new Error("Value is missing id, tried to add to entity "+t.name);var e=y(t,n);return f(r,e,n),n},O=function(r,t,n){if(!n.id)throw new Error("Value is missing id, tried to add to view "+t.name);if(m(r,t,n)){var e=c(r,y(t,n)).value;b(r,t,(0,u.merge)(n,e)),p(r,t)}else{var o=v(t,n);f(r,o,n)}return n},g=(t.put=h(O,b),function(r,t,n){var e=y(t,{id:n});return c(r,e)}),j=function(r,t,n){var e=c(r,y(t,{id:n})),o=c(r,v(t,{id:n})),u=o&&!e;return u?o:e},A=(t.get=h(j,g),t.contains=function(r,t,n){return!!h(j,g)(r,t,n)},function(r,t){var n=o(r,2),e=n[0],u=n[1];return e[t.viewOf]||(e[t.viewOf]=[]),e[t.viewOf].push(t.name),[e,u]}),w=function(r,t){var n=o(r,2),e=n[0],u=n[1];return e[t.name]||(e[t.name]=[]),[e,u]},_=function(r,t){return d(t)?A(r,t):w(r,t)};t.createEntityStore=function(r){return(0,i.reduce)(_,[{},{}],r)}},function(r,t,n){"use strict";function e(r){if(Array.isArray(r)){for(var t=0,n=Array(r.length);t<r.length;t++)n[t]=r[t];return n}return Array.from(r)}Object.defineProperty(t,"__esModule",{value:!0}),t.createQueryCache=t.invalidate=t.get=t.contains=t.getValue=t.put=void 0;var o=Object.assign||function(r){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var e in n)Object.prototype.hasOwnProperty.call(n,e)&&(r[e]=n[e])}return r},u=n(2),i=n(1),a=n(4),c=(0,i.on2)((0,i.reduce)((0,i.join)("-")),(0,i.prop)("name"),(0,i.map)(a.serialize)),f=function(r){return{value:r,timestamp:Date.now()}},l=(0,i.prop)("value"),s=function(r,t){return!!r.cache[t]},p=function(r,t,n){var e=l(r.cache[n]),a=Array.isArray(e)?(0,i.map)((0,u.get)(r.entityStore,t),e):(0,u.get)(r.entityStore,t,e);return o({},r.cache[n],{value:a})},y=(t.put=(0,i.curry)(function(r,t,n,o,a){var l=c(t,[n.name].concat(e((0,i.filter)(i.identity,o))));return Array.isArray(a)?r.cache[l]=f((0,i.map)((0,i.prop)("id"),a)):r.cache[l]=f((0,i.prop)("id",a)),(0,i.map_)((0,u.put)(r.entityStore,t),Array.isArray(a)?a:[a]),a}),t.getValue=function(r){return Array.isArray(r)?(0,i.map)(l,r):l(r)},t.contains=function(r,t,n,o){var u=c(t,[n.name].concat(e((0,i.filter)(i.identity,o))));return s(r,u)},t.get=function(r,t,n,o){var u=c(t,[n.name].concat(e((0,i.filter)(i.identity,o))));if(!s(r,u))throw new Error("Tried to access "+t.name+" with key "+u+" which doesn't exist.\n Do a contains check first!");return p(r,t,u)},function(r){return r.invalidatesOn||["CREATE","UPDATE","DELETE"]}),v=function(r,t){var n=y(r);return n&&n.indexOf(t)>-1},d=(0,i.curry)(function(r,t){var n=Object.keys(r.cache),e=function(n){(0,i.startsWith)(t+"-",n)&&delete r.cache[n]};(0,i.map_)(e,n)}),h=function(r){return r.invalidates||[]},m=function(r,t,n){v(t,n.operation)&&(0,i.map_)(d(r),h(t))},b=function(r,t,n){var e=function(r){return t.name+"-"+r},o=(0,i.compose)(d(r),e);(0,i.map_)(o,h(n))};t.invalidate=function(r,t,n){m(r,t,n),b(r,t,n)},t.createQueryCache=function(r){return{entityStore:r,cache:{}}}},function(r,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.serialize=void 0;var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(r){return typeof r}:function(r){return r&&"function"==typeof Symbol&&r.constructor===Symbol?"symbol":typeof r},o=(n(1),function u(r){return Object.keys(r).map(function(t){return r[t]&&"object"===e(r[t])?u(r[t]):r[t]}).join("-")});t.serialize=function(r){return r&&"object"===("undefined"==typeof r?"undefined":e(r))?o(r):r}},function(r,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.build=void 0;var e=Object.assign||function(r){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var e in n)Object.prototype.hasOwnProperty.call(n,e)&&(r[e]=n[e])}return r},o=function(){function r(r,t){var n=[],e=!0,o=!1,u=void 0;try{for(var i,a=r[Symbol.iterator]();!(e=(i=a.next()).done)&&(n.push(i.value),!t||n.length!==t);e=!0);}catch(c){o=!0,u=c}finally{try{!e&&a["return"]&&a["return"]()}finally{if(o)throw u}}return n}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return r(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),u=n(1),i=n(2),a=n(3),c=n(8),f=function(r){var t=o(r,2),n=t[0],u=t[1];return e({name:n},u)},l=(0,u.compose)((0,u.mapValues)((0,u.prop)("api")),(0,u.toObject)((0,u.prop)("name")));t.build=function(r){var t=(0,u.mapObject)(f,r),n=(0,i.createEntityStore)(t),e=(0,a.createQueryCache)(n),o=(0,u.compose)(l,(0,u.map)((0,c.decorate)(n,e)));return o(t)}},function(r,t,n){"use strict";function e(r,t,n,e){return function(){return e.apply(void 0,arguments).then((0,i.passThrough)((0,o.put)(r,n))).then((0,i.passThrough)(function(){return(0,u.invalidate)(t,n,e)}))}}Object.defineProperty(t,"__esModule",{value:!0}),t.decorateCreate=e;var o=n(2),u=n(3),i=n(1)},function(r,t,n){"use strict";function e(r,t,n,e){return function(){for(var a=arguments.length,c=Array(a),f=0;a>f;f++)c[f]=arguments[f];return(0,o.remove)(r,n,c.join("")),e.apply(void 0,c).then((0,i.passThrough)(function(){return(0,u.invalidate)(t,n,e)}))}}Object.defineProperty(t,"__esModule",{value:!0}),t.decorateDelete=e;var o=n(2),u=n(3),i=n(1)},function(r,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.decorate=void 0;var e=Object.assign||function(r){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var e in n)Object.prototype.hasOwnProperty.call(n,e)&&(r[e]=n[e])}return r},o=n(1),u=n(6),i=n(10),a=n(11),c=n(7),f=n(9),l=(0,o.curry)(function(r,t,n,e){var o={CREATE:u.decorateCreate,READ:i.decorateRead,UPDATE:a.decorateUpdate,DELETE:c.decorateDelete,NO_OPERATION:f.decorateNoOperation}[e.operation||"NO_OPERATION"];return o(r,t,n,e)});t.decorate=(0,o.curry)(function(r,t,n){var u=(0,o.mapValues)(l(r,t,n),n.api);return e({},n,{api:u})})},function(r,t,n){"use strict";function e(r,t,n,e){var i=e.bind(null);return i.operation="NO_OPERATION",function(){return e.apply(void 0,arguments).then((0,u.passThrough)(function(){return(0,o.invalidate)(t,n,i)}))}}Object.defineProperty(t,"__esModule",{value:!0}),t.decorateNoOperation=e;var o=n(3),u=n(1)},function(r,t,n){"use strict";function e(r,t,n,e){return e.byId?l(r,n,e):p(r,t,n,e)}Object.defineProperty(t,"__esModule",{value:!0}),t.decorateRead=e;var o=n(2),u=n(3),i=n(1),a=n(4),c=function(r){return 1e3*(r.ttl||0)},f=function(r,t){return Date.now()-t>c(r)},l=function(r,t,n){return function(e){if((0,o.contains)(r,t,e)&&!n.alwaysGetFreshData){var u=(0,o.get)(r,t,e);if(!f(t,u.timestamp))return Promise.resolve(u.value)}return n(e).then((0,i.passThrough)((0,o.put)(r,t)))}},s=(0,i.curry)(function(r,t,n){if("ARGS"===r.idFrom){if(Array.isArray(n))throw new Error("idFrom is only supported for objects");return n.id=(0,a.serialize)(t),n}return n}),p=function(r,t,n,e){return function(){for(var r=arguments.length,o=Array(r),a=0;r>a;a++)o[a]=arguments[a];if((0,u.contains)(t,n,e,o)&&!e.alwaysGetFreshData){var c=(0,u.get)(t,n,e,o);if(!f(n,c.timestamp))return Promise.resolve((0,u.getValue)(c.value))}return e.apply(void 0,o).then(s(e,o)).then((0,i.passThrough)((0,u.put)(t,n,e,o)))}}},function(r,t,n){"use strict";function e(r,t,n,e){return function(a){for(var c=arguments.length,f=Array(c>1?c-1:0),l=1;c>l;l++)f[l-1]=arguments[l];return(0,o.put)(r,n,a),e.apply(void 0,[a].concat(f)).then((0,i.passThrough)(function(){return(0,u.invalidate)(t,n,e)}))}}Object.defineProperty(t,"__esModule",{value:!0}),t.decorateUpdate=e;var o=n(2),u=n(3),i=n(1)},function(r,t){"use strict";function n(r,t){var u=i({},t),a=e(r);a.forEach(function(n){void 0!==t[n]&&(u[n]=r[n])});var c=o(r);return c.forEach(function(e){void 0!==t[e]&&(u[e]=n(r[e],t[e]))}),u}function e(r){return Object.keys(r).filter(function(t){return null===r[t]||"object"!==u(r[t])||Array.isArray(r[t])})}function o(r){return Object.keys(r).filter(function(t){return null!==r[t]&&!Array.isArray(r[t])&&"object"===u(r[t])})}Object.defineProperty(t,"__esModule",{value:!0});var u="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(r){return typeof r}:function(r){return r&&"function"==typeof Symbol&&r.constructor===Symbol?"symbol":typeof r},i=Object.assign||function(r){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var e in n)Object.prototype.hasOwnProperty.call(n,e)&&(r[e]=n[e])}return r};t.merge=n}]); | ||
module.exports = | ||
/******/ (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] = { | ||
/******/ exports: {}, | ||
/******/ id: moduleId, | ||
/******/ loaded: false | ||
/******/ }; | ||
/******/ // Execute the module function | ||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); | ||
/******/ // Flag the module as loaded | ||
/******/ module.loaded = 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; | ||
/******/ // __webpack_public_path__ | ||
/******/ __webpack_require__.p = ""; | ||
/******/ // Load entry module and return exports | ||
/******/ return __webpack_require__(0); | ||
/******/ }) | ||
/************************************************************************/ | ||
/******/ ([ | ||
/* 0 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
'use strict'; | ||
var _builder = __webpack_require__(1); | ||
module.exports = { | ||
build: _builder.build | ||
}; | ||
/***/ }, | ||
/* 1 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.build = undefined; | ||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | ||
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); | ||
var _fp = __webpack_require__(2); | ||
var _entityStore = __webpack_require__(3); | ||
var _queryCache = __webpack_require__(5); | ||
var _decorator = __webpack_require__(7); | ||
// [[EntityName, EntityConfig]] -> Entity | ||
var toEntity = function toEntity(_ref) { | ||
var _ref2 = _slicedToArray(_ref, 2), | ||
name = _ref2[0], | ||
c = _ref2[1]; | ||
return _extends({ | ||
name: name | ||
}, c); | ||
}; | ||
// [Entity] -> Api | ||
var toApi = (0, _fp.compose)((0, _fp.mapValues)((0, _fp.prop)('api')), (0, _fp.toObject)((0, _fp.prop)('name'))); | ||
var getEntityConfigs = function getEntityConfigs(c) { | ||
var cCopy = _extends({}, c); | ||
delete cCopy.__config; | ||
return cCopy; | ||
}; | ||
// Config -> Api | ||
var build = exports.build = function build(c) { | ||
var config = c.__config || { idField: 'id' }; | ||
var entityConfigs = getEntityConfigs(c); | ||
var entities = (0, _fp.mapObject)(toEntity, entityConfigs); | ||
var entityStore = (0, _entityStore.createEntityStore)(entities); | ||
var queryCache = (0, _queryCache.createQueryCache)(entityStore); | ||
var createApi = (0, _fp.compose)(toApi, (0, _fp.map)((0, _decorator.decorate)(config, entityStore, queryCache))); | ||
return createApi(entities); | ||
}; | ||
/***/ }, | ||
/* 2 */ | ||
/***/ function(module, exports) { | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; | ||
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); | ||
var debug = exports.debug = function debug(x) { | ||
console.log(x); | ||
return x; | ||
}; | ||
// a -> a | ||
var identity = exports.identity = function identity(x) { | ||
return x; | ||
}; | ||
// VarFn -> VarFn | ||
var curry = exports.curry = function curry(f) { | ||
return function () { | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
var nrArgsRequired = f.length; | ||
if (args.length < nrArgsRequired) { | ||
return curry(f.bind.apply(f, [null].concat(args))); | ||
} else { | ||
return f.apply(undefined, args); | ||
} | ||
}; | ||
}; | ||
var passThrough = exports.passThrough = curry(function (f, x) { | ||
f(x); | ||
return x; | ||
}); | ||
var startsWith = exports.startsWith = curry(function (x, xs) { | ||
return xs.indexOf(x) === 0; | ||
}); | ||
var join = exports.join = curry(function (separator, x, y) { | ||
return x + separator + y; | ||
}); | ||
var on = exports.on = curry(function (f, g, h, x) { | ||
return f(g(x), h(x)); | ||
}); | ||
// a -> a -> bool | ||
var isEqual = exports.isEqual = curry(function (x, y) { | ||
return x === y; | ||
}); | ||
// UnFn -> UnFn -> UnFn -> Value -> Value -> BiFn | ||
var on2 = exports.on2 = curry(function (f, g, h, x, y) { | ||
return f(g(x), h(y)); | ||
}); | ||
var init = exports.init = function init(xs) { | ||
return xs.slice(0, xs.length - 1); | ||
}; | ||
var tail = exports.tail = function tail(xs) { | ||
return xs.slice(1, xs.length); | ||
}; | ||
var last = exports.last = function last(xs) { | ||
return xs[xs.length - 1]; | ||
}; | ||
var head = exports.head = function head(xs) { | ||
return xs[0]; | ||
}; | ||
// Function -> [a] -> [b] | ||
var map = exports.map = curry(function (fn, xs) { | ||
return xs.map(fn); | ||
}); | ||
// Function -> [a] -> () | ||
var map_ = exports.map_ = curry(function (fn, xs) { | ||
map(fn, xs); | ||
}); | ||
var reverse = exports.reverse = function reverse(xs) { | ||
return xs.slice().reverse(); | ||
}; | ||
// (a -> b -> a) -> a -> [b] -> c | ||
var reduce = exports.reduce = curry(function (f, currResult, xs) { | ||
map_(function (x) { | ||
currResult = f(currResult, x); | ||
}, xs); | ||
return currResult; | ||
}); | ||
var compose = exports.compose = function compose() { | ||
for (var _len2 = arguments.length, fns = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { | ||
fns[_key2] = arguments[_key2]; | ||
} | ||
return function () { | ||
return reduce(function (m, f) { | ||
return f(m); | ||
}, last(fns).apply(undefined, arguments), tail(reverse(fns))); | ||
}; | ||
}; | ||
// String -> Object -> Value | ||
var prop = exports.prop = curry(function (key, x) { | ||
return x[key]; | ||
}); | ||
// [a] -> [b] -> [[a, b]] | ||
var zip = exports.zip = function zip(xs, ys) { | ||
var toTake = Math.min(xs.length, ys.length); | ||
var zs = []; | ||
for (var i = 0; i < toTake; i++) { | ||
zs.push([xs[i], ys[i]]); | ||
} | ||
return zs; | ||
}; | ||
// BiFn -> BiFn | ||
var flip = exports.flip = function flip(fn) { | ||
return curry(function (x, y) { | ||
return fn(y, x); | ||
}); | ||
}; | ||
// Object -> [[key, val]] | ||
var toPairs = exports.toPairs = function toPairs(x) { | ||
var keys = Object.keys(x); | ||
var getValue = flip(prop)(x); | ||
return zip(keys, map(getValue, keys)); | ||
}; | ||
// [[key, val]] -> Object<Key, Val> | ||
var fromPairs = exports.fromPairs = function fromPairs(xs) { | ||
var addToObj = function addToObj(o, _ref) { | ||
var _ref2 = _slicedToArray(_ref, 2), | ||
k = _ref2[0], | ||
v = _ref2[1]; | ||
return passThrough(function () { | ||
return o[k] = v; | ||
}, o); | ||
}; | ||
return reduce(addToObj, {}, xs); | ||
}; | ||
// ([a, b] -> c) -> Object<a, b> -> [c] | ||
var mapObject = exports.mapObject = on2(map, identity, toPairs); | ||
var writeToObject = curry(function (o, k, v) { | ||
o[k] = v; | ||
return o; | ||
}); | ||
// (a -> b) -> o -> o | ||
var mapValues = exports.mapValues = curry(function (fn, o) { | ||
var keys = Object.keys(o); | ||
return reduce(function (m, x) { | ||
m[x] = fn(o[x]); | ||
return m; | ||
}, {}, keys); | ||
}); | ||
// [Object] -> Object | ||
var toObject = exports.toObject = curry(function (getK, xs) { | ||
return reduce(function (m, x) { | ||
return writeToObject(m, getK(x), x); | ||
}, {}, xs); | ||
}); | ||
var takeIf = curry(function (p, m, x) { | ||
if (p(x)) { | ||
m.push(x); | ||
} | ||
return m; | ||
}); | ||
var filter = exports.filter = curry(function (p, xs) { | ||
return reduce(takeIf(p), [], xs); | ||
}); | ||
var clone = exports.clone = function clone(o) { | ||
if (!o) { | ||
return o; | ||
} | ||
if (Array.isArray(o)) { | ||
return o.slice(0); | ||
} | ||
if ((typeof o === 'undefined' ? 'undefined' : _typeof(o)) === 'object') { | ||
return _extends({}, o); | ||
} | ||
}; | ||
/***/ }, | ||
/* 3 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.createEntityStore = exports.contains = exports.get = exports.put = exports.remove = undefined; | ||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | ||
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); /* A data structure that is aware of views and entities. | ||
* 1. If a value exist both in a view and entity, the newest value is preferred. | ||
* 2. If a view or entity is removed, the connected views and entities are also removed. | ||
* 3. If a new view value is added, it will be merged into the entity value if such exist. | ||
* otherwise a new view value will be added. | ||
* | ||
* Note that a view will always return at least what constitutes the view. | ||
* It can return the full entity too. This means the client code needs to take this into account | ||
* by not depending on only a certain set of values being there. | ||
* This is done to save memory and to simplify always providing the latest data. | ||
* Of course, this also requiers the view to truly be a subset of the entity. | ||
*/ | ||
var _merger = __webpack_require__(4); | ||
var _fp = __webpack_require__(2); | ||
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); } } | ||
// Value -> StoreValue | ||
var toStoreValue = function toStoreValue(v) { | ||
return { value: v, timestamp: Date.now() }; | ||
}; | ||
// EntityStore -> String -> Value | ||
var read = function read(_ref, k) { | ||
var _ref2 = _slicedToArray(_ref, 2), | ||
_ = _ref2[0], | ||
s = _ref2[1]; | ||
return s[k] ? _extends({}, s[k], { value: (0, _fp.clone)(s[k].value) }) : s[k]; | ||
}; | ||
// EntityStore -> String -> Value -> () | ||
var set = function set(_ref3, k, v) { | ||
var _ref4 = _slicedToArray(_ref3, 2), | ||
eMap = _ref4[0], | ||
s = _ref4[1]; | ||
return s[k] = toStoreValue((0, _fp.clone)(v)); | ||
}; | ||
// EntityStore -> String -> () | ||
var rm = (0, _fp.curry)(function (_ref5, k) { | ||
var _ref6 = _slicedToArray(_ref5, 2), | ||
_ = _ref6[0], | ||
s = _ref6[1]; | ||
return delete s[k]; | ||
}); | ||
// Entity -> Type | ||
var getEntityType = function getEntityType(e) { | ||
return e.viewOf || e.name; | ||
}; | ||
// EntityStore -> Entity -> () | ||
var rmViews = function rmViews(_ref7, e) { | ||
var _ref8 = _slicedToArray(_ref7, 2), | ||
eMap = _ref8[0], | ||
s = _ref8[1]; | ||
var entityType = getEntityType(e); | ||
var toRemove = [].concat(_toConsumableArray(eMap[entityType])); | ||
(0, _fp.map_)(rm([eMap, s]), toRemove); | ||
}; | ||
// Entity -> Value -> String -> () | ||
var createEntityKey = function createEntityKey(e, v) { | ||
return getEntityType(e) + v.__ladda__id; | ||
}; | ||
// Entity -> Value -> String -> () | ||
var createViewKey = function createViewKey(e, v) { | ||
return e.name + v.__ladda__id; | ||
}; | ||
// Entity -> Bool | ||
var isView = function isView(e) { | ||
return !!e.viewOf; | ||
}; | ||
// EntityStore -> Entity -> Value -> () | ||
var remove = exports.remove = function remove(es, e, id) { | ||
rm(es, createEntityKey(e, { __ladda__id: id })); | ||
rmViews(es, e); | ||
}; | ||
// EntityStore -> Entity -> Value | ||
var handle = (0, _fp.curry)(function (viewHandler, entityHandler, s, e, v) { | ||
if (isView(e)) { | ||
return viewHandler(s, e, v); | ||
} else { | ||
return entityHandler(s, e, v); | ||
} | ||
}); | ||
// EntityStore -> Entity -> Value -> Bool | ||
var entityValueExist = function entityValueExist(s, e, v) { | ||
return !!read(s, createEntityKey(e, v)); | ||
}; | ||
// EntityStore -> Entity -> Value -> () | ||
var setEntityValue = function setEntityValue(s, e, v) { | ||
if (!v.__ladda__id) { | ||
throw new Error('Value is missing id, tried to add to entity ' + e.name); | ||
} | ||
var k = createEntityKey(e, v); | ||
set(s, k, v); | ||
return v; | ||
}; | ||
// EntityStore -> Entity -> Value -> () | ||
var setViewValue = function setViewValue(s, e, v) { | ||
if (!v.__ladda__id) { | ||
throw new Error('Value is missing id, tried to add to view ' + e.name); | ||
} | ||
if (entityValueExist(s, e, v)) { | ||
var eValue = read(s, createEntityKey(e, v)).value; | ||
setEntityValue(s, e, (0, _merger.merge)(v, eValue)); | ||
rmViews(s, e); // all views will prefer entity cache since it is newer | ||
} else { | ||
var k = createViewKey(e, v); | ||
set(s, k, v); | ||
} | ||
return v; | ||
}; | ||
// EntityStore -> Entity -> Value -> () | ||
var put = exports.put = handle(setViewValue, setEntityValue); | ||
// EntityStore -> Entity -> String -> Value | ||
var getEntityValue = function getEntityValue(s, e, id) { | ||
var k = createEntityKey(e, { __ladda__id: id }); | ||
return read(s, k); | ||
}; | ||
// EntityStore -> Entity -> String -> Value | ||
var getViewValue = function getViewValue(s, e, id) { | ||
var entityValue = read(s, createEntityKey(e, { __ladda__id: id })); | ||
var viewValue = read(s, createViewKey(e, { __ladda__id: id })); | ||
var onlyViewValueExist = viewValue && !entityValue; | ||
if (onlyViewValueExist) { | ||
return viewValue; | ||
} else { | ||
return entityValue; | ||
} | ||
}; | ||
// EntityStore -> Entity -> id -> () | ||
var get = exports.get = handle(getViewValue, getEntityValue); | ||
var contains = exports.contains = function contains(es, e, id) { | ||
return !!handle(getViewValue, getEntityValue)(es, e, id); | ||
}; | ||
// [Object, Object] -> Entity -> [Object, Object] | ||
var registerView = function registerView(_ref9, e) { | ||
var _ref10 = _slicedToArray(_ref9, 2), | ||
eMap = _ref10[0], | ||
store = _ref10[1]; | ||
if (!eMap[e.viewOf]) { | ||
eMap[e.viewOf] = []; | ||
} | ||
eMap[e.viewOf].push(e.name); | ||
return [eMap, store]; | ||
}; | ||
// [Object, Object] -> Entity -> [Object, Object] | ||
var registerEntity = function registerEntity(_ref11, e) { | ||
var _ref12 = _slicedToArray(_ref11, 2), | ||
eMap = _ref12[0], | ||
store = _ref12[1]; | ||
if (!eMap[e.name]) { | ||
eMap[e.name] = []; | ||
} | ||
return [eMap, store]; | ||
}; | ||
// [a] -> [Object, Object] | ||
var updateIndex = function updateIndex(m, e) { | ||
return isView(e) ? registerView(m, e) : registerEntity(m, e); | ||
}; | ||
// [Entity] -> EntityStore | ||
var createEntityStore = exports.createEntityStore = function createEntityStore(c) { | ||
return (0, _fp.reduce)(updateIndex, [{}, {}], c); | ||
}; | ||
/***/ }, | ||
/* 4 */ | ||
/***/ function(module, exports) { | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; | ||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | ||
exports.merge = merge; | ||
function merge(source, destination) { | ||
var result = _extends({}, destination); | ||
var keysForNonObjects = getNonObjectKeys(source); | ||
keysForNonObjects.forEach(function (key) { | ||
if (destination[key] !== undefined) { | ||
result[key] = source[key]; | ||
} | ||
}); | ||
var keysForObjects = getObjectKeys(source); | ||
keysForObjects.forEach(function (key) { | ||
if (destination[key] !== undefined) { | ||
result[key] = merge(source[key], destination[key]); | ||
} | ||
}); | ||
return result; | ||
} | ||
function getNonObjectKeys(object) { | ||
return Object.keys(object).filter(function (key) { | ||
return object[key] === null || _typeof(object[key]) !== 'object' || Array.isArray(object[key]); | ||
}); | ||
} | ||
function getObjectKeys(object) { | ||
return Object.keys(object).filter(function (key) { | ||
return object[key] !== null && !Array.isArray(object[key]) && _typeof(object[key]) === 'object'; | ||
}); | ||
} | ||
/***/ }, | ||
/* 5 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.createQueryCache = exports.invalidate = exports.get = exports.contains = exports.getValue = exports.put = undefined; | ||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; /* Handles queries, in essence all GET operations. | ||
* Provides invalidation and querying. Uses the underlying EntityStore for all actual data. | ||
* Only ids are stored here. | ||
*/ | ||
var _entityStore = __webpack_require__(3); | ||
var _fp = __webpack_require__(2); | ||
var _serializer = __webpack_require__(6); | ||
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); } } | ||
// Entity -> [String] -> String | ||
var createKey = (0, _fp.on2)((0, _fp.reduce)((0, _fp.join)('-')), (0, _fp.prop)('name'), (0, _fp.map)(_serializer.serialize)); | ||
// Value -> CacheValue | ||
var toCacheValue = function toCacheValue(xs) { | ||
return { value: xs, timestamp: Date.now() }; | ||
}; | ||
// CacheValue -> Value | ||
var toValue = (0, _fp.prop)('value'); | ||
// QueryCache -> String -> Bool | ||
var inCache = function inCache(qc, k) { | ||
return !!qc.cache[k]; | ||
}; | ||
// QueryCache -> Entity -> String -> CacheValue | ||
var getFromCache = function getFromCache(qc, e, k) { | ||
var rawValue = toValue(qc.cache[k]); | ||
var value = Array.isArray(rawValue) ? (0, _fp.map)((0, _entityStore.get)(qc.entityStore, e), rawValue) : (0, _entityStore.get)(qc.entityStore, e, rawValue); | ||
return _extends({}, qc.cache[k], { | ||
value: value | ||
}); | ||
}; | ||
// QueryCache -> Entity -> ApiFunction -> [a] -> [b] -> [b] | ||
var put = exports.put = (0, _fp.curry)(function (qc, e, aFn, args, xs) { | ||
var k = createKey(e, [aFn.name].concat(_toConsumableArray((0, _fp.filter)(_fp.identity, args)))); | ||
if (Array.isArray(xs)) { | ||
qc.cache[k] = toCacheValue((0, _fp.map)((0, _fp.prop)('__ladda__id'), xs)); | ||
} else { | ||
qc.cache[k] = toCacheValue((0, _fp.prop)('__ladda__id', xs)); | ||
} | ||
(0, _fp.map_)((0, _entityStore.put)(qc.entityStore, e), Array.isArray(xs) ? xs : [xs]); | ||
return xs; | ||
}); | ||
// Value -> Promise | ||
var getValue = exports.getValue = function getValue(v) { | ||
return Array.isArray(v) ? (0, _fp.map)(toValue, v) : toValue(v); | ||
}; | ||
// QueryCache -> Entity -> ApiFunction -> [a] -> Bool | ||
var contains = exports.contains = function contains(qc, e, aFn, args) { | ||
var k = createKey(e, [aFn.name].concat(_toConsumableArray((0, _fp.filter)(_fp.identity, args)))); | ||
return inCache(qc, k); | ||
}; | ||
// QueryCache -> Entity -> ApiFunction -> [a] -> Bool | ||
var get = exports.get = function get(qc, e, aFn, args) { | ||
var k = createKey(e, [aFn.name].concat(_toConsumableArray((0, _fp.filter)(_fp.identity, args)))); | ||
if (!inCache(qc, k)) { | ||
throw new Error('Tried to access ' + e.name + ' with key ' + k + ' which doesn\'t exist.\n Do a contains check first!'); | ||
} | ||
return getFromCache(qc, e, k); | ||
}; | ||
// Entity -> [String] | ||
var getInvalidatesOn = function getInvalidatesOn(e) { | ||
return e.invalidatesOn || ['CREATE', 'UPDATE', 'DELETE']; | ||
}; | ||
// Entity -> Operation -> Bool | ||
var shouldInvalidateEntity = function shouldInvalidateEntity(e, op) { | ||
var invalidatesOn = getInvalidatesOn(e); | ||
return invalidatesOn && invalidatesOn.indexOf(op) > -1; | ||
}; | ||
// QueryCache -> String -> () | ||
var invalidateEntity = (0, _fp.curry)(function (qc, entityName) { | ||
var keys = Object.keys(qc.cache); | ||
var removeIfEntity = function removeIfEntity(k) { | ||
if ((0, _fp.startsWith)(entityName, k)) { | ||
delete qc.cache[k]; | ||
} | ||
}; | ||
(0, _fp.map_)(removeIfEntity, keys); | ||
}); | ||
// Object -> a | ||
var getInvalidates = function getInvalidates(x) { | ||
return x.invalidates || []; | ||
}; | ||
// QueryCache -> Entity -> Operation -> () | ||
var invalidateBasedOnEntity = function invalidateBasedOnEntity(qc, e, aFn) { | ||
if (shouldInvalidateEntity(e, aFn.operation)) { | ||
(0, _fp.map_)(invalidateEntity(qc), getInvalidates(e)); | ||
} | ||
}; | ||
// QueryCache -> Entity -> ApiFunction -> Operation -> () | ||
var invalidateBasedOnApiFn = function invalidateBasedOnApiFn(qc, e, aFn) { | ||
var prependEntity = function prependEntity(x) { | ||
return e.name + '-' + x; | ||
}; | ||
var invalidateEntityByApiFn = (0, _fp.compose)(invalidateEntity(qc), prependEntity); | ||
(0, _fp.map_)(invalidateEntityByApiFn, getInvalidates(aFn)); | ||
}; | ||
// QueryCache -> Entity -> ApiFunction -> Operation -> () | ||
var invalidate = exports.invalidate = function invalidate(qc, e, aFn) { | ||
invalidateBasedOnEntity(qc, e, aFn); | ||
invalidateBasedOnApiFn(qc, e, aFn); | ||
}; | ||
// EntityStore -> QueryCache | ||
var createQueryCache = exports.createQueryCache = function createQueryCache(es) { | ||
return { entityStore: es, cache: {} }; | ||
}; | ||
/***/ }, | ||
/* 6 */ | ||
/***/ function(module, exports) { | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; | ||
var serializeObject = function serializeObject(o) { | ||
return Object.keys(o).map(function (x) { | ||
if (o[x] && _typeof(o[x]) === 'object') { | ||
return serializeObject(o[x]); | ||
} else { | ||
return o[x]; | ||
} | ||
}).join('-'); | ||
}; | ||
var serialize = exports.serialize = function serialize(x) { | ||
if (x && (typeof x === 'undefined' ? 'undefined' : _typeof(x)) === 'object') { | ||
return serializeObject(x); | ||
} else { | ||
return x; | ||
} | ||
}; | ||
/***/ }, | ||
/* 7 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.decorate = undefined; | ||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | ||
var _fp = __webpack_require__(2); | ||
var _create = __webpack_require__(8); | ||
var _read = __webpack_require__(10); | ||
var _update = __webpack_require__(11); | ||
var _delete = __webpack_require__(12); | ||
var _noOperation = __webpack_require__(13); | ||
var decorateApi = (0, _fp.curry)(function (config, entityStore, queryCache, entity, apiFn) { | ||
var handler = { | ||
CREATE: _create.decorateCreate, | ||
READ: _read.decorateRead, | ||
UPDATE: _update.decorateUpdate, | ||
DELETE: _delete.decorateDelete, | ||
NO_OPERATION: _noOperation.decorateNoOperation | ||
}[apiFn.operation || 'NO_OPERATION']; | ||
return handler(config, entityStore, queryCache, entity, apiFn); | ||
}); | ||
var decorate = exports.decorate = (0, _fp.curry)(function (config, entityStore, queryCache, entity) { | ||
var decoratedApi = (0, _fp.mapValues)(decorateApi(config, entityStore, queryCache, entity), entity.api); | ||
return _extends({}, entity, { | ||
api: decoratedApi | ||
}); | ||
}); | ||
/***/ }, | ||
/* 8 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.decorateCreate = decorateCreate; | ||
var _entityStore = __webpack_require__(3); | ||
var _queryCache = __webpack_require__(5); | ||
var _fp = __webpack_require__(2); | ||
var _idHelper = __webpack_require__(9); | ||
function decorateCreate(c, es, qc, e, aFn) { | ||
return function () { | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
return aFn.apply(undefined, args).then((0, _fp.passThrough)((0, _fp.compose)((0, _entityStore.put)(es, e), (0, _idHelper.addId)(c, aFn, args)))).then((0, _fp.passThrough)(function () { | ||
return (0, _queryCache.invalidate)(qc, e, aFn); | ||
})); | ||
}; | ||
} | ||
/***/ }, | ||
/* 9 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.removeId = exports.addId = undefined; | ||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | ||
var _serializer = __webpack_require__(6); | ||
var _fp = __webpack_require__(2); | ||
var getIdGetter = function getIdGetter(c, aFn) { | ||
if (aFn && aFn.idFrom && typeof aFn.idFrom === 'function') { | ||
return aFn.idFrom; | ||
} else { | ||
return (0, _fp.prop)(c.idField || 'id'); | ||
} | ||
}; | ||
var addId = exports.addId = (0, _fp.curry)(function (c, aFn, args, o) { | ||
if (aFn && aFn.idFrom === 'ARGS') { | ||
return _extends({}, o, { | ||
__ladda__id: (0, _serializer.serialize)(args) | ||
}); | ||
} else { | ||
var getId = getIdGetter(c, aFn); | ||
if (Array.isArray(o)) { | ||
return (0, _fp.map)(function (x) { | ||
return _extends({}, x, { | ||
__ladda__id: getId(x) | ||
}); | ||
}, o); | ||
} else { | ||
return _extends({}, o, { | ||
__ladda__id: getId(o) | ||
}); | ||
} | ||
} | ||
}); | ||
var removeId = exports.removeId = function removeId(o) { | ||
if (!o) { | ||
return o; | ||
} | ||
if (Array.isArray(o)) { | ||
return (0, _fp.map)(function (x) { | ||
delete x.__ladda__id; | ||
return x; | ||
}, o); | ||
} else { | ||
delete o.__ladda__id; | ||
return o; | ||
} | ||
}; | ||
/***/ }, | ||
/* 10 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.decorateRead = decorateRead; | ||
var _entityStore = __webpack_require__(3); | ||
var _queryCache = __webpack_require__(5); | ||
var _fp = __webpack_require__(2); | ||
var _idHelper = __webpack_require__(9); | ||
var getTtl = function getTtl(e) { | ||
return (e.ttl || 0) * 1000; | ||
}; | ||
// Entity -> Int -> Bool | ||
var hasExpired = function hasExpired(e, timestamp) { | ||
return Date.now() - timestamp > getTtl(e); | ||
}; | ||
var decorateReadSingle = function decorateReadSingle(c, es, qc, e, aFn) { | ||
return function (id) { | ||
if ((0, _entityStore.contains)(es, e, id) && !aFn.alwaysGetFreshData) { | ||
var v = (0, _entityStore.get)(es, e, id); | ||
if (!hasExpired(e, v.timestamp)) { | ||
return Promise.resolve((0, _idHelper.removeId)(v.value)); | ||
} | ||
} | ||
return aFn(id).then((0, _fp.passThrough)((0, _fp.compose)((0, _entityStore.put)(es, e), (0, _idHelper.addId)(c, aFn, id)))).then((0, _fp.passThrough)(function () { | ||
return (0, _queryCache.invalidate)(qc, e, aFn); | ||
})); | ||
}; | ||
}; | ||
var decorateReadQuery = function decorateReadQuery(c, es, qc, e, aFn) { | ||
return function () { | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
if ((0, _queryCache.contains)(qc, e, aFn, args) && !aFn.alwaysGetFreshData) { | ||
var v = (0, _queryCache.get)(qc, e, aFn, args); | ||
if (!hasExpired(e, v.timestamp)) { | ||
return Promise.resolve((0, _idHelper.removeId)((0, _queryCache.getValue)(v.value))); | ||
} | ||
} | ||
return aFn.apply(undefined, args).then((0, _fp.passThrough)((0, _fp.compose)((0, _queryCache.put)(qc, e, aFn, args), (0, _idHelper.addId)(c, aFn, args)))).then((0, _fp.passThrough)(function () { | ||
return (0, _queryCache.invalidate)(qc, e, aFn); | ||
})); | ||
}; | ||
}; | ||
function decorateRead(c, es, qc, e, aFn) { | ||
if (aFn.byId) { | ||
return decorateReadSingle(c, es, qc, e, aFn); | ||
} else { | ||
return decorateReadQuery(c, es, qc, e, aFn); | ||
} | ||
} | ||
/***/ }, | ||
/* 11 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.decorateUpdate = decorateUpdate; | ||
var _entityStore = __webpack_require__(3); | ||
var _queryCache = __webpack_require__(5); | ||
var _fp = __webpack_require__(2); | ||
var _idHelper = __webpack_require__(9); | ||
function decorateUpdate(c, es, qc, e, aFn) { | ||
return function (eValue) { | ||
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { | ||
args[_key - 1] = arguments[_key]; | ||
} | ||
(0, _entityStore.put)(es, e, (0, _idHelper.addId)(c, undefined, undefined, eValue)); | ||
return aFn.apply(undefined, [eValue].concat(args)).then((0, _fp.passThrough)(function () { | ||
return (0, _queryCache.invalidate)(qc, e, aFn); | ||
})); | ||
}; | ||
} | ||
/***/ }, | ||
/* 12 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.decorateDelete = decorateDelete; | ||
var _entityStore = __webpack_require__(3); | ||
var _queryCache = __webpack_require__(5); | ||
var _fp = __webpack_require__(2); | ||
var _serializer = __webpack_require__(6); | ||
function decorateDelete(c, es, qc, e, aFn) { | ||
return function () { | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
(0, _entityStore.remove)(es, e, (0, _serializer.serialize)(args)); | ||
return aFn.apply(undefined, args).then((0, _fp.passThrough)(function () { | ||
return (0, _queryCache.invalidate)(qc, e, aFn); | ||
})); | ||
}; | ||
} | ||
/***/ }, | ||
/* 13 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.decorateNoOperation = decorateNoOperation; | ||
var _queryCache = __webpack_require__(5); | ||
var _fp = __webpack_require__(2); | ||
function decorateNoOperation(c, es, qc, e, aFn) { | ||
var newApiFn = aFn.bind(null); | ||
for (var x in aFn) { | ||
if (aFn.hasOwnProperty(x)) { | ||
newApiFn[x] = aFn[x]; | ||
} | ||
} | ||
newApiFn.operation = 'NO_OPERATION'; | ||
return function () { | ||
return aFn.apply(undefined, arguments).then((0, _fp.passThrough)(function () { | ||
return (0, _queryCache.invalidate)(qc, e, newApiFn); | ||
})); | ||
}; | ||
} | ||
/***/ } | ||
/******/ ]); |
{ | ||
"name": "ladda-cache", | ||
"version": "0.1.1", | ||
"version": "0.1.2", | ||
"description": "Data fetching layer with support for caching", | ||
@@ -16,2 +16,3 @@ "main": "dist/bundle.js", | ||
"chai": "^3.5.0", | ||
"gitbook-cli": "^2.3.0", | ||
"mocha": "^2.5.3", | ||
@@ -21,2 +22,4 @@ "sinon": "^1.17.7" | ||
"scripts": { | ||
"docs:prepare": "gitbook install", | ||
"docs:watch": "npm run docs:prepare && gitbook serve", | ||
"test": "env NODE_PATH=$NODE_PATH:$PWD/src ./node_modules/.bin/mocha --compilers js:babel-register --reporter spec src/*.spec.js src/**/*.spec.js --require mocha.config", | ||
@@ -23,0 +26,0 @@ "coverage": "env NODE_PATH=$NODE_PATH:$PWD/src nyc -x '**/*.spec.js' -x '**/*.config.js' --reporter=lcov --reporter=text mocha --compilers js:babel-register --reporter spec src/*.spec.js src/**/*.spec.js --require mocha.config" |
191
README.md
# Ladda | ||
Ladda is a tool that moves caching logic, in particular invalidation logic, out from our application code. It allows us to easily model what should be invalidated when an entity is created, updated or deleted. For example, when a user is deleted you might want to invalidate blog posts, since you know that deleting a user also delets all blog posts by the user. Rather than tainting application code with this we can observe that a user was deleted and do what is necessary. | ||
Ladda is a library that helps you with caching, invalidation of caches and to handle different representations of the same data in a **performant** and **memory efficient** way. | ||
# When to Use Ladda | ||
Ladda is not meant to cover all cases. This is very intentional. Bad libraries are often the result of trying to do too much. You should only use Ladda when you have or intend to follow REST to a high extent. This means: | ||
The main goal with Ladda is to make it easy for you to add sophisticated caching **without making your application code more complex**. Ladda will take care of logic that would otherwise increase the complexity of your application code, and it will do so in a corner outside of your application. | ||
* Define an entity E such that E.id uniquely identifies the entity. | ||
* Define create, read, update and delete such that: | ||
* **create: { E \ {id} }** where E \ {id} means E without id. | ||
* **update: { E }** | ||
* **delete: { id }** | ||
* **read: { id }** where the backend responds with E | ||
* **readMultiple: { … }** where the backend responds with [E] and “…” is an unique query. | ||
You can expect a **significant performance boost** (if you didn't have any caching already) with possibly no changes to you application code. Ladda is designed to be something you can ignore once you set it up. | ||
Ladda can be used in more cases. It is very flexible and often allows you to work around issues. But you need to think about how you are using it then. If you want to do some custom solution, where E don’t have an id or call it something else, then you need to use something else or be creative. | ||
When developing your application you shouldn't care about Ladda nor caching. You should just assume that backend calls are for free, that they will be cached if possible and data will be refetched if it has to. This can **simplify you application code**. | ||
# Get Started | ||
To use Ladda you need to configure it and export a API built from the configuration. You might create a file "api/index.js": | ||
If you get bored of Ladda you can easily get rid of it. Ladda is designed to influence your application code as little as possible. We want you to get hooked, but not because of the cost of getting rid of Ladda. | ||
```javascript | ||
import * as project from './project'; | ||
import { build } from 'ladda-cache'; | ||
# Demo | ||
The easiest way to get a glimpse of what Ladda can do is checking out our [demos](/docs/Demos.md). | ||
const config = { | ||
projects: { | ||
ttl: 300, | ||
api: project | ||
} | ||
}; | ||
# Get Started | ||
Check out the [guide](/docs/GettingStarted.md) for getting started. And have a look in the [examples folder](https://github.com/petercrona/ladda/tree/master/examples). For a minimal example (everything in one file) that you can clone and run, check out [ladda-example-mini-project](https://github.com/petercrona/ladda-example-mini-project) ([code](https://github.com/petercrona/ladda-example-mini-project/blob/master/script.js)). | ||
export default build(config); | ||
``` | ||
# Documentation | ||
You'll find the [documentation](https://petercrona.gitbooks.io/ladda/content/) over at Gitbooks. | ||
where project is a bunch of api-methods returning promises, eg: | ||
# Why Use Ladda? | ||
The sales pitch, here's a bunch of things that we are proud of: | ||
```javascript | ||
createProject.operation = 'CREATE'; | ||
export function createProject(project) { | ||
return post(resource, { postData: project }); | ||
} | ||
## Lightweight | ||
Ladda is a lightweight and comes with no additional dependencies. The library has a file size of only 14 KB (minimized). | ||
getProjects.operation = 'READ'; | ||
getProjects.plural = true; | ||
export function getProjects(foo) { | ||
return get(resource); | ||
} | ||
``` | ||
## Quality | ||
Ladda has a high test coverage (**100%** line coverage) with tests constantly being added. And yes, we know that high test coverage is a "feel good" number, our focus is still on meaningful and good tests. It has a reasonably simple architecture and often tries to stay [tacit](https://www.youtube.com/watch?v=seVSlKazsNk&feature=youtu.be) and concise by taking inspiration from [functional programming](https://drboolean.gitbooks.io/mostly-adequate-guide/content/). We urge you to check out the [source code](https://github.com/petercrona/ladda/tree/master/src). Help us to improve it further, or just enjoy reading JavaScript that looks a bit different from what you are used to. | ||
# Main Concepts | ||
**Type** | ||
## Standalone | ||
Apart from being independent from any dependencies, Ladda is library and framework agnostic. It doesn't depend on the latest single page application framework out there. It doesn't reinvent the wheel of caching every time a new framework comes around. You can use it in your evolving application as your caching solution. | ||
For example “User” can be a type. API-methods are associated with a type. So for example a method `deleteById(id)` will automatically delete the entry from the cache for “User”. You can think of a type as defining a namespace for the cache. | ||
## Low Buy-In | ||
Ladda is just a wrapper around your client-side API layer. Somewhere in your application you might have defined all your outgoing API requests. Ladda will wrap these requests and act as your client-side cache. The API requests themselves don't change, but Ladda enhances them with caching capabilities. To get rid of Ladda, you can just remove the wrapping, and your API functions return to just being themselves. We believe that it is equally important to make it easy to add Ladda to your application, as it is to make it easy to remove Ladda from your application. | ||
To define the type user: | ||
``` | ||
User: { | ||
api: userApi | ||
} | ||
``` | ||
**Item** | ||
An instance of a type. For example userA and userB are items of type User. Could look like `{ id: “randomId”, name: “Kalle” }`. Always have “id” specified except for when you create a new one. | ||
**Operation** | ||
Ladda follows the CRUD-model. Operations are: | ||
* CREATE: Create a new item, for example a new User, given an item without id specified. | ||
* READ: Get one or multiple items given a query or id. | ||
* UPDATE: Update an item given an item with id specified. | ||
* DELETE: Remove an item given a id. | ||
**ID** | ||
Ladda relies on IDs being available. These are assumed to uniquely identify an entity of a certain type. For example “user.id”, where user is an entity in the type User, is assumed to uniquely identify a specific user. The main assumptions, per operations, are: | ||
*CREATE*: | ||
A function declared with “operation = CREATE” is expected to as its only parameter get an item without the “id” being set. For example: | ||
``` | ||
{ name: “Peter”, from: “Sweden”, livingIn: “Germany” } | ||
``` | ||
The server is required to respond with `{ id: <uniqueIdForItem> }`. The response can contain more data, but only the id will be used. Note the assumption that the server will not manipulate the entity saved. | ||
*READ - Singular:* | ||
An id is expected to be provided as the only argument. The response from the server is expected to be an item with “id” set. | ||
*READ - Plural:* | ||
An query, which is just an object, is expected to be provided as the only argument. The response from the server is expected to be a list of item. Each item must have an ID specified. Eg. if `[userA, userB]` is returned, userA.id and userB.id are required to be set. | ||
**Singular** | ||
This is defined by setting for example `getUserById.plural = false;`. This is only important for READ. See example above under “READ - Singular”. | ||
**Plural** | ||
This is defined by setting for example `getUserById.plural = true;`. This is only important for READ. See example above under “READ - Plural”. | ||
**Query** | ||
This is only important for “READ - Plural”. Consider an endpoint that gives you all the users born in 1989 and that have names starting with A. A query might look like: `{ nameStartsWith: “A”, born: 1989 }`. | ||
**API** | ||
Every type is associated with an API, which is simply an object with functions. For instance `{ getById: fetchFromDBFunction }`. Each function in the API needs to be decorated with at least “operation”. For example: | ||
``` | ||
getById.operation = “READ”; | ||
function getById(id) { return fetchFromDBFunction(id); } | ||
``` | ||
It has to return a Promise. As mentioned about, depending on the operation certain requirements are made: | ||
CREATE: An object with id set is returned: { id } | ||
READ - Singular: Single item is returned | ||
READ - Plural: A list of items | ||
UPDATE: No requirements | ||
DELETE: No requirements | ||
# Type Configuration | ||
Example: | ||
``` | ||
projects: { | ||
ttl: 300, | ||
invalidates: ['projects', 'projectPreview'], | ||
invalidatesOn: ['CREATE'], | ||
api: project | ||
} | ||
``` | ||
**viewOf** | ||
Specifies that the current type is a view of another entity. The super type will influence the cache of the view and vice versa. Eg. if a UserPreview is a view of User, then updating a user's name calling `User.updateName({ id, name })` will update UserPreview.name and vice versa. Default is no super type. | ||
**ttl** | ||
How long the cache is valid in seconds. After the number of seconds specified Ladda will pretend the cached entity don't exist. Default is no TTL (meaning no caching at all). | ||
**invalidates** | ||
Other entities to invalidate on operations specified in "invalidatesOn". Default is none. | ||
**invalidatesOn** | ||
Operations to invalidate on, where operations can be CREATE, READ, UPDATE, DELETE. Default is CREATE. | ||
# API Function Configuration | ||
Example: | ||
``` | ||
getAll.operation = 'READ'; | ||
getAll.plural = true; | ||
export function getAll(query) { | ||
return get('/api/v2/downloadable-file', { getData: query }); | ||
} | ||
poll.operation = 'READ'; | ||
poll.plural = true; | ||
poll.alwaysGetFreshData = true; | ||
poll.invalidates = ['getAll(*)']; | ||
export function poll(query) { | ||
return get('/api/v2/downloadable-file', { getData: query }); | ||
} | ||
``` | ||
**alwaysGetFreshData** | ||
Always fetch data (even if it exists in the cache) and save in cache. Default is false. | ||
**plural** | ||
Used when operation is set to READ. Informs Ladda that a list of multiple entities is expected. Default is false. | ||
**invalidates** | ||
Invalidates the query cache for the specified api function in the same type. If suffixed with (*) all caching for the specified api function will be cleared (regardless of which arguments it was called with). Otherwise only api function called without parameters. Default is none. | ||
**operation** | ||
CREATE | READ | UPDATE | DELETE - necessary for Ladda to handle caching correclty. Always has to be specified. | ||
# Try it out | ||
Do a "npm install ladda-cache" in your project. Stay tuned for an example project. | ||
# Contribute | ||
Please let me know if you have any feedback. Fork the repo, create PRs and create issues! For PRs with code, don't forget to write tests. |
@@ -15,10 +15,18 @@ import {mapObject, mapValues, compose, map, toObject, prop} from './fp'; | ||
const getEntityConfigs = c => { | ||
const cCopy = {...c}; | ||
delete cCopy.__config; | ||
return cCopy; | ||
}; | ||
// Config -> Api | ||
export const build = (c) => { | ||
const entities = mapObject(toEntity, c); | ||
const config = c.__config || {idField: 'id'}; | ||
const entityConfigs = getEntityConfigs(c); | ||
const entities = mapObject(toEntity, entityConfigs); | ||
const entityStore = createEntityStore(entities); | ||
const queryCache = createQueryCache(entityStore); | ||
const createApi = compose(toApi, map(decorate(entityStore, queryCache))); | ||
const createApi = compose(toApi, map(decorate(config, entityStore, queryCache))); | ||
return createApi(entities); | ||
}; |
@@ -41,2 +41,52 @@ import {build} from './builder'; | ||
}); | ||
it('Two read api calls will return the same output', (done) => { | ||
const myConfig = config(); | ||
myConfig.user.api.getUsers = sinon.spy(myConfig.user.api.getUsers); | ||
const api = build(myConfig); | ||
const expectOnlyOneApiCall = (xs) => { | ||
expect(xs).to.be.deep.equal([{id: 1}, {id: 2}]); | ||
done(); | ||
}; | ||
Promise.resolve() | ||
.then(() => api.user.getUsers()) | ||
.then(() => api.user.getUsers()) | ||
.then(expectOnlyOneApiCall); | ||
}); | ||
it('1000 calls is not slow', (done) => { | ||
const myConfig = config(); | ||
myConfig.user.api.getUsers = sinon.spy(myConfig.user.api.getUsers); | ||
myConfig.user.api.getUsers.idFrom = 'ARGS'; | ||
const api = build(myConfig); | ||
const start = Date.now(); | ||
const checkTimeConstraint = (xs) => { | ||
expect(Date.now() - start < 1000).to.be.true; | ||
done(); | ||
}; | ||
let bc = Promise.resolve(); | ||
for (let i = 0; i < 1000; i++) { | ||
bc = bc.then(() => api.user.getUsers('wei')); | ||
} | ||
bc.then(checkTimeConstraint); | ||
}); | ||
it('Works with non default id set', (done) => { | ||
const myConfig = config(); | ||
myConfig.__config = {idField: 'mySecretId'}; | ||
myConfig.user.api.getUsers = sinon.spy(() => | ||
Promise.resolve([{mySecretId: 1}, {mySecretId: 2}])); | ||
myConfig.user.api.getUsers.operation = 'READ'; | ||
const api = build(myConfig); | ||
const expectOnlyOneApiCall = (xs) => { | ||
expect(myConfig.user.api.getUsers.callCount).to.equal(1); | ||
expect(xs).to.be.deep.equal([{mySecretId: 1}, {mySecretId: 2}]); | ||
done(); | ||
}; | ||
Promise.resolve() | ||
.then(() => api.user.getUsers()) | ||
.then(() => api.user.getUsers()) | ||
.then(expectOnlyOneApiCall); | ||
}); | ||
}); |
import {put} from 'entity-store'; | ||
import {invalidate} from 'query-cache'; | ||
import {passThrough} from 'fp'; | ||
import {passThrough, compose} from 'fp'; | ||
import {addId} from 'id-helper'; | ||
export function decorateCreate(es, qc, e, aFn) { | ||
export function decorateCreate(c, es, qc, e, aFn) { | ||
return (...args) => { | ||
return aFn(...args) | ||
.then(passThrough(put(es, e))) | ||
.then(passThrough(compose(put(es, e), addId(c, aFn, args)))) | ||
.then(passThrough(() => invalidate(qc, e, aFn))); | ||
}; | ||
} |
@@ -51,6 +51,6 @@ import {decorateCreate} from './create'; | ||
}); | ||
const res = decorateCreate(es, qc, e, aFn); | ||
const res = decorateCreate({}, es, qc, e, aFn); | ||
res(xOrg).then((newX) => { | ||
expect(newX).to.equal(response); | ||
expect(get(es, e, 1).value).to.equal(response); | ||
expect(get(es, e, 1).value).to.deep.equal({...response, __ladda__id: 1}); | ||
done(); | ||
@@ -57,0 +57,0 @@ }); |
import {remove} from 'entity-store'; | ||
import {invalidate} from 'query-cache'; | ||
import {passThrough} from 'fp'; | ||
import {serialize} from 'serializer'; | ||
export function decorateDelete(es, qc, e, aFn) { | ||
export function decorateDelete(c, es, qc, e, aFn) { | ||
return (...args) => { | ||
remove(es, e, args.join('')); | ||
remove(es, e, serialize(args)); | ||
return aFn(...args) | ||
@@ -9,0 +10,0 @@ .then(passThrough(() => invalidate(qc, e, aFn))); |
import {decorateDelete} from './delete'; | ||
import {createEntityStore, get, put} from 'entity-store'; | ||
import {createQueryCache} from 'query-cache'; | ||
import {addId} from 'id-helper'; | ||
import sinon from 'sinon'; | ||
@@ -50,4 +51,4 @@ | ||
}); | ||
put(es, e, xOrg); | ||
const res = decorateDelete(es, qc, e, aFn); | ||
put(es, e, addId({}, undefined, undefined, xOrg)); | ||
const res = decorateDelete({}, es, qc, e, aFn); | ||
res(1).then(() => { | ||
@@ -54,0 +55,0 @@ expect(get(es, e, 1)).to.equal(undefined); |
@@ -8,3 +8,3 @@ import {curry, mapValues} from 'fp'; | ||
const decorateApi = curry((entityStore, queryCache, entity, apiFn) => { | ||
const decorateApi = curry((config, entityStore, queryCache, entity, apiFn) => { | ||
const handler = { | ||
@@ -17,7 +17,7 @@ CREATE: decorateCreate, | ||
}[apiFn.operation || 'NO_OPERATION']; | ||
return handler(entityStore, queryCache, entity, apiFn); | ||
return handler(config, entityStore, queryCache, entity, apiFn); | ||
}); | ||
export const decorate = curry((entityStore, queryCache, entity) => { | ||
const decoratedApi = mapValues(decorateApi(entityStore, queryCache, entity), entity.api); | ||
export const decorate = curry((config, entityStore, queryCache, entity) => { | ||
const decoratedApi = mapValues(decorateApi(config, entityStore, queryCache, entity), entity.api); | ||
return { | ||
@@ -24,0 +24,0 @@ ...entity, |
import {decorate} from './index'; | ||
import {createEntityStore} from 'entity-store'; | ||
import {createQueryCache, put, contains} from 'query-cache'; | ||
import {addId} from 'id-helper'; | ||
@@ -33,3 +34,3 @@ const config = [ | ||
const entity = {api: {getAll: f}}; | ||
const res = decorate(null, null, entity); | ||
const res = decorate({}, null, null, entity); | ||
expect(res.api.getAll).not.to.equal(f); | ||
@@ -44,4 +45,4 @@ }); | ||
const eCar = config[1]; | ||
const carsApi = decorate(es, qc, eCar, aFn); | ||
put(qc, eUser, aFn, [1], xOrg); | ||
const carsApi = decorate({}, es, qc, eCar, aFn); | ||
put(qc, eUser, aFn, [1], addId({}, undefined, undefined, xOrg)); | ||
@@ -48,0 +49,0 @@ expect(contains(qc, eUser, aFn, [1])).to.be.true; |
import {invalidate} from 'query-cache'; | ||
import {passThrough} from 'fp'; | ||
export function decorateNoOperation(es, qc, e, aFn) { | ||
export function decorateNoOperation(c, es, qc, e, aFn) { | ||
const newApiFn = aFn.bind(null); | ||
for (let x in aFn) { | ||
if (aFn.hasOwnProperty(x)) { | ||
newApiFn[x] = aFn[x]; | ||
} | ||
} | ||
newApiFn.operation = 'NO_OPERATION'; | ||
@@ -7,0 +12,0 @@ return (...args) => { |
@@ -5,7 +5,8 @@ import {get as getFromEs, | ||
import {get as getFromQc, | ||
invalidate, | ||
put as putInQc, | ||
contains as inQc, | ||
getValue} from 'query-cache'; | ||
import {passThrough, curry} from 'fp'; | ||
import {serialize} from 'serializer'; | ||
import {passThrough, compose, prop} from 'fp'; | ||
import {addId, removeId} from 'id-helper'; | ||
@@ -19,3 +20,3 @@ const getTtl = e => (e.ttl || 0) * 1000; | ||
const decorateReadSingle = (es, e, aFn) => { | ||
const decorateReadSingle = (c, es, qc, e, aFn) => { | ||
return (id) => { | ||
@@ -25,25 +26,12 @@ if (inEs(es, e, id) && !aFn.alwaysGetFreshData) { | ||
if (!hasExpired(e, v.timestamp)) { | ||
return Promise.resolve(v.value); | ||
return Promise.resolve(removeId(v.value)); | ||
} | ||
} | ||
return aFn(id).then(passThrough(putInEs(es, e))); | ||
return aFn(id).then(passThrough(compose(putInEs(es, e), addId(c, aFn, id)))) | ||
.then(passThrough(() => invalidate(qc, e, aFn))); | ||
}; | ||
}; | ||
const addId = curry((aFn, args, o) => { | ||
// TODO Add id as a special field, allowing us to remove it before returning to the user. | ||
// Eg. o.__ladda__id | ||
if (aFn.idFrom === 'ARGS') { | ||
if (Array.isArray(o)) { | ||
throw new Error('idFrom is only supported for objects'); | ||
} | ||
o.id = serialize(args); | ||
return o; | ||
} else { | ||
return o; | ||
} | ||
}); | ||
const decorateReadQuery = (es, qc, e, aFn) => { | ||
const decorateReadQuery = (c, es, qc, e, aFn) => { | ||
return (...args) => { | ||
@@ -53,16 +41,18 @@ if (inQc(qc, e, aFn, args) && !aFn.alwaysGetFreshData) { | ||
if (!hasExpired(e, v.timestamp)) { | ||
return Promise.resolve(getValue(v.value)); | ||
return Promise.resolve(removeId(getValue(v.value))); | ||
} | ||
} | ||
return aFn(...args).then(addId(aFn, args)).then(passThrough(putInQc(qc, e, aFn, args))); | ||
return aFn(...args) | ||
.then(passThrough(compose(putInQc(qc, e, aFn, args), addId(c, aFn, args)))) | ||
.then(passThrough(() => invalidate(qc, e, aFn))); | ||
}; | ||
}; | ||
export function decorateRead(es, qc, e, aFn) { | ||
export function decorateRead(c, es, qc, e, aFn) { | ||
if (aFn.byId) { | ||
return decorateReadSingle(es, e, aFn); | ||
return decorateReadSingle(c, es, qc, e, aFn); | ||
} else { | ||
return decorateReadQuery(es, qc, e, aFn); | ||
return decorateReadQuery(c, es, qc, e, aFn); | ||
} | ||
} |
@@ -42,7 +42,7 @@ import {decorateRead} from './read'; | ||
describe('decorateRead', () => { | ||
it('throws error if idFrom ARGS and array is returned', (done) => { | ||
it('stores and returns an array with elements that lack id', (done) => { | ||
const es = createEntityStore(config); | ||
const qc = createQueryCache(es); | ||
const e = config[0]; | ||
const xOrg = [{id: 1, name: 'Kalle'}]; | ||
const xOrg = [{name: 'Kalle'}, {name: 'Anka'}]; | ||
const aFn = sinon.spy(() => { | ||
@@ -52,5 +52,5 @@ return Promise.resolve(xOrg); | ||
aFn.idFrom = 'ARGS'; | ||
const res = decorateRead(es, qc, e, aFn); | ||
res(1).catch(x => { | ||
expect(x).to.be.an('Error'); | ||
const res = decorateRead({}, es, qc, e, aFn); | ||
res(1).then(x => { | ||
expect(x).to.deep.equal(xOrg); | ||
done(); | ||
@@ -68,5 +68,5 @@ }); | ||
aFn.idFrom = 'ARGS'; | ||
const res = decorateRead(es, qc, e, aFn); | ||
const res = decorateRead({}, es, qc, e, aFn); | ||
res({hello: 'hej', other: 'svej'}).then(x => { | ||
expect(x).to.deep.equal({id: 'hej-svej', name: 'Kalle'}); | ||
expect(x).to.deep.equal({name: 'Kalle'}); | ||
done(); | ||
@@ -84,4 +84,4 @@ }); | ||
aFn.byId = true; | ||
const res = decorateRead(es, qc, e, aFn); | ||
res(1).then(() => { | ||
const res = decorateRead({}, es, qc, e, aFn); | ||
res(1).then((x) => { | ||
expect(aFn.callCount).to.equal(1); | ||
@@ -100,3 +100,3 @@ done(); | ||
aFn.byId = true; | ||
const res = decorateRead(es, qc, e, aFn); | ||
const res = decorateRead({}, es, qc, e, aFn); | ||
res(1).then(res.bind(null, 1)).then(() => { | ||
@@ -115,3 +115,3 @@ expect(aFn.callCount).to.equal(1); | ||
}); | ||
const res = decorateRead(es, qc, e, aFn); | ||
const res = decorateRead({}, es, qc, e, aFn); | ||
res(1).then(() => { | ||
@@ -130,3 +130,3 @@ expect(aFn.callCount).to.equal(1); | ||
}); | ||
const res = decorateRead(es, qc, e, aFn); | ||
const res = decorateRead({}, es, qc, e, aFn); | ||
@@ -150,3 +150,3 @@ const firstCall = res(1); | ||
}); | ||
const res = decorateRead(es, qc, e, aFn); | ||
const res = decorateRead({}, es, qc, e, aFn); | ||
@@ -170,3 +170,3 @@ const firstCall = res(1); | ||
}); | ||
const res = decorateRead(es, qc, e, aFn); | ||
const res = decorateRead({}, es, qc, e, aFn); | ||
res(1).then((x) => { | ||
@@ -185,3 +185,3 @@ expect(x).to.equal(xOrg); | ||
}); | ||
const res = decorateRead(es, qc, e, aFn); | ||
const res = decorateRead({}, es, qc, e, aFn); | ||
@@ -205,3 +205,3 @@ const firstCall = res(1); | ||
}); | ||
const res = decorateRead(es, qc, e, aFn); | ||
const res = decorateRead({}, es, qc, e, aFn); | ||
@@ -225,3 +225,3 @@ const firstCall = res(1); | ||
}); | ||
const res = decorateRead(es, qc, e, aFn); | ||
const res = decorateRead({}, es, qc, e, aFn); | ||
@@ -228,0 +228,0 @@ res().catch(e => { |
import {put} from 'entity-store'; | ||
import {invalidate} from 'query-cache'; | ||
import {passThrough} from 'fp'; | ||
import {addId} from 'id-helper'; | ||
export function decorateUpdate(es, qc, e, aFn) { | ||
export function decorateUpdate(c, es, qc, e, aFn) { | ||
return (eValue, ...args) => { | ||
put(es, e, eValue); | ||
put(es, e, addId(c, undefined, undefined, eValue)); | ||
return aFn(eValue, ...args) | ||
@@ -9,0 +10,0 @@ .then(passThrough(() => invalidate(qc, e, aFn))); |
@@ -51,5 +51,5 @@ import {decorateUpdate} from './update'; | ||
const res = decorateUpdate(es, qc, e, aFn); | ||
const res = decorateUpdate({}, es, qc, e, aFn); | ||
res(xOrg).then(() => { | ||
expect(get(es, e, 1).value).to.equal(xOrg); | ||
expect(get(es, e, 1).value).to.deep.equal({...xOrg, __ladda__id: 1}); | ||
done(); | ||
@@ -56,0 +56,0 @@ }); |
@@ -15,3 +15,3 @@ /* A data structure that is aware of views and entities. | ||
import {merge} from './merger'; | ||
import {curry, reduce, map_} from 'fp'; | ||
import {curry, reduce, map_, clone} from 'fp'; | ||
@@ -22,6 +22,6 @@ // Value -> StoreValue | ||
// EntityStore -> String -> Value | ||
const read = ([_, s], k) => s[k]; | ||
const read = ([_, s], k) => (s[k] ? {...s[k], value: clone(s[k].value)} : s[k]); | ||
// EntityStore -> String -> Value -> () | ||
const set = ([eMap, s], k, v) => s[k] = toStoreValue(v); | ||
const set = ([eMap, s], k, v) => s[k] = toStoreValue(clone(v)); | ||
@@ -43,3 +43,3 @@ // EntityStore -> String -> () | ||
const createEntityKey = (e, v) => { | ||
return getEntityType(e) + v.id; | ||
return getEntityType(e) + v.__ladda__id; | ||
}; | ||
@@ -49,3 +49,3 @@ | ||
const createViewKey = (e, v) => { | ||
return e.name + v.id; | ||
return e.name + v.__ladda__id; | ||
}; | ||
@@ -58,3 +58,3 @@ | ||
export const remove = (es, e, id) => { | ||
rm(es, createEntityKey(e, {id})); | ||
rm(es, createEntityKey(e, {__ladda__id: id})); | ||
rmViews(es, e); | ||
@@ -77,3 +77,3 @@ }; | ||
const setEntityValue = (s, e, v) => { | ||
if (!v.id) { | ||
if (!v.__ladda__id) { | ||
throw new Error(`Value is missing id, tried to add to entity ${e.name}`); | ||
@@ -88,3 +88,3 @@ } | ||
const setViewValue = (s, e, v) => { | ||
if (!v.id) { | ||
if (!v.__ladda__id) { | ||
throw new Error(`Value is missing id, tried to add to view ${e.name}`); | ||
@@ -110,3 +110,3 @@ } | ||
const getEntityValue = (s, e, id) => { | ||
const k = createEntityKey(e, {id}); | ||
const k = createEntityKey(e, {__ladda__id: id}); | ||
return read(s, k); | ||
@@ -117,4 +117,4 @@ }; | ||
const getViewValue = (s, e, id) => { | ||
const entityValue = read(s, createEntityKey(e, {id})); | ||
const viewValue = read(s, createViewKey(e, {id})); | ||
const entityValue = read(s, createEntityKey(e, {__ladda__id: id})); | ||
const viewValue = read(s, createViewKey(e, {__ladda__id: id})); | ||
const onlyViewValueExist = viewValue && !entityValue; | ||
@@ -121,0 +121,0 @@ |
import {createEntityStore, put, get, contains, remove} from './entity-store'; | ||
import {addId} from 'id-helper'; | ||
@@ -60,6 +61,15 @@ const config = [ | ||
const e = { name: 'user'}; | ||
put(s, e, v); | ||
put(s, e, addId({}, undefined, undefined, v)); | ||
const r = get(s, e, v.id); | ||
expect(r.value).to.equal(v); | ||
expect(r.value).to.deep.equal({...v, __ladda__id: 'hello'}); | ||
}); | ||
it('altering an added value does not alter the stored value when doing a get later', () => { | ||
const s = createEntityStore(config); | ||
const v = {id: 'hello', name: 'kalle'}; | ||
const e = { name: 'user'}; | ||
put(s, e, addId({}, undefined, undefined, v)); | ||
v.name = 'ingvar'; | ||
const r = get(s, e, v.id); | ||
expect(r.value.name).to.equal('kalle'); | ||
}); | ||
it('an added value to a view is later returned when calling get for view', () => { | ||
@@ -69,5 +79,5 @@ const s = createEntityStore(config); | ||
const e = { name: 'user'}; | ||
put(s, e, v); | ||
put(s, e, addId({}, undefined, undefined, v)); | ||
const r = get(s, e, v.id); | ||
expect(r.value).to.equal(v); | ||
expect(r.value).to.deep.equal({...v, __ladda__id: 'hello'}); | ||
}); | ||
@@ -79,6 +89,6 @@ it('merges view into entity value', () => { | ||
const eView = {name: 'userPreview', viewOf: 'user'}; | ||
put(s, e, {...v, name: 'kalle'}); | ||
put(s, eView, {...v, name: 'ingvar'}); | ||
put(s, e, addId({}, undefined, undefined, {...v, name: 'kalle'})); | ||
put(s, eView, addId({}, undefined, undefined, {...v, name: 'ingvar'})); | ||
const r = get(s, eView, v.id); | ||
expect(r.value).to.be.deep.equal({id: 'hello', name: 'ingvar'}); | ||
expect(r.value).to.be.deep.equal({__ladda__id: 'hello', id: 'hello', name: 'ingvar'}); | ||
}); | ||
@@ -89,3 +99,3 @@ it('writing view value without id throws error', () => { | ||
const eView = {name: 'userPreview', viewOf: 'user'}; | ||
const write = () => put(s, eView, {...v, name: 'kalle'}); | ||
const write = () => put(s, eView, addId({}, undefined, undefined, {...v, name: 'kalle'})); | ||
expect(write).to.throw(Error); | ||
@@ -97,3 +107,3 @@ }); | ||
const e = {name: 'user'}; | ||
const write = () => put(s, e, {...v, name: 'kalle'}); | ||
const write = () => put(s, e, addId({}, undefined, undefined, {...v, name: 'kalle'})); | ||
expect(write).to.throw(Error); | ||
@@ -107,6 +117,16 @@ }); | ||
const e = { name: 'user'}; | ||
put(s, e, v); | ||
put(s, e, addId({}, undefined, undefined, v)); | ||
const r = get(s, e, v.id); | ||
expect(r.timestamp).to.not.be.undefined; | ||
}); | ||
it('altering retrieved value does not alter the stored value', () => { | ||
const s = createEntityStore(config); | ||
const v = {id: 'hello', name: 'kalle'}; | ||
const e = { name: 'user'}; | ||
put(s, e, addId({}, undefined, undefined, v)); | ||
const r = get(s, e, v.id); | ||
r.value.name = 'ingvar'; | ||
const r2 = get(s, e, v.id); | ||
expect(r2.value.name).to.equal(v.name); | ||
}); | ||
it('gets undefined if value does not exist', () => { | ||
@@ -130,6 +150,6 @@ const s = createEntityStore(config); | ||
const e = {name: 'user'}; | ||
put(s, e, v); | ||
put(s, e, addId({}, undefined, undefined, v)); | ||
const eView = {name: 'userPreview', viewOf: 'user'}; | ||
const r = get(s, eView, v.id); | ||
expect(r.value).to.be.deep.equal(v); | ||
expect(r.value).to.be.deep.equal({...v, __ladda__id: 'hello'}); | ||
}); | ||
@@ -141,5 +161,5 @@ it('gets view if only it exist', () => { | ||
const eView = {name: 'userPreview', viewOf: 'user'}; | ||
put(s, eView, v); | ||
put(s, eView, addId({}, undefined, undefined, v)); | ||
const r = get(s, eView, v.id); | ||
expect(r.value).to.be.deep.equal(v); | ||
expect(r.value).to.be.deep.equal({...v, __ladda__id: 'hello'}); | ||
}); | ||
@@ -151,6 +171,6 @@ it('gets entity value if same timestamp as view value', () => { | ||
const eView = {name: 'userPreview', viewOf: 'user'}; | ||
put(s, eView, v); | ||
put(s, e, {...v, name: 'kalle'}); | ||
put(s, eView, addId({}, undefined, undefined, v)); | ||
put(s, e, addId({}, undefined, undefined, {...v, name: 'kalle'})); | ||
const r = get(s, eView, v.id); | ||
expect(r.value).to.be.deep.equal({...v, name: 'kalle'}); | ||
expect(r.value).to.be.deep.equal({...v, name: 'kalle', __ladda__id: 'hello'}); | ||
}); | ||
@@ -162,7 +182,7 @@ it('gets entity value if newer than view value', (done) => { | ||
const eView = {name: 'userPreview', viewOf: 'user'}; | ||
put(s, eView, v); | ||
put(s, eView, addId({}, undefined, undefined, v)); | ||
setTimeout(() => { | ||
put(s, e, {...v, name: 'kalle'}); | ||
put(s, e, addId({}, undefined, undefined, {...v, name: 'kalle'})); | ||
const r = get(s, eView, v.id); | ||
expect(r.value).to.be.deep.equal({...v, name: 'kalle'}); | ||
expect(r.value).to.be.deep.equal({...v, name: 'kalle', __ladda__id: 'hello'}); | ||
done(); | ||
@@ -177,3 +197,3 @@ }, 1); | ||
const e = { name: 'user'}; | ||
put(s, e, v); | ||
put(s, e, addId({}, undefined, undefined, v)); | ||
const r = contains(s, e, v.id); | ||
@@ -195,3 +215,3 @@ expect(r).to.be.true; | ||
const e = { name: 'user'}; | ||
put(s, e, v); | ||
put(s, e, addId({}, undefined, undefined, v)); | ||
remove(s, e, v.id); | ||
@@ -198,0 +218,0 @@ const r = contains(s, e, v.id); |
@@ -126,1 +126,15 @@ export const debug = (x) => { | ||
}); | ||
export const clone = o => { | ||
if (!o) { | ||
return o; | ||
} | ||
if (Array.isArray(o)) { | ||
return o.slice(0); | ||
} | ||
if (typeof o === 'object') { | ||
return {...o}; | ||
} | ||
}; |
@@ -5,3 +5,3 @@ import {debug, identity, curry, passThrough, | ||
reduce, compose, prop, zip, flip, toPairs, fromPairs, | ||
mapObject, mapValues, toObject, filter} from './fp'; | ||
mapObject, mapValues, toObject, filter, clone} from './fp'; | ||
import sinon from 'sinon'; | ||
@@ -214,2 +214,19 @@ | ||
}); | ||
describe('clone', () => { | ||
it('returns object if falsy', () => { | ||
expect(clone(undefined)).to.equal(undefined); | ||
}); | ||
it('clones array', () => { | ||
const o = [1,2,3]; | ||
const cloned = clone(o); | ||
o.push(1); | ||
expect(cloned).to.not.deep.equal(o); | ||
}); | ||
it('clones object', () => { | ||
const o = {name: 'kalle'}; | ||
const cloned = clone(o); | ||
o.name = 'ingvar'; | ||
expect(cloned).to.not.deep.equal(o); | ||
}); | ||
}); | ||
}); |
@@ -39,5 +39,5 @@ /* Handles queries, in essence all GET operations. | ||
if (Array.isArray(xs)) { | ||
qc.cache[k] = toCacheValue(map(prop('id'), xs)); | ||
qc.cache[k] = toCacheValue(map(prop('__ladda__id'), xs)); | ||
} else { | ||
qc.cache[k] = toCacheValue(prop('id', xs)); | ||
qc.cache[k] = toCacheValue(prop('__ladda__id', xs)); | ||
} | ||
@@ -85,3 +85,3 @@ map_(putInEs(qc.entityStore, e), Array.isArray(xs) ? xs : [xs]); | ||
const removeIfEntity = k => { | ||
if (startsWith(entityName + '-', k)) { | ||
if (startsWith(entityName, k)) { | ||
delete qc.cache[k]; | ||
@@ -88,0 +88,0 @@ } |
import {createEntityStore} from './entity-store'; | ||
import {createQueryCache, getValue, put, contains, get, invalidate} from './query-cache'; | ||
import {addId} from 'id-helper'; | ||
@@ -74,3 +75,3 @@ const config = [ | ||
const xs = [{id: 1}, {id: 2}, {id: 3}]; | ||
put(qc, e, aFn, args, xs); | ||
put(qc, e, aFn, args, addId({}, undefined, undefined, xs)); | ||
expect(contains(qc, e, aFn, args)).to.be.true; | ||
@@ -85,3 +86,3 @@ }); | ||
const xs = [{id: 1}, {id: 2}, {id: 3}]; | ||
put(qc, e, aFn, args, xs); | ||
put(qc, e, aFn, args, addId({}, undefined, undefined, xs)); | ||
expect(contains(qc, e, aFn, args)).to.be.true; | ||
@@ -96,3 +97,3 @@ }); | ||
const xs = [{id: 1}, {id: 2}, {id: 3}]; | ||
put(qc, e, aFn, args, xs); | ||
put(qc, e, aFn, args, addId({}, undefined, undefined, xs)); | ||
expect(contains(qc, e, aFn, args)).to.be.true; | ||
@@ -117,4 +118,5 @@ }); | ||
const xs = [{id: 1}, {id: 2}, {id: 3}]; | ||
put(qc, e, aFn, args, xs); | ||
expect(getValue(get(qc, e, aFn, args).value)).to.deep.equal(xs); | ||
const xsRet = [{id: 1, __ladda__id: 1}, {id: 2, __ladda__id: 2}, {id: 3, __ladda__id: 3}]; | ||
put(qc, e, aFn, args, addId({}, undefined, undefined, xs)); | ||
expect(getValue(get(qc, e, aFn, args).value)).to.deep.equal(xsRet); | ||
}); | ||
@@ -141,3 +143,3 @@ it('if an does not exist, throw an error', () => { | ||
const xs = [{id: 1}, {id: 2}, {id: 3}]; | ||
put(qc, eUser, aFn, args, xs); | ||
put(qc, eUser, aFn, args, addId({}, undefined, undefined, xs)); | ||
invalidate(qc, eCars, aFn); | ||
@@ -144,0 +146,0 @@ const hasUser = contains(qc, eUser, aFn, args); |
@@ -1,3 +0,1 @@ | ||
import {map} from 'fp'; | ||
const serializeObject = (o) => { | ||
@@ -4,0 +2,0 @@ return Object.keys(o).map(x => { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
545158
113
3126
11
38