├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── TRANSITION.md ├── bower.json ├── build_examples ├── browserify │ ├── README.md │ ├── app.js │ └── index.html ├── r.js │ ├── README.md │ ├── app.js │ ├── index.html │ ├── main.js │ └── require.config.js ├── webpack │ ├── README.md │ ├── app.js │ ├── index.html │ └── webpack.config.js ├── webpack_es6 │ ├── README.md │ ├── app.js │ ├── index.html │ └── webpack.config.js └── webpack_es6_2 │ ├── README.md │ ├── app.js │ ├── index.html │ └── webpack.config.js ├── circle.yml ├── dist ├── angular-cache.js ├── angular-cache.js.map ├── angular-cache.min.js └── angular-cache.min.map ├── karma.conf.js ├── karma.start.js ├── package.json ├── rollup.config.js ├── scripts └── postbuild.js ├── src └── index.js └── test └── unit ├── Cache ├── index.destroy.test.js ├── index.get.test.js ├── index.info.test.js ├── index.keySet.test.js ├── index.keys.test.js ├── index.put.test.js ├── index.remove.test.js ├── index.removeAll.test.js ├── index.removeExpired.test.js └── index.setOptions.test.js └── DSCacheFactory ├── DSCacheFactoryProvider.setCacheDefaults.test.js ├── index.clearAll.test.js ├── index.destroyAll.test.js ├── index.disableAll.test.js ├── index.enableAll.test.js ├── index.get.test.js ├── index.info.test.js ├── index.keySet.test.js ├── index.keys.test.js └── index.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | 4 | .DS_Store 5 | 6 | libpeerconnection.log 7 | 8 | # IntelliJ IDEA 9 | .idea/ 10 | *.iml 11 | 12 | coverage/Chrome*/* 13 | coverage/Phantom*/* 14 | coverage/Firefox*/* 15 | coverage/ 16 | 17 | bower_components/ 18 | 19 | build_examples/**/bundle.js 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ##### 4.6.0 29 April 2016 2 | 3 | ###### Bug fixes 4 | - #215 - angular-cache.min.js tries to load the .map from dist/ 5 | - #204 - Does not work with Angular 1.3 and browserify 6 | 7 | ##### 4.5.0 12 January 2016 8 | 9 | ###### Backwards compatible API changes 10 | - #205 - Feature request: Cache.getAllValues 11 | 12 | ###### Backwards compatible bug fixes 13 | 14 | - #211 - Expires value gets sometimes automatically set to null 15 | - #212 - Internal Cache not reloaded on Reload 16 | 17 | ###### Other 18 | 19 | - Upgraded to CacheFactory v1.4.0 20 | 21 | ##### 4.4.3 30 December 2015 22 | 23 | - #179 - capacity does not work on often reload 24 | - #210 - Why does angular-cache ignore capacity configuration? 25 | 26 | ##### 4.4.1 12 October 2015 27 | 28 | - #203 - trouble: bundle with r.js not works 29 | 30 | ##### 4.4.0 12 October 2015 31 | 32 | - #200 - 4.3 storagePrefix is not backwards compatible 33 | - #201 - webpack minification error 34 | 35 | ##### 4.3.2 10 July 2015 36 | 37 | - #191 - yabh issue with ie8 38 | 39 | ##### 4.3.1 07 July 2015 40 | 41 | - #190 - 4.3 breaks phantomJS tests 42 | 43 | ##### 4.3.0 06 July 2015 44 | 45 | - #189 - Extracted non-angular code. 46 | 47 | ##### 4.2.2 01 July 2015 48 | 49 | ###### Backwards compatible bug fixes 50 | - #165 - storageMode does not inherits from defaults 51 | 52 | ##### 4.2.1 01 July 2015 53 | 54 | Upgraded dependencies 55 | Better CommonJS interop 56 | Added a number of build examples 57 | 58 | ##### 4.2.0 27 April 2015 59 | 60 | ###### Backwards compatible bug fixes 61 | - #174 - Cache not being fully emptied if using localStorage and multiple web pages 62 | 63 | ##### 4.1.0 30 March 2015 64 | 65 | ###### Backwards compatible API changes 66 | - #169 - Official support for ngResource 67 | 68 | ##### 4.0.2 22 March 2015 69 | 70 | ###### Backwards compatible bug fixes 71 | - #164 - onExpire is still called when cache is empty 72 | 73 | ##### 4.0.1 20 March 2015 74 | 75 | ###### Backwards compatible bug fixes 76 | - #163 - Configuring CacheOption storagePrefix results in "true.{key}" 77 | 78 | ##### 4.0.0 15 March 2015 79 | 80 | ###### Breaking API changes 81 | - Completely disassociated angular-cache from the deprecated angular-data (angular-data has been replaced by js-data + js-data-angular) 82 | - Angular module renamed to _angular-cache_ 83 | - _DSCacheFactory_ renamed to _CacheFactory_ 84 | - _DSBinaryHeap_ renamed to _BinaryHeap_ 85 | - Removed `DSCacheFactoryProvider.setCacheDefaults`. You now do `angular.extend(CacheFactoryProvider.defaults, { ... });` 86 | - No longer exposing a `DSCache` constructor function (as it no longer exists) 87 | - `storageMode` can now be set dynamically, which will remove all items from current storage and insert them into the new storage 88 | 89 | ###### Other 90 | - Fixes #161 91 | - Converted to ES6 and a webpack build with better umd support 92 | - Now exporting the module name _angular-cache_ (when you do `require('angular-cache')` you get `"angular-cache"`) 93 | - Deprecating angular-cache < 4.0.0 94 | 95 | ##### 3.2.5 02 February 2015 96 | 97 | ###### Backwards compatible bug fixes 98 | - #152 - Expired items sometimes only expire after double time. 99 | - #153 - Missing angular dependency in bower.json 100 | 101 | ##### 3.2.4 17 December 2014 102 | 103 | ###### Backwards compatible bug fixes 104 | - #149 - when removing an object from localStorage the key didn't get removed if the passed parameter is of number type. 105 | 106 | ##### 3.2.3 13 December 2014 107 | 108 | ###### Backwards compatible bug fixes 109 | - #112 - $resource cache and 3.0.0-beta-x 110 | - #122 - Error using DSCacheFactory with $http/ $resource and localStorage 111 | - #148 - Illegal operation when using local-/sessionStorage 112 | 113 | ##### 3.2.2 24 November 2014 114 | 115 | ###### Backwards compatible bug fixes 116 | - #147 - `storeOnResolve` and `storeOnReject` should default to `false` 117 | 118 | ##### 3.2.1 10 November 2014 119 | 120 | ###### Backwards compatible bug fixes 121 | - #142 - Use JSON.stringify instead of angular.toJson 122 | 123 | ##### 3.2.0 07 November 2014 124 | 125 | ###### Backwards compatible API changes 126 | - #135 - Closes #135. (Improved handling of promises.) 127 | 128 | ##### 3.1.1 28 August 2014 129 | 130 | ###### Backwards compatible bug fixes 131 | - #124 - DSCache.info does not work if the storageMode is localStorage. 132 | - #127 - requirejs conflict, require object overwritten 133 | 134 | ##### 3.1.0 15 July 2014 135 | 136 | ###### Backwards compatible API changes 137 | - #117 - call to DSCacheFactory(...) produces JSHint warning (Added DSCacheFactory.createCache method) 138 | 139 | ###### Backwards compatible bug fixes 140 | - #118 - dist/angular-cache.js doesn't end with a semicolon (Upgraded dependencies) 141 | - #120 - How come the non minified version has minified code? (Upgraded dependencies) 142 | 143 | ##### 3.0.3 16 June 2014 144 | 145 | ###### Backwards compatible bug fixes 146 | - Angular 1.2.18 with $http/localStorage #116 147 | 148 | ##### 3.0.2 15 June 2014 149 | 150 | ###### Backwards compatible bug fixes 151 | - $http w/ cache is trying to store a promise, which dies on JSON.stringify #115 152 | 153 | ##### 3.0.1 14 June 2014 154 | 155 | ###### Backwards compatible bug fixes 156 | - Added polyfill for `$$minErr`. 157 | 158 | ##### 3.0.0 14 June 2014 159 | 160 | 3.0.0 Release 161 | 162 | ##### 3.0.0-beta.4 22 April 2014 163 | 164 | ###### Backwards compatible API changes 165 | - Add feature to 'touch' elements in the cache #103 166 | 167 | ###### Backwards compatible bug fixes 168 | - `localstorage` and Safari Private Browsing #107 169 | 170 | ##### 3.0.0-beta.3 03 March 2014 171 | 172 | ###### Backwards compatible bug fixes 173 | - Fixed duplicate keys when using localStorage #106 174 | 175 | ##### 3.0.0-beta.2 25 February 2014 176 | 177 | ###### Backwards compatible bug fixes 178 | - Fixed missing reference to DSBinaryHeap #105 179 | 180 | ##### 3.0.0-beta.1 24 February 2014 181 | 182 | ###### Breaking API changes 183 | - `maxAge` and `deleteOnExpire` are no longer overridable for individual items 184 | - Renamed angular module to `angular-data.DSCacheFactory`. Angular-cache is now part of the `angular-data` namespace 185 | - The `verifyIntegrity` option has been completely removed due to a cache being exclusively in-memory OR in web storage #96 186 | - Supported values for the `storageMode` option are now: `"memory"`, `"localStorage"` or `"sessionStorage"` with the default being `"memory"` 187 | - `DSCache#put(key, value)` no longer accepts a third `options` argument 188 | - `DSCache#removeExpired()` no longer accepts an `options` argument and thus no longer supports returning removed expired items as an array 189 | - `DSCache#remove(key)` no longer accepts an `options` argument 190 | - `DSCache#setOptions(options[, strict])` no longer accepts `storageMode` and `storageImpl` as part of the `options` argument 191 | - `storageMode` is no longer dynamically configurable 192 | - `storageImpl` is no longer dynamically configurable 193 | 194 | ###### Backwards compatible API changes 195 | - Added `DSCache#enable()` 196 | - Added `DSCache#disable()` 197 | - Added `DSCache#setCapacity(capacity)` 198 | - Added `DSCache#setMaxAge(maxAge)` 199 | - Added `DSCache#setCacheFlushInterval(cacheFlushInterval)` 200 | - Added `DSCache#setRecycleFreq(recycleFreq)` 201 | - Added `DSCache#setDeleteOnExpire(deleteOnExpire)` 202 | - Added `DSCache#setOnExpire(onExpire)` 203 | - Added option `storagePrefix` for customizing the prefix used in `localStorage`, etc. #98 204 | - Refactored to be in-memory OR webStorage, never both #96 205 | 206 | ###### Other 207 | - I might have missed something... 208 | 209 | ##### 2.3.3 - 24 February 2014 210 | 211 | ###### Backwards compatible bug fixes 212 | - *sigh Fixed #102 (regression from #100) 213 | 214 | ##### 2.3.2 - 23 February 2014 215 | 216 | ###### Backwards compatible bug fixes 217 | - Fixed #100 (regression from #89) 218 | 219 | ##### 2.3.1 - 19 February 2014 220 | 221 | ###### Backwards compatible bug fixes 222 | - Fixed #89 223 | 224 | ##### 2.3.0 - 09 January 2014 225 | - Caches can now be disabled #82 226 | - The `options` object (`$angularCacheFactory()`, `AngularCache#setOptions()`, and `$angularCacheFactoryProvider.setCacheDefaults()`) now accepts a `disabled` field, which can be set to `true` and defaults to `false`. 227 | - `$angularCacheFactory.enableAll()` will enable any disabled caches. 228 | - `$angularCacheFactory.disableAll()` will disable all caches. 229 | - A disabled cache will operate as normal, except `AngularCache#get()` and `AngularCache#put()` will both immediately return `undefined` instead of performing their normal functions. 230 | 231 | ###### Backwards compatible API changes 232 | - `removeExpired()` now returns an object (or array) of the removed items. 233 | 234 | ###### Backwards compatible bug fixes 235 | - `removeExpired()` now removes _all_ expired items. 236 | 237 | ##### 2.2.0 - 15 December 2013 238 | 239 | ###### Backwards compatible API changes 240 | - `removeExpired()` now returns an object (or array) of the removed items. 241 | 242 | ###### Backwards compatible bug fixes 243 | - `removeExpired()` now removes _all_ expired items. 244 | 245 | ##### 2.1.1 - 20 November 2013 246 | 247 | ###### Backwards compatible bug fixes 248 | - Allow number keys, but stringify them #76 249 | - Fix "Uncaught TypeError: Cannot read property 'maxAge' of null" #77 (thanks @evngeny-o) 250 | 251 | ##### 2.1.0 - 03 November 2013 252 | 253 | ###### Backwards compatible API changes 254 | - Modify .get(key, options) to accept multiple keys #71 (thanks @roryf) 255 | 256 | ###### Other 257 | - Run tests against multiple versions of Angular.js #72 258 | - Add banner to dist/angular-cache.min.js #68 259 | 260 | ##### 2.0.0 - 30 October 2013 261 | - Not all methods of AngularCache and $angularCacheFactory are in README #61 262 | - Fix demo to work with 2.0.0-rc.1 #62 263 | - Using Bower to install this package, the dist filenames change per version? #63 264 | 265 | ##### 2.0.0-rc.1 - 14 October 2013 266 | 267 | ###### Breaking API changes 268 | - Swapped `aggressiveDelete` option for `deleteOnExpire` option. #30, #47 269 | - Changed `$angularCacheFactory.info()` to return an object similar to `AngularCache.info()` #45 270 | - Namespaced angular-cache module under `jmdobry` so it is now "jmdobry.angular-cache". #42 271 | - Substituted `storageImpl` and `sessionStorageImpl` options for just `storageImpl` option. 272 | 273 | ###### Backwards compatible API changes 274 | - Added `recycleFreq` to specify how frequently to check for expired items (no more $timeout). #28, #57 275 | - Added ability to set global cache defaults in $angularCacheFactoryProvider. #55 276 | 277 | ###### Backwards compatible bug fixes 278 | - cacheFlushInterval doesn't clear web storage when storageMode is used. #52 279 | - AngularCache#info(key) should return 'undefined' if the key isn't in the cache #53 280 | - Fixed timespan issues in README.md. #59 281 | 282 | ###### Other 283 | - Refactored angular-cache `setOptions()` internals to be less convoluted and to have better validation. #46 284 | - Re-wrote documentation to be clearer and more organized. #56 285 | - Fixed documentation where time spans were incorrectly labeled. #59 286 | 287 | ##### 1.2.0 - 20 September 2013 288 | 289 | ###### Backwards compatible API changes 290 | - Added AngularCache#info(key) #43 291 | 292 | ###### Backwards compatible bug fixes 293 | - Fixed #39, #44, #49, #50 294 | 295 | ##### 1.1.0 - 03 September 2013 296 | 297 | ###### Backwards compatible API changes 298 | - Added `onExpire` callback hook #27 299 | - Added `$angularCacheFactory.removeAll()` and `$angularCacheFactory.clearAll()` convenience methods #37, #38 300 | 301 | ###### Backwards compatible bug fixes 302 | - Fixed #36 303 | 304 | ##### 1.0.0 - 25 August 2013 305 | - Closed #31 (Improved documentation) 306 | - Closed #32 307 | 308 | ##### 1.0.0-rc.1 - 21 August 2013 309 | - Added localStorage feature #26, #29 310 | 311 | ##### 0.9.1 - 03 August 2013 312 | - Fixed #25 313 | 314 | ##### 0.9.0 - 03 August 2013 315 | - Added a changelog #13 316 | - Added documentation for installing with bower 317 | - Added ability to set option `aggressiveDelete` when creating cache and when adding items 318 | - Cleaned up README.md 319 | - Switched the demo to use Bootstrap 3 320 | 321 | ##### 0.8.2 - 09 July 2013 322 | - Added CONTRIBUTING.md #22 323 | - Cleaned up meta data in bower.json and package.json 324 | 325 | ##### 0.8.1 - 09 July 2013 326 | - Added .jshintrc 327 | - Cleaned up the docs a bit 328 | - `bower.json` now uses `src/angular-cache.js` instead of the versioned output files #21 329 | - From now on the tags for the project will be named using [semver](http://semver.org/) 330 | 331 | ##### 0.8.0 - 08 July 2013 332 | - Added `AngularCache.setOptions()`, the ability to dynamically change the configuration of a cache #20 333 | - Added `AngularCache.keys()`, which returns an array of the keys in a cache #19 334 | - Added `AngularCache.keySet()`, which returns a hash of the keys in a cache #19 335 | 336 | ##### 0.7.2 - June 2013 337 | - Added `angular-cache` to bower registry #7 338 | - Created a working demo #9 #17 339 | - Fixed the size not being reset to 0 when the cache clears itself #14 #16 340 | - Added `$angularCacheFactory.keys()`, which returns an array of the keys (the names of the caches) in $angularCacheFactory #18 341 | - Added `$angularCacheFactory.keySet()`, which returns a hash of the keys (the names of the caches) in $angularCacheFactory #18 342 | 343 | ##### 0.6.1 - June 2013 344 | - Got the project building on TravisCI 345 | - Renamed the project to `angular-cache` #5 346 | 347 | ##### 0.5.0 - June 2013 348 | - Added a roadmap to README.md #4 349 | - Clarify usage documentation #3 350 | - Wrote unit tests #2 351 | 352 | ##### 0.4.0 - May 2013 353 | - Added Grunt build tasks #1 354 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | Contributing to `angular-cache` is fairly easy. This document shows you how to 4 | get the project, run all provided tests and generate a production ready build. 5 | 6 | It also covers provided grunt tasks, that help you developing on `angular-cache`. 7 | 8 | ## Dependencies 9 | 10 | To make sure, that the following instructions work, please install the following dependecies 11 | on you machine: 12 | 13 | - Node.js 14 | - npm 15 | - Git 16 | 17 | If you install node through the binary installation file, **npm** will be already there. 18 | When **npm** is installed, use it to install the needed npm packages: 19 | 20 | - bower `npm install -g bower` 21 | - grunt-cli `npm install -g grunt-cli` 22 | - karma `npm install -g karma` 23 | 24 | ## Installation 25 | 26 | To get the source of `angular-cache` clone the git repository via: 27 | 28 | ```` 29 | $ git clone https://github.com/jmdobry/angular-cache 30 | ```` 31 | 32 | This will clone the complete source to your local machine. Navigate to the project folder 33 | and install all needed dependencies via **npm** and **bower**: 34 | 35 | ```` 36 | $ npm install 37 | $ bower install 38 | ```` 39 | 40 | `angular-cache` is now installed and ready to be built. 41 | 42 | ## Building 43 | 44 | `angular-cache` comes with a few **grunt tasks** which help you to automate 45 | the development process. The following grunt tasks are provided: 46 | 47 | #### `grunt` 48 | 49 | Running `grunt` without any parameters, will actually execute the registered 50 | default task. This task is currently nothing more then a **lint task**, to make sure 51 | that your JavaScript code is written well. 52 | 53 | #### `grunt test` 54 | 55 | `grunt test` executes the unit tests, which are located in `test/`. The task uses **karma** the spectacular test runner to execute the tests with the **jasmine testing framework**. 56 | 57 | #### `grunt build` 58 | 59 | You only have to use this task, if you want to generate a production ready build of 60 | `angular-cache`. This task will also **lint**, **test** and **minify** the 61 | source. After running this task, you'll find the following files in a generated 62 | `dist` folder: 63 | 64 | ```` 65 | dist/angular-cache-x.x.x.js 66 | dist/angular-cache-x.x.x.min.js 67 | ```` 68 | 69 | ## Contributing/Submitting changes 70 | 71 | - Checkout a new branch based on `master` and name it to what you intend to do: 72 | - Example: 73 | ```` 74 | $ git checkout -b BRANCH_NAME 75 | ```` 76 | - Use one branch per fix/feature 77 | - Make your changes 78 | - Make sure to provide a spec for unit tests 79 | - Run your tests with either `karma start` or `grunt test` 80 | - When all tests pass, everything's fine 81 | - Commit your changes 82 | - Please provide a git message which explains what you've done 83 | - Commit to the forked repository 84 | - Make a pull request 85 | - Travis CI is watching you! 86 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (C) 2013-2016 angular-cache project authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-cache 2 | 3 | [![bower version](https://img.shields.io/bower/v/angular-cache.svg?style=flat)](https://www.npmjs.org/package/angular-cache) 4 | [![npm version](https://img.shields.io/npm/v/angular-cache.svg?style=flat)](https://www.npmjs.org/package/angular-cache) 5 | [![Circle CI](https://img.shields.io/circleci/project/jmdobry/angular-cache/master.svg?style=flat)](https://circleci.com/gh/jmdobry/angular-cache/tree/master) 6 | [![npm downloads](https://img.shields.io/npm/dm/angular-cache.svg?style=flat)](https://www.npmjs.org/package/angular-cache) 7 | [![codecov](https://img.shields.io/codecov/c/github/jmdobry/angular-cache.svg)](https://codecov.io/gh/jmdobry/angular-cache) 8 | 9 | A very useful replacement for Angular's $cacheFactory. 10 | 11 | __versions of angular-cache below 4.0.0 have been deprecated, see the [breaking changes](https://github.com/jmdobry/angular-cache/blob/master/CHANGELOG.md) in 4.0.0__ 12 | 13 | ### Table of Contents 14 | - [Quick Start](#quick-start) 15 | - [The Basics](#the-basics) 16 | - [Working with a cache](#working-with-a-cache) 17 | - [Configuration Options](#configuration-options) 18 | - [Using angular-cache with $http](#using-angular-cache-with-http) 19 | - [Using angular-cache with localStorage](#using-angular-cache-with-localstorage) 20 | - [API Reference](#api-reference) 21 | 22 | ### Quick Start 23 | `bower install --save angular-cache` or `npm install --save angular-cache`. 24 | 25 | ```js 26 | angular.module('myApp', ['angular-cache']) 27 | .config(function (CacheFactoryProvider) { 28 | angular.extend(CacheFactoryProvider.defaults, { maxAge: 15 * 60 * 1000 }); 29 | }) 30 | .service('BookService', function (CacheFactory, $http) { 31 | if (!CacheFactory.get('bookCache')) { 32 | // or CacheFactory('bookCache', { ... }); 33 | CacheFactory.createCache('bookCache', { 34 | deleteOnExpire: 'aggressive', 35 | recycleFreq: 60000 36 | }); 37 | } 38 | 39 | var bookCache = CacheFactory.get('bookCache'); 40 | 41 | return { 42 | findBookById: function (id) { 43 | return $http.get('/api/books/' + id, { cache: bookCache }); 44 | } 45 | }; 46 | }); 47 | ``` 48 | 49 | #### Why would you want to replace $cacheFactory? 50 | 51 | | Feature | $cacheFactory | angular-cache | 52 | | ----------- | ------------------- | --------------- | 53 | | core implementation usable outside of Angular | no | [__yes__](https://github.com/jmdobry/CacheFactory) | 54 | | key/value store | __yes__ | __yes__ | 55 | | in-memory support | __yes__ | __yes__ | 56 | | localStorage support | no | __yes__ | 57 | | sessionStorage support | no | __yes__ | 58 | | custom storage support | no | __yes__ | 59 | | can set maximum capacity | __yes__ | __yes__ | 60 | | LRU support | __yes__ | __yes__ | 61 | | support for time-based expiration of items | no | __yes__ | 62 | | remove all expired items | n/a | __yes__ | 63 | | "touch" an item | n/a | __yes__ | 64 | | execute callback when an item expires | n/a | __yes__ | 65 | | get info about cache | __yes__ | __yes__ | 66 | | get info about single item | no | __yes__ | 67 | | get item by key | __yes__ | __yes__ | 68 | | remove item by key | __yes__ | __yes__ | 69 | | remove all items | __yes__ | __yes__ | 70 | | get keys of all caches | no | __yes__ | 71 | | clear all caches | no | __yes__ | 72 | | disable/enable a cache | no | __yes__ | 73 | | "touch" all items in all caches | n/a | __yes__ | 74 | | remove expired items from all caches | n/a | __yes__ | 75 | | get all keys in a cache | no | __yes__ | 76 | | clear a cache on a given interval | no | __yes__ | 77 | 78 | ### The Basics 79 | 80 | First, inject `CacheFactory` then create a cache. Let's go: 81 | 82 | ```js 83 | app.service('myService', function (CacheFactory) { 84 | var profileCache; 85 | 86 | // Check to make sure the cache doesn't already exist 87 | if (!CacheFactory.get('profileCache')) { 88 | profileCache = CacheFactory('profileCache'); 89 | } 90 | }); 91 | ``` 92 | 93 | Let's add some items to the cache: 94 | 95 | ```js 96 | profileCache.put('/profiles/34', { 97 | name: 'John', 98 | skills: ['programming', 'piano'] 99 | }); 100 | 101 | profileCache.put('/profiles/22', { 102 | name: 'Sally', 103 | skills: ['marketing', 'climbing', 'painting'] 104 | }); 105 | ``` 106 | 107 | Right now, these items will stay in the cache until a page refresh. 108 | 109 | Let's have items which are added to `profileCache` expire after an hour: 110 | 111 | ```js 112 | profileCache = CacheFactory('profileCache', { 113 | maxAge: 60 * 60 * 1000 // 1 hour 114 | }); 115 | ``` 116 | 117 | Perfect. Say we also want the items removed from the cache when they expire: 118 | 119 | ```js 120 | profileCache = CacheFactory('profileCache', { 121 | maxAge: 60 * 60 * 1000, // 1 hour 122 | deleteOnExpire: 'aggressive' 123 | }); 124 | ``` 125 | 126 | Let's say that when the items do expire, we want to refresh them with new values: 127 | 128 | ```js 129 | profileCache = CacheFactory('profileCache', { 130 | maxAge: 60 * 60 * 1000, // 1 hour 131 | deleteOnExpire: 'aggressive', 132 | onExpire: function (key, value) { 133 | $http.get(key).success(function (data) { 134 | profileCache.put(key, data); 135 | }); 136 | } 137 | }); 138 | ``` 139 | 140 | Or say we want all of our caches to use that configuration as their default: 141 | 142 | ```js 143 | angular.module('app', ['angular-cache']).config(function (CacheFactoryProvider) { 144 | angular.extend(CacheFactoryProvider.defaults, { 145 | maxAge: 3600000, 146 | deleteOnExpire: 'aggressive', 147 | onExpire: function (key, value) { 148 | var _this = this; // "this" is the cache in which the item expired 149 | angular.injector(['ng']).get('$http').get(key).success(function (data) { 150 | _this.put(key, data); 151 | }); 152 | } 153 | }); 154 | }); 155 | ``` 156 | 157 | ### Working with a cache 158 | 159 | We can retrieve items from a cache like so: 160 | 161 | ```js 162 | var profile = profileCache.get('/profiles/34'); 163 | 164 | profile.name; // 'John' 165 | ``` 166 | 167 | And get information about items in the cache: 168 | 169 | ```js 170 | var info = profileCache.info('/profiles/34'); 171 | 172 | info.isExpired; // false 173 | // etc. 174 | ``` 175 | 176 | and information about the cache itself: 177 | 178 | ``` 179 | var info = profileCache.info(); 180 | 181 | info.size; // 2 182 | info.maxAge; // 3600000 183 | info.deleteOnExpire; // 'aggressive' 184 | // etc. 185 | ``` 186 | 187 | Items are easily removed, and we can destroy our cache when we're done with it: 188 | 189 | ```js 190 | profileCache.remove('/profiles/34'); 191 | 192 | profileCache.get('/profiles/34'); // undefined 193 | 194 | profileCache.destroy(); 195 | 196 | CacheFactory.get('profileCache'); // undefined 197 | ``` 198 | 199 | ### Configuration Options 200 | 201 | These options apply to: 202 | 203 | - `CacheFactory(cacheId[, options)` 204 | - `CacheFactory.createCache(cacheId[, options])` 205 | - `Cache#setOptions(options[, strict])` 206 | - `Cache#setMaxAge(maxAge)`, `Cache#setOnExpire(onExpire)`, etc. 207 | 208 | ##### `cacheFlushInterval` 209 | 210 | If set, remove all items from a cache on an interval after the given number of milliseconds. Default: `null`. 211 | 212 | ##### `capacity` 213 | 214 | Maximum number of items a cache can hold. Adding more items than the capacity will cause the cache to operate like an LRU cache, removing the least recently used items to stay under capacity. Default: `Number.MAX_VALUE`. 215 | 216 | ##### `deleteOnExpire` 217 | 218 | Determines the behavior of a cache when an item expires. Default: `none`. 219 | 220 | Possible values: 221 | 222 | - `none` - Cache will do nothing when an item expires. 223 | - `passive` - Cache will do nothing when an item expires. Expired items will remain in the cache until requested, at which point they are removed, and `undefined` is returned. 224 | - `aggressive` - Cache will remove expired items as soon as they are discovered. 225 | 226 | ##### `disabled` 227 | 228 | Determines whether a cache is disabled. Default: `false`. 229 | 230 | ##### `onExpire` 231 | 232 | A callback function to be executed whenever an expired item is removed from a cache when the cache is in `passive` or `aggressive` mode. Will be passed the `key` and `value` of the expired item. 233 | 234 | Will be passed a third `done` argument if the cache is in `passive` mode. This allows you to synchronously access the `key` and `value` of the expired item when you make the `Cache#get(key[, options])` call that is the reason the expired item is being removed in the first place. Default: `null`. 235 | 236 | ##### `maxAge` 237 | 238 | The number of milliseconds until a newly inserted item expires. Default: `Number.MAX_VALUE`. 239 | 240 | ##### `recycleFreq` 241 | 242 | Determines how often a cache will scan for expired items when in `aggressive` mode. Default: `1000` (milliseconds). 243 | 244 | ##### `storageImpl` 245 | 246 | Provide a custom storage medium, e.g. a polyfill for `localStorage`. Default: `null`. 247 | 248 | Must implement: 249 | 250 | - `setItem` - Same API as `localStorage.setItem(key, value)` 251 | - `getItem` - Same API as `localStorage.getItem(key)` 252 | - `removeItem` - Same API as `localStorage.removeItem(key)` 253 | 254 | ##### `storageMode` 255 | 256 | Determines the storage medium used by a cache. Default: `memory`. 257 | 258 | Possible values: 259 | 260 | - `memory` - Cache will hold data in memory. Data is cleared when the page is refreshed. 261 | - `localStorage` - Cache will hold data in `localStorage` if available. Data is _not_ cleared when the page is refreshed. 262 | - `sessionStorage` - Cache will hold data in `sessionStorage` if available. Data is _not_ cleared when the page is refreshed. 263 | 264 | ##### `storagePrefix` 265 | 266 | Determines the namespace of a cache when `storageMode` is set to `localStorage` or `sessionStorage`. Make it a shorter string to save space. Default: `angular-cache.caches.`. 267 | 268 | ##### `storeOnReject` 269 | 270 | If inserting a promise into a cache, also insert the rejection value if the promise rejects. Default: `false`. 271 | 272 | ##### storeOnResolve 273 | 274 | If inserting a promise into a cache, also insert the resolved value if the promise resolves. Default: `false`. 275 | 276 | ### Using angular-cache with $http 277 | 278 | __Note:__ The downside of letting `$http` handle caching for you is that it caches the responses (in string form) to your requests–not the JavaScript Object parsed from the response body. This means you can't interact with the data in the cache used by `$http`. See below for how to handle the caching yourself, which gives you more control and the ability to interact with the cache (use it as a data store). 279 | 280 | Configure `$http` to use a cache created by `CacheFactory` by default: 281 | 282 | ```js 283 | app.run(function ($http, CacheFactory) { 284 | $http.defaults.cache = CacheFactory('defaultCache', { 285 | maxAge: 15 * 60 * 1000, // Items added to this cache expire after 15 minutes 286 | cacheFlushInterval: 60 * 60 * 1000, // This cache will clear itself every hour 287 | deleteOnExpire: 'aggressive' // Items will be deleted from this cache when they expire 288 | }); 289 | }); 290 | ``` 291 | 292 | ```js 293 | app.service('MyService', function ($http, $q) { 294 | return { 295 | getDataById: function (id) { 296 | var deferred = $q.defer(); 297 | var start = new Date().getTime(); 298 | 299 | $http.get('api/data/' + id, { 300 | cache: true 301 | }).success(function (data) { 302 | console.log('time taken for request: ' + (new Date().getTime() - start) + 'ms'); 303 | deferred.resolve(data); 304 | }); 305 | return deferred.promise; 306 | } 307 | }; 308 | }); 309 | ``` 310 | 311 | ```js 312 | app.controller('myCtrl', function (MyService) { 313 | MyService.getDataById(1).then(function (data) { 314 | // e.g. "time taken for request: 2375ms" 315 | // Data returned by this next call is already cached. 316 | return MyService.getDataById(1).then(function (data) { 317 | // e.g. "time taken for request: 1ms" 318 | }); 319 | }); 320 | }); 321 | ``` 322 | 323 | Tell $http to use a cache created by CacheFactory for a specific request: 324 | 325 | ```js 326 | app.service('MyService', function ($q, $http, CacheFactory) { 327 | 328 | CacheFactory('dataCache', { 329 | maxAge: 15 * 60 * 1000, // Items added to this cache expire after 15 minutes 330 | cacheFlushInterval: 60 * 60 * 1000, // This cache will clear itself every hour 331 | deleteOnExpire: 'aggressive' // Items will be deleted from this cache when they expire 332 | }); 333 | 334 | return { 335 | getDataById: function (id) { 336 | var deferred = $q.defer(); 337 | var start = new Date().getTime(); 338 | 339 | $http.get('api/data/' + id, { 340 | cache: CacheFactory.get('dataCache') 341 | }).success(function (data) { 342 | console.log('time taken for request: ' + (new Date().getTime() - start) + 'ms'); 343 | deferred.resolve(data); 344 | }); 345 | return deferred.promise; 346 | } 347 | }; 348 | }); 349 | ``` 350 | 351 | ```js 352 | app.controller('myCtrl', function (MyService) { 353 | MyService.getDataById(1).then(function (data) { 354 | // e.g. "time taken for request: 2375ms" 355 | // Data returned by this next call is already cached. 356 | return MyService.getDataById(1).then(function (data) { 357 | // e.g. "time taken for request: 1ms" 358 | }); 359 | }); 360 | }); 361 | ``` 362 | 363 | Do your own caching while using the $http service: 364 | 365 | ```js 366 | app.service('MyService', function ($q, $http, CacheFactory) { 367 | 368 | CacheFactory('dataCache', { 369 | maxAge: 15 * 60 * 1000, // Items added to this cache expire after 15 minutes 370 | cacheFlushInterval: 60 * 60 * 1000, // This cache will clear itself every hour 371 | deleteOnExpire: 'aggressive' // Items will be deleted from this cache when they expire 372 | }); 373 | 374 | return { 375 | getDataById: function (id) { 376 | var deferred = $q.defer(); 377 | var start = new Date().getTime(); 378 | var dataCache = CacheFactory.get('dataCache'); 379 | 380 | // Now that control of inserting/removing from the cache is in our hands, 381 | // we can interact with the data in "dataCache" outside of this context, 382 | // e.g. Modify the data after it has been returned from the server and 383 | // save those modifications to the cache. 384 | if (dataCache.get(id)) { 385 | deferred.resolve(dataCache.get(id)); 386 | } else { 387 | $http.get('api/data/' + id).success(function (data) { 388 | console.log('time taken for request: ' + (new Date().getTime() - start) + 'ms'); 389 | dataCache.put(id, data); 390 | deferred.resolve(data); 391 | }); 392 | } 393 | return deferred.promise; 394 | } 395 | }; 396 | }); 397 | ``` 398 | 399 | ```js 400 | app.controller('myCtrl', function (MyService) { 401 | MyService.getDataById(1).then(function (data) { 402 | // e.g. "time taken for request: 2375ms" 403 | // Data returned by this next call is already cached. 404 | return MyService.getDataById(1).then(function (data) { 405 | // e.g. "time taken for request: 1ms" 406 | }); 407 | }); 408 | }); 409 | ``` 410 | 411 | ### Using angular-cache with localStorage 412 | 413 | ```js 414 | app.service('myService', function (CacheFactory) { 415 | 416 | // This cache will sync itself with localStorage if it exists, otherwise it won't. Every time the 417 | // browser loads this app, this cache will attempt to initialize itself with any data it had 418 | // already saved to localStorage (or sessionStorage if you used that). 419 | var myAwesomeCache = CacheFactory('myAwesomeCache', { 420 | maxAge: 15 * 60 * 1000, // Items added to this cache expire after 15 minutes. 421 | cacheFlushInterval: 60 * 60 * 1000, // This cache will clear itself every hour. 422 | deleteOnExpire: 'aggressive', // Items will be deleted from this cache right when they expire. 423 | storageMode: 'localStorage' // This cache will use `localStorage`. 424 | }); 425 | }); 426 | ``` 427 | 428 | Using angular-cache in browsers that DON'T support localStorage: 429 | 430 | Option 1 - Do nothing (the cache will just store data in memory) 431 | 432 | Option 2 - Create/use a polyfill that provides the global `localStorage` and `sessionStorage` objects. angular-cache will attempt to use these if it finds them. 433 | 434 | Option 3 - Tell angular-cache exactly which polyfill to use (also useful if you just want to use your own implementation/wrapper for localStorage): 435 | 436 | ```js 437 | app.service('myService', function (CacheFactory) { 438 | 439 | var localStoragePolyfill = { 440 | getItem: function (key) { ... }, 441 | setItem: function (key, value) { ... }, 442 | removeItem: function (key) { ... } 443 | }; 444 | 445 | // Always use the polyfill 446 | var myAwesomeCache = CacheFactory('myAwesomeCache', { 447 | maxAge: 15 * 60 * 1000, // Items added to this cache expire after 15 minutes. 448 | cacheFlushInterval: 60 * 60 * 1000, // This cache will clear itself every hour. 449 | deleteOnExpire: 'aggressive', // Items will be deleted from this cache right when they expire. 450 | storageMode: 'localStorage', // This cache will use `localStorage`. 451 | storageImpl: localStoragePolyfill // angular-cache will use this polyfill instead of looking for localStorage 452 | }); 453 | 454 | // Conditionally use the polyfill 455 | var options = { 456 | maxAge: 15 * 60 * 1000, // Items added to this cache expire after 15 minutes. 457 | cacheFlushInterval: 60 * 60 * 1000, // This cache will clear itself every hour. 458 | deleteOnExpire: 'aggressive', // Items will be deleted from this cache right when they expire. 459 | storageMode: 'localStorage' // This cache will use `localStorage`. 460 | }; 461 | if (!window.localStorage) { 462 | options.storageImpl = localStoragePolyfill; 463 | } 464 | var myAwesomeCache = CacheFactory('myAwesomeCache', options); 465 | }); 466 | ``` 467 | 468 | Documentation on the interface that must be implemented by any storageImpl polyfill used by angular-cache can be found on the W3C Recommendation page for webstorage. The interface itself looks like: 469 | 470 | ``` 471 | interface Storage { 472 | readonly attribute unsigned long length; 473 | DOMString? key(unsigned long index); 474 | getter DOMString getItem(DOMString key); 475 | setter creator void setItem(DOMString key, DOMString value); 476 | deleter void removeItem(DOMString key); 477 | void clear(); 478 | }; 479 | ``` 480 | 481 | angular-cache cares only about these three methods: 482 | 483 | - `setItem` 484 | - `getItem` 485 | - `removeItem` 486 | 487 | One developer suggested using store.js–a wrapper and polyfill for localStorage. However, store.js has its own API that doesn't match that of the webstorage spec, so if you want to use store.js or any other 3rd-party polyfill then you'll need to create a wrapper for it if it doesn't have the same API as localStorage . For example: 488 | 489 | ```js 490 | var storeJsToStandard { 491 | getItem: store.get, 492 | setItem: store.set, 493 | removeItem: store.remove 494 | }; 495 | 496 | CacheFactory('myNewCache', { 497 | storageMode: 'localStorage', 498 | storageImpl: storeJsToStandard 499 | }); 500 | ``` 501 | 502 | ### API Reference 503 | 504 | ##### `CacheFactory(cacheId[, options])` & `CacheFactory.createCache(cacheId[, options])` 505 | 506 | Create a cache. Cache must not already exist. `cacheId` must be a string. `options` is an optional argument and must be an object. Any options you pass here will override any default options. 507 | 508 | ```js 509 | var cache = CacheFactory('cache'); 510 | var cache2 = CacheFactory.createCache('cache2'); 511 | var cache3 = CacheFactory('cache', { maxAge: 900000 }); 512 | var cache4 = CacheFactory('cache'); // Error "cache already exists!" 513 | ``` 514 | 515 | ##### `CacheFactory.get(cacheId)` 516 | 517 | Return the cache with the given `cacheId`. __Note:__ angular-cache does not automatically load meta data about previously created caches from the defined storage system. This means that on a page refresh this function will return `undefined`. To 'reload' an existing cache simply re-create the cache with the same name every time your app loads, and the cache will possibly load any data that was previously saved under that cache's name. 518 | 519 | ##### `CacheFactory.info()` 520 | 521 | Return an object of key-value pairs, the keys being cache ids and the values being the result of `.info()` being called on each cache. 522 | 523 | ##### `CacheFactory.keySet()` 524 | 525 | Return the ids of all registered caches as an object. 526 | 527 | ##### `CacheFactory.keys()` 528 | 529 | Return the ids of all registered caches as an array. 530 | 531 | ##### `CacheFactory.destroy(cacheId)` 532 | 533 | Destroy the cache with the given `cacheId`. 534 | 535 | ##### `CacheFactory.destroyAll()` 536 | 537 | Destroy all registered caches. 538 | 539 | ##### `CacheFactory.clearAll()` 540 | 541 | Remove all data from all registered caches. 542 | 543 | ##### `CacheFactory.enableAll()` 544 | 545 | Enable all registered caches. 546 | 547 | ##### `CacheFactory.disableAll()` 548 | 549 | Disable all registered caches. 550 | 551 | ##### `CacheFactory.touchAll()` 552 | 553 | Call `.touch()` on all registered caches. 554 | 555 | ##### `CacheFactory.removeExpiredFromAll()` 556 | 557 | Call `.removeExpired()` on all registered caches. Returns a hash of any expired items, keyed by cache id. 558 | 559 | ##### `Cache#get(key[, options])` 560 | 561 | Return the item with the given `key`. `options`, if provided, must be an object. 562 | 563 | If the cache is in `passive` mode, then `options.onExpire` can be a function that will be called with the `key` and `value` of the requested item if the requested item is expired, with the `get` call itself returning undefined. 564 | 565 | ##### `Cache#put(key, value[, options])` 566 | 567 | Insert the item with the given `key` and `value` into the cache. `options`, if provided, must be an object. 568 | 569 | If inserting a promise, `options.storeOnReject` determines whether to insert the rejection value if the promise rejects (overriding the default `storeOnReject` setting for the cache). 570 | If inserting a promise, `options.storeOnResolve` determines whether to insert the resolved value if the promise resolves (overriding the default `storeOnResolve` setting for the cache). 571 | 572 | ##### `Cache#remove(key)` 573 | 574 | Remove and return the item with the given `key`, if it is in the cache. 575 | 576 | ##### `Cache#removeAll()` 577 | 578 | Remove all items in the cache. 579 | 580 | ##### `Cache#removeExpired()` 581 | 582 | Remove and return all expired items in the cache. 583 | 584 | ##### `Cache#destroy()` 585 | 586 | Completely destroy this cache and its data. 587 | 588 | ##### `Cache#info([key])` 589 | 590 | `Cache#info()` returns an object containing information about the cache. 591 | 592 | `Cache#info(key)` returns an object containing information about the item with the given `key`, if the item is in the cache. 593 | 594 | ##### `Cache#keySet()` 595 | 596 | Return the keys of all items in the cache as an object. 597 | 598 | ##### `Cache#keys()` 599 | 600 | Return the keys of all items in the cache as an array. 601 | 602 | ##### `Cache#enable()` 603 | 604 | Enable the cache. 605 | 606 | ##### `Cache#disable()` 607 | 608 | Disable the cache. 609 | 610 | ##### `Cache#values()` 611 | 612 | Return all values in the cache. 613 | 614 | ##### `Cache#touch([key])` 615 | 616 | `Cache#touch()` will "touch" all items in the cache. 617 | `Cache#touch(key)` will "touch" the item with the given `key`. 618 | 619 | ##### `Cache#setCacheFlushInterval(cacheFlushInterval)` 620 | 621 | Set the `cacheFlushInterval` for the cache. 622 | 623 | ##### `Cache#setCapacity(capacity)` 624 | 625 | Set the `capacity` for the cache. Setting this lower than the current item count will result in those items being removed. 626 | 627 | ##### `Cache#setDeleteOnExpire(deleteOnExpire)` 628 | 629 | Set the `deleteOnExpire` for the cache. 630 | 631 | ##### `Cache#setMaxAge(maxAge)` 632 | 633 | Set the `maxAge` for the cache. 634 | 635 | ##### `Cache#setOnExpire(onExpire)` 636 | 637 | Set the `onExpire` for the cache. 638 | 639 | ##### `Cache#setRecycleFreq(recycleFreq)` 640 | 641 | Set the `recycleFreq` for the cache. 642 | 643 | ##### `Cache#setStorageMode(storageMode)` 644 | 645 | Set the `storageMode` for the cache. This will move data from the current storage medium to the new one. 646 | 647 | ##### `Cache#setOptions(options[, strict])` 648 | 649 | Set multiple options for the cache at a time. Setting `strict` to `true` will reset options for the cache that are not specifically set in the `options` hash to `CacheFactoryProvider.defaults`. 650 | 651 | ### License 652 | [MIT License](https://github.com/jmdobry/angular-cache/blob/master/LICENSE) 653 | 654 | Copyright (C) 2013-2016 angular-cache project authors 655 | 656 | Permission is hereby granted, free of charge, to any person obtaining a copy of 657 | this software and associated documentation files (the "Software"), to deal in 658 | the Software without restriction, including without limitation the rights to 659 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 660 | of the Software, and to permit persons to whom the Software is furnished to do 661 | so, subject to the following conditions: 662 | 663 | The above copyright notice and this permission notice shall be included in all 664 | copies or substantial portions of the Software. 665 | 666 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 667 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 668 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 669 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 670 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 671 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 672 | -------------------------------------------------------------------------------- /TRANSITION.md: -------------------------------------------------------------------------------- 1 | ### 2.x.x. ---> 3.x.x - xx April 2013 2 | TODO 3 | 4 | ### 1.x.x. ---> 2.0.0 - 30 October 2013 5 | 6 | #### Breaking API changes 7 | ##### Swapped `aggressiveDelete` option for `deleteOnExpire` option. 8 | 9 | ###### 1.x.x 10 | Aggressively delete expiring items. 11 | ```javascript 12 | $angularCacheFactory('myNewCache', { 13 | maxAge: 90000, // Items added to this cache expire after 15 minutes 14 | aggressiveDelete: true // Items will be actively deleted when they expire 15 | }); 16 | ``` 17 | 18 | Passively delete items when they are requested after they have expired. 19 | ```javascript 20 | $angularCacheFactory('myNewCache', { 21 | maxAge: 90000, // Items added to this cache expire after 15 minutes 22 | aggressiveDelete: false // Items will be actively deleted when they expire 23 | }); 24 | ``` 25 | 26 | ###### 2.0.0 27 | Aggressively delete expiring items. 28 | ```javascript 29 | $angularCacheFactory('myNewCache', { 30 | maxAge: 90000, // Items added to this cache expire after 15 minutes 31 | deleteOnExpire: 'aggressive' // Items will be actively deleted when they expire 32 | }); 33 | ``` 34 | 35 | Passively delete items when they are requested after they have expired. 36 | ```javascript 37 | $angularCacheFactory('myNewCache', { 38 | maxAge: 90000, // Items added to this cache expire after 15 minutes 39 | deleteOnExpire: 'passive' // Items will be passively deleted when requested after expiration 40 | }); 41 | ``` 42 | 43 | Do nothing with expired items (not in 1.x.x). 44 | ```javascript 45 | $angularCacheFactory('myNewCache', { 46 | maxAge: 90000, // Items added to this cache expire after 15 minutes 47 | deleteOnExpire: 'none' // Items will expire but not be removed 48 | }); 49 | ``` 50 | 51 | ##### Substituted `localStorageImpl` and `sessionStorageImpl` options for just `storageImpl` option. 52 | 53 | ###### 1.x.x 54 | ```javascript 55 | $angularCacheFactory('myNewCache', { 56 | storageMode: 'localStorage', 57 | localStorageImpl: myLocalStoragePolyfill // Use custom localStorage implementation 58 | }); 59 | 60 | $angularCacheFactory('myNewCache2', { 61 | storageMode: 'sessionStorage', 62 | sessionStorageImpl: mySessionStoragePolyfill // Use custom sessionStorage implementation 63 | }); 64 | ``` 65 | 66 | ###### 2.0.0 67 | ```javascript 68 | $angularCacheFactory('myNewCache', { 69 | storageMode: 'localStorage', 70 | storageImpl: myLocalStoragePolyfill // Use custom localStorage implementation 71 | }); 72 | 73 | $angularCacheFactory('myNewCache2', { 74 | storageMode: 'sessionStorage', 75 | storageImpl: mySessionStoragePolyfill // Use custom sessionStorage implementation 76 | }); 77 | ``` 78 | 79 | ##### Installation 80 | The Bower package now contains only `dist/angular-cache.js` and `dist/angular-cache.min.js`. 81 | 82 | ##### onExpire 83 | 84 | ###### 1.x.x 85 | ```javascript 86 | cache.get('someKey', function (key, value) { 87 | // do something with expired item 88 | }); 89 | ``` 90 | 91 | ###### 2.0.0 92 | ```javascript 93 | cache.get('someKey', { 94 | onExpire: function (key, value) { 95 | // do something with expired item 96 | } 97 | }); 98 | ``` 99 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Jason Dobry", 3 | "name": "angular-cache", 4 | "description": "angular-cache is a very useful replacement for Angular's $cacheFactory.", 5 | "homepage": "https://github.com/jmdobry/angular-cache", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/jmdobry/angular-cache.git" 9 | }, 10 | "main": "./dist/angular-cache.js", 11 | "ignore": [ 12 | ".idea/", 13 | ".*", 14 | "*.iml", 15 | "src/", 16 | "bower_components/", 17 | "coverage/", 18 | "Gruntfile.js", 19 | "package.json", 20 | "CONTRIBUTING.md", 21 | "node_modules/", 22 | "test/", 23 | "karma.*.js", 24 | "libpeerconnection.log", 25 | "build_examples/" 26 | ], 27 | "devDependencies": { 28 | "angular-1.2.25": "angular#1.2.25", 29 | "angular-resource": "angular-resource#1.2.25", 30 | "angular-mocks-1.2.25": "angular-mocks#1.2.25" 31 | }, 32 | "dependencies": { 33 | "angular": ">=1.x" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /build_examples/browserify/README.md: -------------------------------------------------------------------------------- 1 | Running `browserify app.js > bundle.js` in this directory will produce `bundle.js` 2 | -------------------------------------------------------------------------------- /build_examples/browserify/app.js: -------------------------------------------------------------------------------- 1 | var angular = require('angular'); 2 | 3 | angular.module('app', [ 4 | // this is what you would do in a real app 5 | // require('angular-cache') 6 | 7 | // for the example to work 8 | require('../../dist/angular-cache.js') 9 | ]).run(function ($rootScope, CacheFactory) { 10 | $rootScope.test = 'It works! Using ' + (CacheFactory ? 'angular-cache' : 'undefined'); 11 | }); 12 | -------------------------------------------------------------------------------- /build_examples/browserify/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | My App 5 | 6 | 7 | 8 | 9 |

{{ test }}

10 | 11 | 12 | -------------------------------------------------------------------------------- /build_examples/r.js/README.md: -------------------------------------------------------------------------------- 1 | Running `r.js -o require.config.js` in this directory will produce `bundle.js` 2 | 3 | In `index.html` switch `script/main` between `main` (load scripts dynamically) and `bundle` (load bundled scripts) 4 | -------------------------------------------------------------------------------- /build_examples/r.js/app.js: -------------------------------------------------------------------------------- 1 | define('app', [ 2 | 'angular', 3 | 'angular-cache' 4 | ], function (angular, angularCacheModuleName) { 5 | return angular.module('app', ['angular-cache']) 6 | .run(function ($rootScope) { 7 | $rootScope.test = 'It works! Using ' + angularCacheModuleName; 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /build_examples/r.js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | My App 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

{{ test }}

13 | 14 | 15 | -------------------------------------------------------------------------------- /build_examples/r.js/main.js: -------------------------------------------------------------------------------- 1 | require.config({ 2 | paths: { 3 | angular: '../../bower_components/angular/angular', 4 | 'angular-cache': '../../dist/angular-cache', 5 | }, 6 | shim: { 7 | 'angular': { 8 | exports: 'angular' 9 | } 10 | } 11 | }); 12 | 13 | require([ 14 | 'angular', 15 | 'app' 16 | ], function (angular, app) { 17 | angular.element(document.getElementsByTagName('html')[0]).ready(function () { 18 | // bootstrap the app manually 19 | angular.bootstrap(document, ['app']); 20 | }); 21 | } 22 | ); 23 | -------------------------------------------------------------------------------- /build_examples/r.js/require.config.js: -------------------------------------------------------------------------------- 1 | ({ 2 | name: 'main', 3 | mainConfigFile: 'main.js', 4 | out: 'bundle.js', 5 | optimize: 'none' 6 | }) 7 | -------------------------------------------------------------------------------- /build_examples/webpack/README.md: -------------------------------------------------------------------------------- 1 | Running `webpack` in this directory will produce `bundle.js` 2 | -------------------------------------------------------------------------------- /build_examples/webpack/app.js: -------------------------------------------------------------------------------- 1 | var angular = require('angular'); 2 | var angularCacheModuleName = require('angular-cache'); 3 | 4 | var app = angular.module('app', [ 5 | angularCacheModuleName 6 | ]).run(function ($rootScope) { 7 | $rootScope.test = 'It works, imported ' + angularCacheModuleName; 8 | }); 9 | -------------------------------------------------------------------------------- /build_examples/webpack/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | My App 5 | 6 | 7 | 8 | 9 |

{{ test }}

10 | 11 | 12 | -------------------------------------------------------------------------------- /build_examples/webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: './app.js', 3 | output: { 4 | filename: 'bundle.js' 5 | }, 6 | resolve: { 7 | alias: { 8 | 'angular-cache': '../../dist/angular-cache.js' 9 | } 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /build_examples/webpack_es6/README.md: -------------------------------------------------------------------------------- 1 | Running `webpack` in this directory will produce `bundle.js` 2 | -------------------------------------------------------------------------------- /build_examples/webpack_es6/app.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import angularCacheModuleName from 'angular-cache'; 3 | 4 | let app = angular.module('app', [ 5 | angularCacheModuleName 6 | ]).run($rootScope => { 7 | $rootScope.test = 'It works, imported ' + angularCacheModuleName; 8 | }); 9 | -------------------------------------------------------------------------------- /build_examples/webpack_es6/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | My App 5 | 6 | 7 | 8 | 9 |

{{ test }}

10 | 11 | 12 | -------------------------------------------------------------------------------- /build_examples/webpack_es6/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: './app.js', 3 | output: { 4 | filename: 'bundle.js' 5 | }, 6 | resolve: { 7 | alias: { 8 | 'angular-cache': '../../dist/angular-cache.js' 9 | } 10 | }, 11 | module: { 12 | loaders: [ 13 | { test: /(.+)\.js$/, loader: 'babel-loader?blacklist=useStrict' } 14 | ] 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /build_examples/webpack_es6_2/README.md: -------------------------------------------------------------------------------- 1 | Running `webpack` in this directory will produce `bundle.js` 2 | -------------------------------------------------------------------------------- /build_examples/webpack_es6_2/app.js: -------------------------------------------------------------------------------- 1 | import 'angular'; 2 | import 'angular-cache'; 3 | 4 | let app = angular.module('app', [ 5 | 'angular-cache' 6 | ]).run(($rootScope, CacheFactory) => { 7 | $rootScope.test = 'It works, imported ' + (CacheFactory ? 'angular-cache' : 'undefined'); 8 | }); 9 | -------------------------------------------------------------------------------- /build_examples/webpack_es6_2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | My App 5 | 6 | 7 | 8 | 9 |

{{ test }}

10 | 11 | 12 | -------------------------------------------------------------------------------- /build_examples/webpack_es6_2/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: './app.js', 3 | output: { 4 | filename: 'bundle.js' 5 | }, 6 | resolve: { 7 | alias: { 8 | 'angular-cache': '../../dist/angular-cache.js' 9 | } 10 | }, 11 | module: { 12 | loaders: [ 13 | { test: /(.+)\.js$/, loader: 'babel-loader?blacklist=useStrict' } 14 | ] 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 4.1.0 4 | test: 5 | override: 6 | - npm run ci 7 | -------------------------------------------------------------------------------- /dist/angular-cache.js: -------------------------------------------------------------------------------- 1 | /** 2 | * angular-cache 3 | * @version 4.6.0 - Homepage 4 | * @copyright (c) 2013-2016 angular-cache project authors 5 | * @license MIT 6 | * @overview angular-cache is a very useful replacement for Angular's $cacheFactory. 7 | */ 8 | (function (global, factory) { 9 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(typeof angular === 'undefined' ? require('angular') : angular) : 10 | typeof define === 'function' && define.amd ? define('angular-cache', ['angular'], factory) : 11 | (global.angularCacheModuleName = factory(global.angular)); 12 | }(this, function (angular) { 'use strict'; 13 | 14 | angular = 'default' in angular ? angular['default'] : angular; 15 | 16 | var babelHelpers = {}; 17 | babelHelpers.typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { 18 | return typeof obj; 19 | } : function (obj) { 20 | return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; 21 | }; 22 | 23 | /** 24 | * @method bubbleUp 25 | * @param {array} heap The heap. 26 | * @param {function} weightFunc The weight function. 27 | * @param {number} n The index of the element to bubble up. 28 | */ 29 | var bubbleUp = function bubbleUp(heap, weightFunc, n) { 30 | var element = heap[n]; 31 | var weight = weightFunc(element); 32 | // When at 0, an element can not go up any further. 33 | while (n > 0) { 34 | // Compute the parent element's index, and fetch it. 35 | var parentN = Math.floor((n + 1) / 2) - 1; 36 | var parent = heap[parentN]; 37 | // If the parent has a lesser weight, things are in order and we 38 | // are done. 39 | if (weight >= weightFunc(parent)) { 40 | break; 41 | } else { 42 | heap[parentN] = element; 43 | heap[n] = parent; 44 | n = parentN; 45 | } 46 | } 47 | }; 48 | 49 | /** 50 | * @method bubbleDown 51 | * @param {array} heap The heap. 52 | * @param {function} weightFunc The weight function. 53 | * @param {number} n The index of the element to sink down. 54 | */ 55 | var bubbleDown = function bubbleDown(heap, weightFunc, n) { 56 | var length = heap.length; 57 | var node = heap[n]; 58 | var nodeWeight = weightFunc(node); 59 | 60 | while (true) { 61 | var child2N = (n + 1) * 2; 62 | var child1N = child2N - 1; 63 | var swap = null; 64 | if (child1N < length) { 65 | var child1 = heap[child1N]; 66 | var child1Weight = weightFunc(child1); 67 | // If the score is less than our node's, we need to swap. 68 | if (child1Weight < nodeWeight) { 69 | swap = child1N; 70 | } 71 | } 72 | // Do the same checks for the other child. 73 | if (child2N < length) { 74 | var child2 = heap[child2N]; 75 | var child2Weight = weightFunc(child2); 76 | if (child2Weight < (swap === null ? nodeWeight : weightFunc(heap[child1N]))) { 77 | swap = child2N; 78 | } 79 | } 80 | 81 | if (swap === null) { 82 | break; 83 | } else { 84 | heap[n] = heap[swap]; 85 | heap[swap] = node; 86 | n = swap; 87 | } 88 | } 89 | }; 90 | 91 | function BinaryHeap(weightFunc, compareFunc) { 92 | if (!weightFunc) { 93 | weightFunc = function weightFunc(x) { 94 | return x; 95 | }; 96 | } 97 | if (!compareFunc) { 98 | compareFunc = function compareFunc(x, y) { 99 | return x === y; 100 | }; 101 | } 102 | if (typeof weightFunc !== 'function') { 103 | throw new Error('BinaryHeap([weightFunc][, compareFunc]): "weightFunc" must be a function!'); 104 | } 105 | if (typeof compareFunc !== 'function') { 106 | throw new Error('BinaryHeap([weightFunc][, compareFunc]): "compareFunc" must be a function!'); 107 | } 108 | this.weightFunc = weightFunc; 109 | this.compareFunc = compareFunc; 110 | this.heap = []; 111 | } 112 | 113 | var BHProto = BinaryHeap.prototype; 114 | 115 | BHProto.push = function (node) { 116 | this.heap.push(node); 117 | bubbleUp(this.heap, this.weightFunc, this.heap.length - 1); 118 | }; 119 | 120 | BHProto.peek = function () { 121 | return this.heap[0]; 122 | }; 123 | 124 | BHProto.pop = function () { 125 | var front = this.heap[0]; 126 | var end = this.heap.pop(); 127 | if (this.heap.length > 0) { 128 | this.heap[0] = end; 129 | bubbleDown(this.heap, this.weightFunc, 0); 130 | } 131 | return front; 132 | }; 133 | 134 | BHProto.remove = function (node) { 135 | var length = this.heap.length; 136 | for (var i = 0; i < length; i++) { 137 | if (this.compareFunc(this.heap[i], node)) { 138 | var removed = this.heap[i]; 139 | var end = this.heap.pop(); 140 | if (i !== length - 1) { 141 | this.heap[i] = end; 142 | bubbleUp(this.heap, this.weightFunc, i); 143 | bubbleDown(this.heap, this.weightFunc, i); 144 | } 145 | return removed; 146 | } 147 | } 148 | return null; 149 | }; 150 | 151 | BHProto.removeAll = function () { 152 | this.heap = []; 153 | }; 154 | 155 | BHProto.size = function () { 156 | return this.heap.length; 157 | }; 158 | 159 | var _Promise = null; 160 | try { 161 | _Promise = window.Promise; 162 | } catch (e) {} 163 | 164 | var utils = { 165 | isNumber: function isNumber(value) { 166 | return typeof value === 'number'; 167 | }, 168 | isString: function isString(value) { 169 | return typeof value === 'string'; 170 | }, 171 | isObject: function isObject(value) { 172 | return value !== null && (typeof value === 'undefined' ? 'undefined' : babelHelpers.typeof(value)) === 'object'; 173 | }, 174 | isFunction: function isFunction(value) { 175 | return typeof value === 'function'; 176 | }, 177 | fromJson: function fromJson(value) { 178 | return JSON.parse(value); 179 | }, 180 | equals: function equals(a, b) { 181 | return a === b; 182 | }, 183 | 184 | Promise: _Promise 185 | }; 186 | 187 | function _keys(collection) { 188 | var keys = []; 189 | var key = void 0; 190 | if (!utils.isObject(collection)) { 191 | return keys; 192 | } 193 | for (key in collection) { 194 | if (collection.hasOwnProperty(key)) { 195 | keys.push(key); 196 | } 197 | } 198 | return keys; 199 | } 200 | 201 | function _isPromiseLike(value) { 202 | return value && typeof value.then === 'function'; 203 | } 204 | 205 | function _stringifyNumber(number) { 206 | if (utils.isNumber(number)) { 207 | return number.toString(); 208 | } 209 | return number; 210 | } 211 | 212 | function _keySet(collection) { 213 | var keySet = {}; 214 | var key = void 0; 215 | if (!utils.isObject(collection)) { 216 | return keySet; 217 | } 218 | for (key in collection) { 219 | if (collection.hasOwnProperty(key)) { 220 | keySet[key] = key; 221 | } 222 | } 223 | return keySet; 224 | } 225 | 226 | var defaults = { 227 | capacity: Number.MAX_VALUE, 228 | maxAge: Number.MAX_VALUE, 229 | deleteOnExpire: 'none', 230 | onExpire: null, 231 | cacheFlushInterval: null, 232 | recycleFreq: 1000, 233 | storageMode: 'memory', 234 | storageImpl: null, 235 | disabled: false, 236 | storagePrefix: 'cachefactory.caches.', 237 | storeOnResolve: false, 238 | storeOnReject: false 239 | }; 240 | 241 | var caches = {}; 242 | 243 | function createCache(cacheId, options) { 244 | if (cacheId in caches) { 245 | throw new Error(cacheId + ' already exists!'); 246 | } else if (!utils.isString(cacheId)) { 247 | throw new Error('cacheId must be a string!'); 248 | } 249 | 250 | var $$data = {}; 251 | var $$promises = {}; 252 | var $$storage = null; 253 | var $$expiresHeap = new BinaryHeap(function (x) { 254 | return x.expires; 255 | }, utils.equals); 256 | var $$lruHeap = new BinaryHeap(function (x) { 257 | return x.accessed; 258 | }, utils.equals); 259 | 260 | var cache = caches[cacheId] = { 261 | 262 | $$id: cacheId, 263 | 264 | destroy: function destroy() { 265 | clearInterval(this.$$cacheFlushIntervalId); 266 | clearInterval(this.$$recycleFreqId); 267 | this.removeAll(); 268 | if ($$storage) { 269 | $$storage().removeItem(this.$$prefix + '.keys'); 270 | $$storage().removeItem(this.$$prefix); 271 | } 272 | $$storage = null; 273 | $$data = null; 274 | $$lruHeap = null; 275 | $$expiresHeap = null; 276 | this.$$prefix = null; 277 | delete caches[this.$$id]; 278 | }, 279 | disable: function disable() { 280 | this.$$disabled = true; 281 | }, 282 | enable: function enable() { 283 | delete this.$$disabled; 284 | }, 285 | get: function get(key, options) { 286 | var _this2 = this; 287 | 288 | if (Array.isArray(key)) { 289 | var _ret = function () { 290 | var keys = key; 291 | var values = []; 292 | 293 | keys.forEach(function (key) { 294 | var value = _this2.get(key, options); 295 | if (value !== null && value !== undefined) { 296 | values.push(value); 297 | } 298 | }); 299 | 300 | return { 301 | v: values 302 | }; 303 | }(); 304 | 305 | if ((typeof _ret === 'undefined' ? 'undefined' : babelHelpers.typeof(_ret)) === "object") return _ret.v; 306 | } else { 307 | key = _stringifyNumber(key); 308 | 309 | if (this.$$disabled) { 310 | return; 311 | } 312 | } 313 | 314 | options = options || {}; 315 | if (!utils.isString(key)) { 316 | throw new Error('key must be a string!'); 317 | } else if (options && !utils.isObject(options)) { 318 | throw new Error('options must be an object!'); 319 | } else if (options.onExpire && !utils.isFunction(options.onExpire)) { 320 | throw new Error('options.onExpire must be a function!'); 321 | } 322 | 323 | var item = void 0; 324 | 325 | if ($$storage) { 326 | if ($$promises[key]) { 327 | return $$promises[key]; 328 | } 329 | 330 | var itemJson = $$storage().getItem(this.$$prefix + '.data.' + key); 331 | 332 | if (itemJson) { 333 | item = utils.fromJson(itemJson); 334 | } else { 335 | return; 336 | } 337 | } else if (utils.isObject($$data)) { 338 | if (!(key in $$data)) { 339 | return; 340 | } 341 | 342 | item = $$data[key]; 343 | } 344 | 345 | var value = item.value; 346 | var now = new Date().getTime(); 347 | 348 | if ($$storage) { 349 | $$lruHeap.remove({ 350 | key: key, 351 | accessed: item.accessed 352 | }); 353 | item.accessed = now; 354 | $$lruHeap.push({ 355 | key: key, 356 | accessed: now 357 | }); 358 | } else { 359 | $$lruHeap.remove(item); 360 | item.accessed = now; 361 | $$lruHeap.push(item); 362 | } 363 | 364 | if (this.$$deleteOnExpire === 'passive' && 'expires' in item && item.expires < now) { 365 | this.remove(key); 366 | 367 | if (this.$$onExpire) { 368 | this.$$onExpire(key, item.value, options.onExpire); 369 | } else if (options.onExpire) { 370 | options.onExpire.call(this, key, item.value); 371 | } 372 | value = undefined; 373 | } else if ($$storage) { 374 | $$storage().setItem(this.$$prefix + '.data.' + key, JSON.stringify(item)); 375 | } 376 | 377 | return value; 378 | }, 379 | info: function info(key) { 380 | if (key) { 381 | var item = void 0; 382 | if ($$storage) { 383 | var itemJson = $$storage().getItem(this.$$prefix + '.data.' + key); 384 | 385 | if (itemJson) { 386 | item = utils.fromJson(itemJson); 387 | return { 388 | created: item.created, 389 | accessed: item.accessed, 390 | expires: item.expires, 391 | isExpired: new Date().getTime() - item.created > (item.maxAge || this.$$maxAge) 392 | }; 393 | } else { 394 | return undefined; 395 | } 396 | } else if (utils.isObject($$data) && key in $$data) { 397 | item = $$data[key]; 398 | 399 | return { 400 | created: item.created, 401 | accessed: item.accessed, 402 | expires: item.expires, 403 | isExpired: new Date().getTime() - item.created > (item.maxAge || this.$$maxAge) 404 | }; 405 | } else { 406 | return undefined; 407 | } 408 | } else { 409 | return { 410 | id: this.$$id, 411 | capacity: this.$$capacity, 412 | maxAge: this.$$maxAge, 413 | deleteOnExpire: this.$$deleteOnExpire, 414 | onExpire: this.$$onExpire, 415 | cacheFlushInterval: this.$$cacheFlushInterval, 416 | recycleFreq: this.$$recycleFreq, 417 | storageMode: this.$$storageMode, 418 | storageImpl: $$storage ? $$storage() : undefined, 419 | disabled: !!this.$$disabled, 420 | size: $$lruHeap && $$lruHeap.size() || 0 421 | }; 422 | } 423 | }, 424 | keys: function keys() { 425 | if ($$storage) { 426 | var keysJson = $$storage().getItem(this.$$prefix + '.keys'); 427 | 428 | if (keysJson) { 429 | return utils.fromJson(keysJson); 430 | } else { 431 | return []; 432 | } 433 | } else { 434 | return _keys($$data); 435 | } 436 | }, 437 | keySet: function keySet() { 438 | if ($$storage) { 439 | var keysJson = $$storage().getItem(this.$$prefix + '.keys'); 440 | var kSet = {}; 441 | 442 | if (keysJson) { 443 | var keys = utils.fromJson(keysJson); 444 | 445 | for (var i = 0; i < keys.length; i++) { 446 | kSet[keys[i]] = keys[i]; 447 | } 448 | } 449 | return kSet; 450 | } else { 451 | return _keySet($$data); 452 | } 453 | }, 454 | put: function put(key, value, options) { 455 | var _this3 = this; 456 | 457 | options || (options = {}); 458 | 459 | var storeOnResolve = 'storeOnResolve' in options ? !!options.storeOnResolve : this.$$storeOnResolve; 460 | var storeOnReject = 'storeOnReject' in options ? !!options.storeOnReject : this.$$storeOnReject; 461 | 462 | var getHandler = function getHandler(store, isError) { 463 | return function (v) { 464 | if (store) { 465 | delete $$promises[key]; 466 | if (utils.isObject(v) && 'status' in v && 'data' in v) { 467 | v = [v.status, v.data, v.headers(), v.statusText]; 468 | _this3.put(key, v); 469 | } else { 470 | _this3.put(key, v); 471 | } 472 | } 473 | if (isError) { 474 | if (utils.Promise) { 475 | return utils.Promise.reject(v); 476 | } else { 477 | throw v; 478 | } 479 | } else { 480 | return v; 481 | } 482 | }; 483 | }; 484 | 485 | if (this.$$disabled || !utils.isObject($$data) || value === null || value === undefined) { 486 | return; 487 | } 488 | key = _stringifyNumber(key); 489 | 490 | if (!utils.isString(key)) { 491 | throw new Error('key must be a string!'); 492 | } 493 | 494 | var now = new Date().getTime(); 495 | var item = { 496 | key: key, 497 | value: _isPromiseLike(value) ? value.then(getHandler(storeOnResolve, false), getHandler(storeOnReject, true)) : value, 498 | created: options.created === undefined ? now : options.created, 499 | accessed: options.accessed === undefined ? now : options.accessed 500 | }; 501 | if (options.maxAge) { 502 | item.maxAge = options.maxAge; 503 | } 504 | 505 | if (options.expires === undefined) { 506 | item.expires = item.created + (item.maxAge || this.$$maxAge); 507 | } else { 508 | item.expires = options.expires; 509 | } 510 | 511 | if ($$storage) { 512 | if (_isPromiseLike(item.value)) { 513 | $$promises[key] = item.value; 514 | return $$promises[key]; 515 | } 516 | var keysJson = $$storage().getItem(this.$$prefix + '.keys'); 517 | var keys = keysJson ? utils.fromJson(keysJson) : []; 518 | var itemJson = $$storage().getItem(this.$$prefix + '.data.' + key); 519 | 520 | // Remove existing 521 | if (itemJson) { 522 | this.remove(key); 523 | } 524 | // Add to expires heap 525 | $$expiresHeap.push({ 526 | key: key, 527 | expires: item.expires 528 | }); 529 | // Add to lru heap 530 | $$lruHeap.push({ 531 | key: key, 532 | accessed: item.accessed 533 | }); 534 | // Set item 535 | $$storage().setItem(this.$$prefix + '.data.' + key, JSON.stringify(item)); 536 | var exists = false; 537 | for (var i = 0; i < keys.length; i++) { 538 | if (keys[i] === key) { 539 | exists = true; 540 | break; 541 | } 542 | } 543 | if (!exists) { 544 | keys.push(key); 545 | } 546 | $$storage().setItem(this.$$prefix + '.keys', JSON.stringify(keys)); 547 | } else { 548 | // Remove existing 549 | if ($$data[key]) { 550 | this.remove(key); 551 | } 552 | // Add to expires heap 553 | $$expiresHeap.push(item); 554 | // Add to lru heap 555 | $$lruHeap.push(item); 556 | // Set item 557 | $$data[key] = item; 558 | delete $$promises[key]; 559 | } 560 | 561 | // Handle exceeded capacity 562 | if ($$lruHeap.size() > this.$$capacity) { 563 | this.remove($$lruHeap.peek().key); 564 | } 565 | 566 | return value; 567 | }, 568 | remove: function remove(key) { 569 | key += ''; 570 | delete $$promises[key]; 571 | if ($$storage) { 572 | var itemJson = $$storage().getItem(this.$$prefix + '.data.' + key); 573 | 574 | if (itemJson) { 575 | var item = utils.fromJson(itemJson); 576 | $$lruHeap.remove({ 577 | key: key, 578 | accessed: item.accessed 579 | }); 580 | $$expiresHeap.remove({ 581 | key: key, 582 | expires: item.expires 583 | }); 584 | $$storage().removeItem(this.$$prefix + '.data.' + key); 585 | var keysJson = $$storage().getItem(this.$$prefix + '.keys'); 586 | var keys = keysJson ? utils.fromJson(keysJson) : []; 587 | var index = keys.indexOf(key); 588 | 589 | if (index >= 0) { 590 | keys.splice(index, 1); 591 | } 592 | $$storage().setItem(this.$$prefix + '.keys', JSON.stringify(keys)); 593 | return item.value; 594 | } 595 | } else if (utils.isObject($$data)) { 596 | var value = $$data[key] ? $$data[key].value : undefined; 597 | $$lruHeap.remove($$data[key]); 598 | $$expiresHeap.remove($$data[key]); 599 | $$data[key] = null; 600 | delete $$data[key]; 601 | return value; 602 | } 603 | }, 604 | removeAll: function removeAll() { 605 | if ($$storage) { 606 | $$lruHeap.removeAll(); 607 | $$expiresHeap.removeAll(); 608 | var keysJson = $$storage().getItem(this.$$prefix + '.keys'); 609 | 610 | if (keysJson) { 611 | var keys = utils.fromJson(keysJson); 612 | 613 | for (var i = 0; i < keys.length; i++) { 614 | this.remove(keys[i]); 615 | } 616 | } 617 | $$storage().setItem(this.$$prefix + '.keys', JSON.stringify([])); 618 | } else if (utils.isObject($$data)) { 619 | $$lruHeap.removeAll(); 620 | $$expiresHeap.removeAll(); 621 | for (var key in $$data) { 622 | $$data[key] = null; 623 | } 624 | $$data = {}; 625 | } else { 626 | $$lruHeap.removeAll(); 627 | $$expiresHeap.removeAll(); 628 | $$data = {}; 629 | } 630 | $$promises = {}; 631 | }, 632 | removeExpired: function removeExpired() { 633 | var now = new Date().getTime(); 634 | var expired = {}; 635 | var key = void 0; 636 | var expiredItem = void 0; 637 | 638 | while ((expiredItem = $$expiresHeap.peek()) && expiredItem.expires <= now) { 639 | expired[expiredItem.key] = expiredItem.value ? expiredItem.value : null; 640 | $$expiresHeap.pop(); 641 | } 642 | 643 | if ($$storage) { 644 | for (key in expired) { 645 | var itemJson = $$storage().getItem(this.$$prefix + '.data.' + key); 646 | if (itemJson) { 647 | expired[key] = utils.fromJson(itemJson).value; 648 | this.remove(key); 649 | } 650 | } 651 | } else { 652 | for (key in expired) { 653 | this.remove(key); 654 | } 655 | } 656 | 657 | if (this.$$onExpire) { 658 | for (key in expired) { 659 | this.$$onExpire(key, expired[key]); 660 | } 661 | } 662 | 663 | return expired; 664 | }, 665 | setCacheFlushInterval: function setCacheFlushInterval(cacheFlushInterval) { 666 | var _this = this; 667 | if (cacheFlushInterval === null) { 668 | delete _this.$$cacheFlushInterval; 669 | } else if (!utils.isNumber(cacheFlushInterval)) { 670 | throw new Error('cacheFlushInterval must be a number!'); 671 | } else if (cacheFlushInterval < 0) { 672 | throw new Error('cacheFlushInterval must be greater than zero!'); 673 | } else if (cacheFlushInterval !== _this.$$cacheFlushInterval) { 674 | _this.$$cacheFlushInterval = cacheFlushInterval; 675 | 676 | clearInterval(_this.$$cacheFlushIntervalId); // eslint-disable-line 677 | 678 | _this.$$cacheFlushIntervalId = setInterval(function () { 679 | _this.removeAll(); 680 | }, _this.$$cacheFlushInterval); 681 | } 682 | }, 683 | setCapacity: function setCapacity(capacity) { 684 | if (capacity === null) { 685 | delete this.$$capacity; 686 | } else if (!utils.isNumber(capacity)) { 687 | throw new Error('capacity must be a number!'); 688 | } else if (capacity < 0) { 689 | throw new Error('capacity must be greater than zero!'); 690 | } else { 691 | this.$$capacity = capacity; 692 | } 693 | var removed = {}; 694 | while ($$lruHeap.size() > this.$$capacity) { 695 | removed[$$lruHeap.peek().key] = this.remove($$lruHeap.peek().key); 696 | } 697 | return removed; 698 | }, 699 | setDeleteOnExpire: function setDeleteOnExpire(deleteOnExpire, setRecycleFreq) { 700 | if (deleteOnExpire === null) { 701 | delete this.$$deleteOnExpire; 702 | } else if (!utils.isString(deleteOnExpire)) { 703 | throw new Error('deleteOnExpire must be a string!'); 704 | } else if (deleteOnExpire !== 'none' && deleteOnExpire !== 'passive' && deleteOnExpire !== 'aggressive') { 705 | throw new Error('deleteOnExpire must be "none", "passive" or "aggressive"!'); 706 | } else { 707 | this.$$deleteOnExpire = deleteOnExpire; 708 | } 709 | if (setRecycleFreq !== false) { 710 | this.setRecycleFreq(this.$$recycleFreq); 711 | } 712 | }, 713 | setMaxAge: function setMaxAge(maxAge) { 714 | if (maxAge === null) { 715 | this.$$maxAge = Number.MAX_VALUE; 716 | } else if (!utils.isNumber(maxAge)) { 717 | throw new Error('maxAge must be a number!'); 718 | } else if (maxAge < 0) { 719 | throw new Error('maxAge must be greater than zero!'); 720 | } else { 721 | this.$$maxAge = maxAge; 722 | } 723 | var i = void 0, 724 | keys = void 0, 725 | key = void 0; 726 | 727 | $$expiresHeap.removeAll(); 728 | 729 | if ($$storage) { 730 | var keysJson = $$storage().getItem(this.$$prefix + '.keys'); 731 | 732 | keys = keysJson ? utils.fromJson(keysJson) : []; 733 | 734 | for (i = 0; i < keys.length; i++) { 735 | key = keys[i]; 736 | var itemJson = $$storage().getItem(this.$$prefix + '.data.' + key); 737 | 738 | if (itemJson) { 739 | var item = utils.fromJson(itemJson); 740 | if (this.$$maxAge === Number.MAX_VALUE) { 741 | item.expires = Number.MAX_VALUE; 742 | } else { 743 | item.expires = item.created + (item.maxAge || this.$$maxAge); 744 | } 745 | $$expiresHeap.push({ 746 | key: key, 747 | expires: item.expires 748 | }); 749 | } 750 | } 751 | } else { 752 | keys = _keys($$data); 753 | 754 | for (i = 0; i < keys.length; i++) { 755 | key = keys[i]; 756 | if (this.$$maxAge === Number.MAX_VALUE) { 757 | $$data[key].expires = Number.MAX_VALUE; 758 | } else { 759 | $$data[key].expires = $$data[key].created + ($$data[key].maxAge || this.$$maxAge); 760 | } 761 | $$expiresHeap.push($$data[key]); 762 | } 763 | } 764 | if (this.$$deleteOnExpire === 'aggressive') { 765 | return this.removeExpired(); 766 | } else { 767 | return {}; 768 | } 769 | }, 770 | setOnExpire: function setOnExpire(onExpire) { 771 | if (onExpire === null) { 772 | delete this.$$onExpire; 773 | } else if (!utils.isFunction(onExpire)) { 774 | throw new Error('onExpire must be a function!'); 775 | } else { 776 | this.$$onExpire = onExpire; 777 | } 778 | }, 779 | setOptions: function setOptions(cacheOptions, strict) { 780 | cacheOptions = cacheOptions || {}; 781 | strict = !!strict; 782 | if (!utils.isObject(cacheOptions)) { 783 | throw new Error('cacheOptions must be an object!'); 784 | } 785 | 786 | if ('storagePrefix' in cacheOptions) { 787 | this.$$storagePrefix = cacheOptions.storagePrefix; 788 | } else if (strict) { 789 | this.$$storagePrefix = defaults.storagePrefix; 790 | } 791 | 792 | this.$$prefix = this.$$storagePrefix + this.$$id; 793 | 794 | if ('disabled' in cacheOptions) { 795 | this.$$disabled = !!cacheOptions.disabled; 796 | } else if (strict) { 797 | this.$$disabled = defaults.disabled; 798 | } 799 | 800 | if ('deleteOnExpire' in cacheOptions) { 801 | this.setDeleteOnExpire(cacheOptions.deleteOnExpire, false); 802 | } else if (strict) { 803 | this.setDeleteOnExpire(defaults.deleteOnExpire, false); 804 | } 805 | 806 | if ('recycleFreq' in cacheOptions) { 807 | this.setRecycleFreq(cacheOptions.recycleFreq); 808 | } else if (strict) { 809 | this.setRecycleFreq(defaults.recycleFreq); 810 | } 811 | 812 | if ('maxAge' in cacheOptions) { 813 | this.setMaxAge(cacheOptions.maxAge); 814 | } else if (strict) { 815 | this.setMaxAge(defaults.maxAge); 816 | } 817 | 818 | if ('storeOnResolve' in cacheOptions) { 819 | this.$$storeOnResolve = !!cacheOptions.storeOnResolve; 820 | } else if (strict) { 821 | this.$$storeOnResolve = defaults.storeOnResolve; 822 | } 823 | 824 | if ('storeOnReject' in cacheOptions) { 825 | this.$$storeOnReject = !!cacheOptions.storeOnReject; 826 | } else if (strict) { 827 | this.$$storeOnReject = defaults.storeOnReject; 828 | } 829 | 830 | if ('capacity' in cacheOptions) { 831 | this.setCapacity(cacheOptions.capacity); 832 | } else if (strict) { 833 | this.setCapacity(defaults.capacity); 834 | } 835 | 836 | if ('cacheFlushInterval' in cacheOptions) { 837 | this.setCacheFlushInterval(cacheOptions.cacheFlushInterval); 838 | } else if (strict) { 839 | this.setCacheFlushInterval(defaults.cacheFlushInterval); 840 | } 841 | 842 | if ('onExpire' in cacheOptions) { 843 | this.setOnExpire(cacheOptions.onExpire); 844 | } else if (strict) { 845 | this.setOnExpire(defaults.onExpire); 846 | } 847 | 848 | if ('storageMode' in cacheOptions || 'storageImpl' in cacheOptions) { 849 | this.setStorageMode(cacheOptions.storageMode || defaults.storageMode, cacheOptions.storageImpl || defaults.storageImpl); 850 | } else if (strict) { 851 | this.setStorageMode(defaults.storageMode, defaults.storageImpl); 852 | } 853 | }, 854 | setRecycleFreq: function setRecycleFreq(recycleFreq) { 855 | if (recycleFreq === null) { 856 | delete this.$$recycleFreq; 857 | } else if (!utils.isNumber(recycleFreq)) { 858 | throw new Error('recycleFreq must be a number!'); 859 | } else if (recycleFreq < 0) { 860 | throw new Error('recycleFreq must be greater than zero!'); 861 | } else { 862 | this.$$recycleFreq = recycleFreq; 863 | } 864 | clearInterval(this.$$recycleFreqId); 865 | if (this.$$deleteOnExpire === 'aggressive') { 866 | (function (self) { 867 | self.$$recycleFreqId = setInterval(function () { 868 | self.removeExpired(); 869 | }, self.$$recycleFreq); 870 | })(this); 871 | } else { 872 | delete this.$$recycleFreqId; 873 | } 874 | }, 875 | setStorageMode: function setStorageMode(storageMode, storageImpl) { 876 | if (!utils.isString(storageMode)) { 877 | throw new Error('storageMode must be a string!'); 878 | } else if (storageMode !== 'memory' && storageMode !== 'localStorage' && storageMode !== 'sessionStorage') { 879 | throw new Error('storageMode must be "memory", "localStorage" or "sessionStorage"!'); 880 | } 881 | 882 | var prevStorage = $$storage; 883 | var prevData = $$data; 884 | var shouldReInsert = false; 885 | var items = {}; 886 | 887 | function load(prevStorage, prevData) { 888 | var keys = this.keys(); 889 | var length = keys.length; 890 | if (length) { 891 | var _key = void 0; 892 | var prevDataIsObject = utils.isObject(prevData); 893 | for (var i = 0; i < length; i++) { 894 | _key = keys[i]; 895 | if (prevStorage) { 896 | var itemJson = prevStorage().getItem(this.$$prefix + '.data.' + _key); 897 | if (itemJson) { 898 | items[_key] = utils.fromJson(itemJson); 899 | } 900 | } else if (prevDataIsObject) { 901 | items[_key] = prevData[_key]; 902 | } 903 | this.remove(_key); 904 | } 905 | shouldReInsert = true; 906 | } 907 | } 908 | 909 | if (!this.$$initializing) { 910 | load.call(this, prevStorage, prevData); 911 | } 912 | 913 | this.$$storageMode = storageMode; 914 | 915 | if (storageImpl) { 916 | if (!utils.isObject(storageImpl)) { 917 | throw new Error('storageImpl must be an object!'); 918 | } else if (!('setItem' in storageImpl) || typeof storageImpl.setItem !== 'function') { 919 | throw new Error('storageImpl must implement "setItem(key, value)"!'); 920 | } else if (!('getItem' in storageImpl) || typeof storageImpl.getItem !== 'function') { 921 | throw new Error('storageImpl must implement "getItem(key)"!'); 922 | } else if (!('removeItem' in storageImpl) || typeof storageImpl.removeItem !== 'function') { 923 | throw new Error('storageImpl must implement "removeItem(key)"!'); 924 | } 925 | $$storage = function $$storage() { 926 | return storageImpl; 927 | }; 928 | } else if (this.$$storageMode === 'localStorage') { 929 | try { 930 | localStorage.setItem('cachefactory', 'cachefactory'); 931 | localStorage.removeItem('cachefactory'); 932 | $$storage = function $$storage() { 933 | return localStorage; 934 | }; 935 | } catch (e) { 936 | $$storage = null; 937 | this.$$storageMode = 'memory'; 938 | } 939 | } else if (this.$$storageMode === 'sessionStorage') { 940 | try { 941 | sessionStorage.setItem('cachefactory', 'cachefactory'); 942 | sessionStorage.removeItem('cachefactory'); 943 | $$storage = function $$storage() { 944 | return sessionStorage; 945 | }; 946 | } catch (e) { 947 | $$storage = null; 948 | this.$$storageMode = 'memory'; 949 | } 950 | } else { 951 | $$storage = null; 952 | this.$$storageMode = 'memory'; 953 | } 954 | 955 | if (this.$$initializing) { 956 | load.call(this, $$storage, $$data); 957 | } 958 | 959 | if (shouldReInsert) { 960 | var item = void 0; 961 | for (var key in items) { 962 | item = items[key]; 963 | this.put(key, item.value, { 964 | created: item.created, 965 | accessed: item.accessed, 966 | expires: item.expires 967 | }); 968 | } 969 | } 970 | }, 971 | touch: function touch(key, options) { 972 | var _this4 = this; 973 | 974 | if (key) { 975 | var val = this.get(key, { 976 | onExpire: function onExpire(k, v) { 977 | return _this4.put(k, v); 978 | } 979 | }); 980 | if (val) { 981 | this.put(key, val, options); 982 | } 983 | } else { 984 | var keys = this.keys(); 985 | for (var i = 0; i < keys.length; i++) { 986 | this.touch(keys[i], options); 987 | } 988 | } 989 | }, 990 | values: function values() { 991 | var keys = this.keys(); 992 | var items = []; 993 | for (var i = 0; i < keys.length; i++) { 994 | items.push(this.get(keys[i])); 995 | } 996 | return items; 997 | } 998 | }; 999 | 1000 | cache.$$initializing = true; 1001 | cache.setOptions(options, true); 1002 | cache.$$initializing = false; 1003 | 1004 | return cache; 1005 | } 1006 | 1007 | function CacheFactory(cacheId, options) { 1008 | return createCache(cacheId, options); 1009 | } 1010 | 1011 | CacheFactory.createCache = createCache; 1012 | CacheFactory.defaults = defaults; 1013 | 1014 | CacheFactory.info = function () { 1015 | var keys = _keys(caches); 1016 | var info = { 1017 | size: keys.length, 1018 | caches: {} 1019 | }; 1020 | for (var opt in defaults) { 1021 | if (defaults.hasOwnProperty(opt)) { 1022 | info[opt] = defaults[opt]; 1023 | } 1024 | } 1025 | for (var i = 0; i < keys.length; i++) { 1026 | var key = keys[i]; 1027 | info.caches[key] = caches[key].info(); 1028 | } 1029 | return info; 1030 | }; 1031 | 1032 | CacheFactory.get = function (cacheId) { 1033 | return caches[cacheId]; 1034 | }; 1035 | CacheFactory.keySet = function () { 1036 | return _keySet(caches); 1037 | }; 1038 | CacheFactory.keys = function () { 1039 | return _keys(caches); 1040 | }; 1041 | CacheFactory.destroy = function (cacheId) { 1042 | if (caches[cacheId]) { 1043 | caches[cacheId].destroy(); 1044 | delete caches[cacheId]; 1045 | } 1046 | }; 1047 | CacheFactory.destroyAll = function () { 1048 | for (var cacheId in caches) { 1049 | caches[cacheId].destroy(); 1050 | } 1051 | caches = {}; 1052 | }; 1053 | CacheFactory.clearAll = function () { 1054 | for (var cacheId in caches) { 1055 | caches[cacheId].removeAll(); 1056 | } 1057 | }; 1058 | CacheFactory.removeExpiredFromAll = function () { 1059 | var expired = {}; 1060 | for (var cacheId in caches) { 1061 | expired[cacheId] = caches[cacheId].removeExpired(); 1062 | } 1063 | return expired; 1064 | }; 1065 | CacheFactory.enableAll = function () { 1066 | for (var cacheId in caches) { 1067 | caches[cacheId].$$disabled = false; 1068 | } 1069 | }; 1070 | CacheFactory.disableAll = function () { 1071 | for (var cacheId in caches) { 1072 | caches[cacheId].$$disabled = true; 1073 | } 1074 | }; 1075 | CacheFactory.touchAll = function () { 1076 | for (var cacheId in caches) { 1077 | caches[cacheId].touch(); 1078 | } 1079 | }; 1080 | 1081 | CacheFactory.utils = utils; 1082 | CacheFactory.BinaryHeap = BinaryHeap; 1083 | 1084 | CacheFactory.utils.equals = angular.equals; 1085 | CacheFactory.utils.isObject = angular.isObject; 1086 | CacheFactory.utils.fromJson = angular.fromJson; 1087 | 1088 | function BinaryHeapProvider() { 1089 | this.$get = function () { 1090 | return CacheFactory.BinaryHeap; 1091 | }; 1092 | } 1093 | 1094 | function CacheFactoryProvider() { 1095 | this.defaults = CacheFactory.defaults; 1096 | this.defaults.storagePrefix = 'angular-cache.caches.'; 1097 | 1098 | this.$get = ['$q', function ($q) { 1099 | CacheFactory.utils.Promise = $q; 1100 | return CacheFactory; 1101 | }]; 1102 | } 1103 | 1104 | angular.module('angular-cache', []).provider('BinaryHeap', BinaryHeapProvider).provider('CacheFactory', CacheFactoryProvider); 1105 | 1106 | var index = 'angular-cache'; 1107 | 1108 | return index; 1109 | 1110 | })); 1111 | //# sourceMappingURL=angular-cache.js.map -------------------------------------------------------------------------------- /dist/angular-cache.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t("undefined"==typeof angular?require("angular"):angular):"function"==typeof define&&define.amd?define("angular-cache",["angular"],t):e.angularCacheModuleName=t(e.angular)}(this,function(e){"use strict";function t(e,t){if(e||(e=function(e){return e}),t||(t=function(e,t){return e===t}),"function"!=typeof e)throw new Error('BinaryHeap([weightFunc][, compareFunc]): "weightFunc" must be a function!');if("function"!=typeof t)throw new Error('BinaryHeap([weightFunc][, compareFunc]): "compareFunc" must be a function!');this.weightFunc=e,this.compareFunc=t,this.heap=[]}function r(e){var t=[],r=void 0;if(!d.isObject(e))return t;for(r in e)e.hasOwnProperty(r)&&t.push(r);return t}function i(e){return e&&"function"==typeof e.then}function s(e){return d.isNumber(e)?e.toString():e}function n(e){var t={},r=void 0;if(!d.isObject(e))return t;for(r in e)e.hasOwnProperty(r)&&(t[r]=r);return t}function o(e,o){if(e in g)throw new Error(e+" already exists!");if(!d.isString(e))throw new Error("cacheId must be a string!");var a={},c={},l=null,h=new t(function(e){return e.expires},d.equals),f=new t(function(e){return e.accessed},d.equals),p=g[e]={$$id:e,destroy:function(){clearInterval(this.$$cacheFlushIntervalId),clearInterval(this.$$recycleFreqId),this.removeAll(),l&&(l().removeItem(this.$$prefix+".keys"),l().removeItem(this.$$prefix)),l=null,a=null,f=null,h=null,this.$$prefix=null,delete g[this.$$id]},disable:function(){this.$$disabled=!0},enable:function(){delete this.$$disabled},get:function(e,t){var r=this;if(Array.isArray(e)){var i=function(){var i=e,s=[];return i.forEach(function(e){var i=r.get(e,t);null!==i&&void 0!==i&&s.push(i)}),{v:s}}();if("object"===("undefined"==typeof i?"undefined":u["typeof"](i)))return i.v}else if(e=s(e),this.$$disabled)return;if(t=t||{},!d.isString(e))throw new Error("key must be a string!");if(t&&!d.isObject(t))throw new Error("options must be an object!");if(t.onExpire&&!d.isFunction(t.onExpire))throw new Error("options.onExpire must be a function!");var n=void 0;if(l){if(c[e])return c[e];var o=l().getItem(this.$$prefix+".data."+e);if(!o)return;n=d.fromJson(o)}else if(d.isObject(a)){if(!(e in a))return;n=a[e]}var h=n.value,p=(new Date).getTime();return l?(f.remove({key:e,accessed:n.accessed}),n.accessed=p,f.push({key:e,accessed:p})):(f.remove(n),n.accessed=p,f.push(n)),"passive"===this.$$deleteOnExpire&&"expires"in n&&n.expires(t.maxAge||this.$$maxAge)}):void 0}return d.isObject(a)&&e in a?(t=a[e],{created:t.created,accessed:t.accessed,expires:t.expires,isExpired:(new Date).getTime()-t.created>(t.maxAge||this.$$maxAge)}):void 0}return{id:this.$$id,capacity:this.$$capacity,maxAge:this.$$maxAge,deleteOnExpire:this.$$deleteOnExpire,onExpire:this.$$onExpire,cacheFlushInterval:this.$$cacheFlushInterval,recycleFreq:this.$$recycleFreq,storageMode:this.$$storageMode,storageImpl:l?l():void 0,disabled:!!this.$$disabled,size:f&&f.size()||0}},keys:function(){if(l){var e=l().getItem(this.$$prefix+".keys");return e?d.fromJson(e):[]}return r(a)},keySet:function(){if(l){var e=l().getItem(this.$$prefix+".keys"),t={};if(e)for(var r=d.fromJson(e),i=0;ithis.$$capacity&&this.remove(f.peek().key),t}},remove:function(e){if(e+="",delete c[e],l){var t=l().getItem(this.$$prefix+".data."+e);if(t){var r=d.fromJson(t);f.remove({key:e,accessed:r.accessed}),h.remove({key:e,expires:r.expires}),l().removeItem(this.$$prefix+".data."+e);var i=l().getItem(this.$$prefix+".keys"),s=i?d.fromJson(i):[],n=s.indexOf(e);return n>=0&&s.splice(n,1),l().setItem(this.$$prefix+".keys",JSON.stringify(s)),r.value}}else if(d.isObject(a)){var o=a[e]?a[e].value:void 0;return f.remove(a[e]),h.remove(a[e]),a[e]=null,delete a[e],o}},removeAll:function(){if(l){f.removeAll(),h.removeAll();var e=l().getItem(this.$$prefix+".keys");if(e)for(var t=d.fromJson(e),r=0;re)throw new Error("cacheFlushInterval must be greater than zero!");e!==t.$$cacheFlushInterval&&(t.$$cacheFlushInterval=e,clearInterval(t.$$cacheFlushIntervalId),t.$$cacheFlushIntervalId=setInterval(function(){t.removeAll()},t.$$cacheFlushInterval))}},setCapacity:function(e){if(null===e)delete this.$$capacity;else{if(!d.isNumber(e))throw new Error("capacity must be a number!");if(0>e)throw new Error("capacity must be greater than zero!");this.$$capacity=e}for(var t={};f.size()>this.$$capacity;)t[f.peek().key]=this.remove(f.peek().key);return t},setDeleteOnExpire:function(e,t){if(null===e)delete this.$$deleteOnExpire;else{if(!d.isString(e))throw new Error("deleteOnExpire must be a string!");if("none"!==e&&"passive"!==e&&"aggressive"!==e)throw new Error('deleteOnExpire must be "none", "passive" or "aggressive"!');this.$$deleteOnExpire=e}t!==!1&&this.setRecycleFreq(this.$$recycleFreq)},setMaxAge:function(e){if(null===e)this.$$maxAge=Number.MAX_VALUE;else{if(!d.isNumber(e))throw new Error("maxAge must be a number!");if(0>e)throw new Error("maxAge must be greater than zero!");this.$$maxAge=e}var t=void 0,i=void 0,s=void 0;if(h.removeAll(),l){var n=l().getItem(this.$$prefix+".keys");for(i=n?d.fromJson(n):[],t=0;te)throw new Error("recycleFreq must be greater than zero!");this.$$recycleFreq=e}clearInterval(this.$$recycleFreqId),"aggressive"===this.$$deleteOnExpire?!function(e){e.$$recycleFreqId=setInterval(function(){e.removeExpired()},e.$$recycleFreq)}(this):delete this.$$recycleFreqId},setStorageMode:function(e,t){function r(e,t){var r=this.keys(),i=r.length;if(i){for(var s=void 0,a=d.isObject(t),c=0;i>c;c++){if(s=r[c],e){var l=e().getItem(this.$$prefix+".data."+s);l&&(o[s]=d.fromJson(l))}else a&&(o[s]=t[s]);this.remove(s)}n=!0}}if(!d.isString(e))throw new Error("storageMode must be a string!");if("memory"!==e&&"localStorage"!==e&&"sessionStorage"!==e)throw new Error('storageMode must be "memory", "localStorage" or "sessionStorage"!');var i=l,s=a,n=!1,o={};if(this.$$initializing||r.call(this,i,s),this.$$storageMode=e,t){if(!d.isObject(t))throw new Error("storageImpl must be an object!");if(!("setItem"in t&&"function"==typeof t.setItem))throw new Error('storageImpl must implement "setItem(key, value)"!');if(!("getItem"in t&&"function"==typeof t.getItem))throw new Error('storageImpl must implement "getItem(key)"!');if(!("removeItem"in t)||"function"!=typeof t.removeItem)throw new Error('storageImpl must implement "removeItem(key)"!');l=function(){return t}}else if("localStorage"===this.$$storageMode)try{localStorage.setItem("cachefactory","cachefactory"),localStorage.removeItem("cachefactory"),l=function(){return localStorage}}catch(c){l=null,this.$$storageMode="memory"}else if("sessionStorage"===this.$$storageMode)try{sessionStorage.setItem("cachefactory","cachefactory"),sessionStorage.removeItem("cachefactory"),l=function(){return sessionStorage}}catch(c){l=null,this.$$storageMode="memory"}else l=null,this.$$storageMode="memory";if(this.$$initializing&&r.call(this,l,a),n){var u=void 0;for(var h in o)u=o[h],this.put(h,u.value,{created:u.created,accessed:u.accessed,expires:u.expires})}},touch:function(e,t){var r=this;if(e){var i=this.get(e,{onExpire:function(e,t){return r.put(e,t)}});i&&this.put(e,i,t)}else for(var s=this.keys(),n=0;n0;){var n=Math.floor((r+1)/2)-1,o=e[n];if(s>=t(o))break;e[n]=i,e[r]=o,r=n}},f=function(e,t,r){for(var i=e.length,s=e[r],n=t(s);;){var o=2*(r+1),a=o-1,c=null;if(i>a){var l=e[a],u=t(l);n>u&&(c=a)}if(i>o){var h=e[o],f=t(h);f<(null===c?n:t(e[a]))&&(c=o)}if(null===c)break;e[r]=e[c],e[c]=s,r=c}},p=t.prototype;p.push=function(e){this.heap.push(e),h(this.heap,this.weightFunc,this.heap.length-1)},p.peek=function(){return this.heap[0]},p.pop=function(){var e=this.heap[0],t=this.heap.pop();return this.heap.length>0&&(this.heap[0]=t,f(this.heap,this.weightFunc,0)),e},p.remove=function(e){for(var t=this.heap.length,r=0;t>r;r++)if(this.compareFunc(this.heap[r],e)){var i=this.heap[r],s=this.heap.pop();return r!==t-1&&(this.heap[r]=s,h(this.heap,this.weightFunc,r),f(this.heap,this.weightFunc,r)),i}return null},p.removeAll=function(){this.heap=[]},p.size=function(){return this.heap.length};var m=null;try{m=window.Promise}catch($){}var d={isNumber:function(e){return"number"==typeof e},isString:function(e){return"string"==typeof e},isObject:function(e){return null!==e&&"object"===("undefined"==typeof e?"undefined":u["typeof"](e))},isFunction:function(e){return"function"==typeof e},fromJson:function(e){return JSON.parse(e)},equals:function(e,t){return e===t},Promise:m},v={capacity:Number.MAX_VALUE,maxAge:Number.MAX_VALUE,deleteOnExpire:"none",onExpire:null,cacheFlushInterval:null,recycleFreq:1e3,storageMode:"memory",storageImpl:null,disabled:!1,storagePrefix:"cachefactory.caches.",storeOnResolve:!1,storeOnReject:!1},g={};a.createCache=o,a.defaults=v,a.info=function(){var e=r(g),t={size:e.length,caches:{}};for(var i in v)v.hasOwnProperty(i)&&(t[i]=v[i]);for(var s=0;s=1.x <2" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | var babel = require('rollup-plugin-babel') 2 | var pkg = require('./package.json') 3 | 4 | module.exports = { 5 | moduleName: 'angularCacheModuleName', 6 | moduleId: 'angular-cache', 7 | banner: '/**\n' + 8 | ' * angular-cache\n' + 9 | ' * @version ' + pkg.version + ' - Homepage \n' + 10 | ' * @copyright (c) 2013-2016 angular-cache project authors\n' + 11 | ' * @license MIT \n' + 12 | ' * @overview angular-cache is a very useful replacement for Angular\'s $cacheFactory.\n' + 13 | ' */', 14 | plugins: [ 15 | babel({ 16 | babelrc: false, 17 | presets: [ 18 | 'es2015-rollup' 19 | ] 20 | }) 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /scripts/postbuild.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = __dirname + '/../dist/angular-cache.js' 3 | var file = fs.readFileSync(path, { encoding: 'utf8' }) 4 | file = file.replace( 5 | 'module.exports = factory(require(\'angular\'))', 6 | 'module.exports = factory(typeof angular === \'undefined\' ? require(\'angular\') : angular)' 7 | ) 8 | var index = file.indexOf('babelHelpers;') 9 | var str = 'var babelHelpers = {};\n' + 10 | ' babelHelpers.typeof = typeof Symbol === "function" && babelHelpers.typeof(Symbol.iterator) === "symbol" ? function (obj) {\n' + 11 | ' return typeof obj === "undefined" ? "undefined" : babelHelpers.typeof(obj);\n' + 12 | ' } : function (obj) {\n' + 13 | ' return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj === "undefined" ? "undefined" : babelHelpers.typeof(obj);\n' + 14 | ' };' 15 | 16 | var index2 = file.indexOf(str) 17 | 18 | var file2 = file.substring(index2 + str.length) 19 | file = file.substring(0, index) + file2 20 | 21 | fs.writeFileSync(path, file) 22 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular' 2 | import CacheFactory from '../node_modules/cachefactory/dist/cachefactory.es2015' 3 | 4 | CacheFactory.utils.equals = angular.equals 5 | CacheFactory.utils.isObject = angular.isObject 6 | CacheFactory.utils.fromJson = angular.fromJson 7 | 8 | function BinaryHeapProvider () { 9 | this.$get = function () { return CacheFactory.BinaryHeap } 10 | } 11 | 12 | function CacheFactoryProvider () { 13 | this.defaults = CacheFactory.defaults 14 | this.defaults.storagePrefix = 'angular-cache.caches.' 15 | 16 | this.$get = ['$q', function ($q) { 17 | CacheFactory.utils.Promise = $q 18 | return CacheFactory 19 | }] 20 | } 21 | 22 | angular.module('angular-cache', []) 23 | .provider('BinaryHeap', BinaryHeapProvider) 24 | .provider('CacheFactory', CacheFactoryProvider) 25 | 26 | export default 'angular-cache' 27 | -------------------------------------------------------------------------------- /test/unit/Cache/index.destroy.test.js: -------------------------------------------------------------------------------- 1 | describe('Cache#destroy()', function () { 2 | it('should destroy the cache and remove all traces of its existence.', function () { 3 | var cache = TestCacheFactory('cache'); 4 | cache.destroy(); 5 | try { 6 | assert.equal(cache.info(), { size: 0 }); 7 | fail('should not be able to use a cache after destroying it'); 8 | } catch (err) { 9 | 10 | } 11 | assert.isUndefined(TestCacheFactory.get('cache')); 12 | }); 13 | it('should remove items from localStorage when storageMode is used.', function () { 14 | var localStorageCache = TestCacheFactory('localStorageCache', { storageMode: 'localStorage', storagePrefix: 'acc.' }); 15 | var sessionStorageCache = TestCacheFactory('sessionStorageCache', { storageMode: 'sessionStorage' }); 16 | 17 | localStorageCache.put('item1', 'value1'); 18 | sessionStorageCache.put('item1', 'value1'); 19 | localStorageCache.put('item2', 'value2'); 20 | sessionStorageCache.put('item2', 'value2'); 21 | 22 | assert.equal(angular.fromJson(localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.data.item1')).value, 'value1'); 23 | assert.equal(angular.fromJson(localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.data.item2')).value, 'value2'); 24 | assert.equal(localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.keys'), '["item1","item2"]'); 25 | assert.equal(angular.fromJson(sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.data.item1')).value, 'value1'); 26 | assert.equal(angular.fromJson(sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.data.item2')).value, 'value2'); 27 | assert.equal(sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.keys'), '["item1","item2"]'); 28 | 29 | localStorageCache.destroy(); 30 | sessionStorageCache.destroy(); 31 | 32 | assert.isNull(angular.fromJson(localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.data.item1'))); 33 | assert.isNull(angular.fromJson(localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.data.item2'))); 34 | assert.isNull(localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.keys')); 35 | assert.isNull(angular.fromJson(sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.data.item1'))); 36 | assert.isNull(angular.fromJson(sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.data.item2'))); 37 | assert.isNull(sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.keys')); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/unit/Cache/index.get.test.js: -------------------------------------------------------------------------------- 1 | describe('Cache#get(key[, options])', function () { 2 | it('should do nothing if the cache is disabled.', function () { 3 | var cache = TestCacheFactory.createCache('cache'); 4 | 5 | assert.equal(cache.info().size, 0); 6 | cache.put('1', 'item'); 7 | assert.equal(cache.get('1'), 'item'); 8 | assert.equal(cache.info().size, 1); 9 | cache.setOptions({ disabled: true }); 10 | assert.equal(cache.info().size, 1); 11 | assert.isUndefined(cache.get('1')); 12 | }); 13 | it('should throw an error if "key" is not a string or array.', function () { 14 | var cache = TestCacheFactory.createCache('cache'); 15 | for (var i = 0; i < TYPES_EXCEPT_STRING_OR_ARRAY_OR_NUMBER.length; i++) { 16 | try { 17 | cache.get(TYPES_EXCEPT_STRING_OR_ARRAY_OR_NUMBER[i]); 18 | fail(TYPES_EXCEPT_STRING_OR_ARRAY_OR_NUMBER[i]); 19 | } catch (err) { 20 | assert.equal(err.message, 'key must be a string!'); 21 | continue; 22 | } 23 | fail(TYPES_EXCEPT_STRING_OR_ARRAY_OR_NUMBER[i]); 24 | } 25 | }); 26 | it('should throw an error if "options" is not an object.', function () { 27 | var cache = TestCacheFactory.createCache('cache'); 28 | for (var i = 0; i < TYPES_EXCEPT_OBJECT.length; i++) { 29 | try { 30 | cache.get('item', TYPES_EXCEPT_OBJECT[i]); 31 | if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { 32 | fail(TYPES_EXCEPT_OBJECT[i]); 33 | } 34 | } catch (err) { 35 | assert.equal(err.message, 'options must be an object!'); 36 | continue; 37 | } 38 | if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { 39 | fail(TYPES_EXCEPT_OBJECT[i]); 40 | } 41 | } 42 | }); 43 | it('should throw an error if "onExpire" is not a function.', function () { 44 | var cache = TestCacheFactory.createCache('cache'); 45 | for (var i = 0; i < TYPES_EXCEPT_FUNCTION.length; i++) { 46 | try { 47 | if (!TYPES_EXCEPT_FUNCTION[i]) { 48 | continue; 49 | } 50 | cache.get('item', { onExpire: TYPES_EXCEPT_FUNCTION[i] }); 51 | if (TYPES_EXCEPT_FUNCTION[i] !== null && TYPES_EXCEPT_FUNCTION[i] !== undefined && TYPES_EXCEPT_FUNCTION[i] !== false) { 52 | fail(TYPES_EXCEPT_FUNCTION[i]); 53 | } 54 | } catch (err) { 55 | assert.equal(err.message, 'options.onExpire must be a function!'); 56 | continue; 57 | } 58 | if (TYPES_EXCEPT_FUNCTION[i] !== null && TYPES_EXCEPT_FUNCTION[i] !== undefined && TYPES_EXCEPT_FUNCTION[i] !== false) { 59 | fail(TYPES_EXCEPT_FUNCTION[i]); 60 | } 61 | } 62 | }); 63 | it('should return the correct value for the specified key.', function () { 64 | var cache = TestCacheFactory.createCache('cache'); 65 | var value1 = 'value1', 66 | value2 = 2, 67 | value3 = { 68 | value3: 'stuff' 69 | }; 70 | cache.put('item1', value1); 71 | cache.put('item2', value2); 72 | cache.put('item3', value3); 73 | assert.equal(cache.get('item1'), value1); 74 | assert.equal(cache.get('item2'), value2); 75 | assert.equal(cache.get('item3'), value3); 76 | }); 77 | it('should return undefined if the key isn\'t in the cache.', function () { 78 | var cache = TestCacheFactory.createCache('cache'); 79 | assert.isUndefined(cache.get('item')); 80 | }); 81 | it('should execute globally configured "onExpire" callback if the item is expired in passive mode and global "onExpire" callback is configured.', function (done) { 82 | var cache = TestCacheFactory.createCache('cache', { 83 | maxAge: 10, 84 | recycleFreq: 20, 85 | deleteOnExpire: 'passive', 86 | onExpire: function (key, value, done2) { 87 | done2(key, value, 'executed global callback'); 88 | } 89 | }); 90 | cache.put('item', 'value'); 91 | setTimeout(function () { 92 | cache.get('item', { 93 | onExpire: function (key, value, test) { 94 | assert.equal(key, 'item'); 95 | assert.equal(value, 'value'); 96 | assert.equal(test, 'executed global callback'); 97 | } 98 | }); 99 | done(); 100 | }, 100); 101 | }); 102 | it('should execute globally configured "onExpire" callback when an item is aggressively deleted and global "onExpire" callback is configured.', function (done) { 103 | var options = { 104 | maxAge: 10, 105 | recycleFreq: 20, 106 | deleteOnExpire: 'aggressive', 107 | onExpire: function (key, value) { 108 | } 109 | }; 110 | sinon.spy(options, 'onExpire'); 111 | var cache = TestCacheFactory.createCache('cache', options); 112 | cache.put('item', 'value'); 113 | setTimeout(function () { 114 | assert.isTrue(options.onExpire.called); 115 | assert.isTrue(options.onExpire.calledWith('item', 'value')); 116 | done(); 117 | }, 100); 118 | }); 119 | it('should execute local "onExpire" callback if the item is expired in passive mode and global "onExpire" callback is NOT configured.', function (done) { 120 | var cache = TestCacheFactory.createCache('cache', { 121 | maxAge: 10, 122 | deleteOnExpire: 'passive', 123 | recycleFreq: 20 124 | }); 125 | cache.put('item', 'value'); 126 | setTimeout(function () { 127 | cache.get('item', { 128 | onExpire: function (key, value) { 129 | assert.equal(key, 'item'); 130 | assert.equal(value, 'value'); 131 | } 132 | }); 133 | done(); 134 | }, 100); 135 | }); 136 | it('should return the correct values for multiple keys.', function () { 137 | var cache = TestCacheFactory.createCache('cache'); 138 | var value1 = 'value1', 139 | value2 = 2, 140 | value3 = { 141 | value3: 'stuff' 142 | }; 143 | cache.put('item1', value1); 144 | cache.put('item2', value2); 145 | cache.put('item3', value3); 146 | assert.deepEqual(cache.get(['item1', 'item2', 'item3']), [value1, value2, value3]); 147 | }); 148 | it('should not return undefined values for multiple keys.', function () { 149 | var cache = TestCacheFactory.createCache('cache'); 150 | var value1 = 'value1', 151 | value2 = 2; 152 | cache.put('item1', value1); 153 | cache.put('item2', value2); 154 | assert.deepEqual(cache.get(['item1', 'item2', 'item3']), [value1, value2]); 155 | }); 156 | }); 157 | -------------------------------------------------------------------------------- /test/unit/Cache/index.info.test.js: -------------------------------------------------------------------------------- 1 | describe('Cache#info()', function () { 2 | it('should return the correct values.', function () { 3 | var onExpire = function () { 4 | }; 5 | var cache = TestCacheFactory('cache'), 6 | cache2 = TestCacheFactory('cache2', { maxAge: 1000 }), 7 | cache3 = TestCacheFactory('cache3', { cacheFlushInterval: 1000 }), 8 | cache4 = TestCacheFactory('cache4', { capacity: 1000 }), 9 | cache5 = TestCacheFactory('cache5', { storageMode: 'localStorage' }), 10 | cache6 = TestCacheFactory('cache6', { storageMode: 'sessionStorage' }), 11 | cache7 = TestCacheFactory('cache7', { maxAge: 100, onExpire: onExpire }); 12 | var cacheInfo = cache.info(); 13 | assert.equal(cacheInfo.id, 'cache'); 14 | assert.equal(cacheInfo.capacity, Number.MAX_VALUE); 15 | assert.equal(cacheInfo.size, 0); 16 | assert.equal(cacheInfo.recycleFreq, 1000); 17 | assert.equal(cacheInfo.maxAge, Number.MAX_VALUE); 18 | assert.equal(cacheInfo.cacheFlushInterval, null); 19 | assert.equal(cacheInfo.deleteOnExpire, 'none'); 20 | assert.equal(cacheInfo.storageMode, 'memory'); 21 | assert.equal(cacheInfo.onExpire, null); 22 | cache.put('item', 'value'); 23 | cache.put('item2', 'value2'); 24 | 25 | // DSCache#info(key) 26 | assert.isUndefined(cache.info('non-existent item')); 27 | assert.isNumber(cache.info('item').created); 28 | assert.isNumber(cache.info('item').expires); 29 | assert.isFalse(cache.info('item').isExpired); 30 | assert.isNumber(cache.info('item').accessed); 31 | assert.isNumber(cache.info('item2').created); 32 | assert.isNumber(cache.info('item2').expires); 33 | assert.isFalse(cache.info('item2').isExpired); 34 | assert.isNumber(cache.info('item2').accessed); 35 | 36 | assert.equal(cache.info().size, 2); 37 | 38 | var cacheInfo2 = cache2.info(); 39 | assert.equal(cacheInfo2.id, 'cache2'); 40 | assert.equal(cacheInfo2.capacity, Number.MAX_VALUE); 41 | assert.equal(cacheInfo2.size, 0); 42 | assert.equal(cacheInfo2.recycleFreq, 1000); 43 | assert.equal(cacheInfo2.maxAge, 1000); 44 | assert.equal(cacheInfo2.cacheFlushInterval, null); 45 | assert.equal(cacheInfo2.deleteOnExpire, 'none'); 46 | assert.equal(cacheInfo2.storageMode, 'memory'); 47 | assert.equal(cacheInfo2.onExpire, null); 48 | 49 | assert.equal(cache3.info().id, 'cache3'); 50 | assert.equal(cache3.info().capacity, Number.MAX_VALUE); 51 | assert.equal(cache3.info().cacheFlushInterval, 1000); 52 | assert.equal(cache3.info().size, 0); 53 | 54 | var cacheInfo4 = cache4.info(); 55 | assert.equal(cacheInfo4.id, 'cache4'); 56 | assert.equal(cacheInfo4.capacity, 1000); 57 | assert.equal(cacheInfo4.size, 0); 58 | assert.equal(cacheInfo4.recycleFreq, 1000); 59 | assert.equal(cacheInfo4.maxAge, Number.MAX_VALUE); 60 | assert.equal(cacheInfo4.cacheFlushInterval, null); 61 | assert.equal(cacheInfo4.deleteOnExpire, 'none'); 62 | assert.equal(cacheInfo4.storageMode, 'memory'); 63 | assert.equal(cacheInfo4.onExpire, null); 64 | if (localStorage) { 65 | assert.equal(cache5.info().storageMode, 'localStorage', 'cache5 storageMode should be "memory"'); 66 | } else { 67 | assert.equal(cache5.info().storageMode, null); 68 | } 69 | if (sessionStorage) { 70 | assert.equal(cache6.info().storageMode, 'sessionStorage'); 71 | } else { 72 | assert.equal(cache6.info().storageMode, null); 73 | } 74 | assert.equal(cache7.info().onExpire, onExpire); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /test/unit/Cache/index.keySet.test.js: -------------------------------------------------------------------------------- 1 | describe('Cache#keySet()', function () { 2 | it('should return the set of keys of all items in the cache.', function () { 3 | var itemKeys = ['item1', 'item2', 'item3']; 4 | 5 | var cache = TestCacheFactory('DSCache.keySet.cache'); 6 | 7 | cache.put(itemKeys[0], itemKeys[0]); 8 | cache.put(itemKeys[1], itemKeys[1]); 9 | cache.put(itemKeys[2], itemKeys[2]); 10 | 11 | var keySet = cache.keySet(); 12 | 13 | assert.equal(keySet.hasOwnProperty(itemKeys[0]), true); 14 | assert.equal(keySet.hasOwnProperty(itemKeys[1]), true); 15 | assert.equal(keySet.hasOwnProperty(itemKeys[2]), true); 16 | 17 | assert.equal(keySet[itemKeys[0]], itemKeys[0]); 18 | assert.equal(keySet[itemKeys[1]], itemKeys[1]); 19 | assert.equal(keySet[itemKeys[2]], itemKeys[2]); 20 | 21 | cache.remove(itemKeys[0]); 22 | cache.remove(itemKeys[1]); 23 | cache.remove(itemKeys[2]); 24 | 25 | keySet = cache.keySet(); 26 | 27 | assert.equal(keySet.hasOwnProperty(itemKeys[0]), false); 28 | assert.equal(keySet.hasOwnProperty(itemKeys[1]), false); 29 | assert.equal(keySet.hasOwnProperty(itemKeys[2]), false); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/unit/Cache/index.keys.test.js: -------------------------------------------------------------------------------- 1 | describe('Cache#keys()', function () { 2 | it('should return the array of keys of all items in the cache.', function () { 3 | var itemKeys = ['item1', 'item2', 'item3']; 4 | 5 | var cache = TestCacheFactory('DSCache.keys.cache'); 6 | 7 | cache.put(itemKeys[0], itemKeys[0]); 8 | assert.deepEqual(cache.keys(), [itemKeys[0]]); 9 | 10 | cache.put(itemKeys[0], itemKeys[2]); 11 | assert.deepEqual(cache.keys(), [itemKeys[0]]); 12 | assert.deepEqual(cache.get(itemKeys[0]), itemKeys[2]); 13 | 14 | cache.put(itemKeys[1], itemKeys[1]); 15 | assert.deepEqual(cache.keys(), [itemKeys[0], itemKeys[1]]); 16 | 17 | cache.put(itemKeys[2], itemKeys[2]); 18 | assert.deepEqual(cache.keys(), [itemKeys[0], itemKeys[1], itemKeys[2]]); 19 | 20 | var keys = cache.keys(); 21 | 22 | assert.equal(keys[0], itemKeys[0]); 23 | assert.equal(keys[1], itemKeys[1]); 24 | assert.equal(keys[2], itemKeys[2]); 25 | 26 | cache.remove(itemKeys[0]); 27 | cache.remove(itemKeys[1]); 28 | cache.remove(itemKeys[2]); 29 | 30 | keys = cache.keys(); 31 | 32 | assert.equal(keys.length, 0); 33 | assert.deepEqual(keys, []); 34 | }); 35 | it('should return the array of keys of all items in the cache when using localStorage.', function () { 36 | var itemKeys = ['item1', 'item2', 'item3']; 37 | 38 | var cache = TestCacheFactory('DSCache.keys.cache', { 39 | storageMode: 'localStorage' 40 | }); 41 | 42 | cache.put(itemKeys[0], itemKeys[0]); 43 | assert.deepEqual(cache.keys(), [itemKeys[0]]); 44 | 45 | cache.put(itemKeys[0], itemKeys[2]); 46 | assert.deepEqual(cache.keys(), [itemKeys[0]]); 47 | assert.deepEqual(cache.get(itemKeys[0]), itemKeys[2]); 48 | 49 | cache.put(itemKeys[1], itemKeys[1]); 50 | assert.deepEqual(cache.keys(), [itemKeys[0], itemKeys[1]]); 51 | 52 | cache.put(itemKeys[2], itemKeys[2]); 53 | assert.deepEqual(cache.keys(), [itemKeys[0], itemKeys[1], itemKeys[2]]); 54 | 55 | var keys = cache.keys(); 56 | 57 | assert.equal(keys[0], itemKeys[0]); 58 | assert.equal(keys[1], itemKeys[1]); 59 | assert.equal(keys[2], itemKeys[2]); 60 | 61 | cache.remove(itemKeys[0]); 62 | cache.remove(itemKeys[1]); 63 | cache.remove(itemKeys[2]); 64 | 65 | keys = cache.keys(); 66 | 67 | assert.equal(keys.length, 0); 68 | assert.deepEqual(keys, []); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/unit/Cache/index.put.test.js: -------------------------------------------------------------------------------- 1 | describe('Cache#put(key, value[, options])', function () { 2 | it('should do nothing if the cache is disabled.', function () { 3 | var cache = TestCacheFactory('DSCache.put.cache', { disabled: true }); 4 | 5 | assert.equal(cache.info().size, 0); 6 | assert.isUndefined(cache.put('1', 'item')); 7 | assert.equal(cache.info().size, 0); 8 | }); 9 | it('should throw an error if "key" is not a string.', function () { 10 | var cache = TestCacheFactory('DSCache.put.cache'); 11 | for (var i = 0; i < TYPES_EXCEPT_STRING_OR_NUMBER.length; i++) { 12 | try { 13 | cache.put(TYPES_EXCEPT_STRING_OR_NUMBER[i], 'value'); 14 | fail(TYPES_EXCEPT_STRING_OR_NUMBER[i]); 15 | } catch (err) { 16 | assert.equal(err.message, 'key must be a string!'); 17 | continue; 18 | } 19 | fail(TYPES_EXCEPT_STRING_OR_NUMBER[i]); 20 | } 21 | }); 22 | it('should not add values that are not defined.', function () { 23 | var cache = TestCacheFactory('cache'); 24 | cache.put('item', null); 25 | assert.equal(cache.get('item'), undefined); 26 | cache.put('item', undefined); 27 | assert.equal(cache.get('item'), undefined); 28 | }); 29 | it('should increase the size of the cache by one.', function () { 30 | var cache = TestCacheFactory('cache'); 31 | assert.equal(cache.info().size, 0); 32 | cache.put('item', 'value1'); 33 | assert.equal(cache.info().size, 1); 34 | cache.put('item2', 'value2'); 35 | assert.equal(cache.info().size, 2); 36 | }); 37 | it('should overwrite an item if it is re-added to the cache.', function () { 38 | var cache = TestCacheFactory('cache'); 39 | assert.equal(cache.info().size, 0); 40 | cache.put('item', 'value1'); 41 | assert.equal(cache.info().size, 1); 42 | cache.put('item', 'value2'); 43 | assert.equal(cache.info().size, 1); 44 | assert.equal(cache.get('item'), 'value2'); 45 | }); 46 | it('should remove the least recently used item if the capacity has been reached.', function () { 47 | var cache = TestCacheFactory('cache', { capacity: 2 }); 48 | assert.equal(cache.info().size, 0); 49 | cache.put('item1', 'value1'); 50 | assert.equal(cache.info().size, 1); 51 | cache.put('item2', 'value2'); 52 | assert.equal(cache.info().size, 2); 53 | cache.put('item3', 'value3'); 54 | assert.equal(cache.info().size, 2); 55 | assert.equal(cache.get('item1'), undefined); 56 | assert.equal(cache.get('item2'), 'value2'); 57 | assert.equal(cache.get('item3'), 'value3'); 58 | cache.get('item2'); 59 | cache.put('item1', 'value1'); 60 | assert.equal(cache.get('item3'), undefined); 61 | assert.equal(cache.get('item1'), 'value1'); 62 | assert.equal(cache.get('item2'), 'value2'); 63 | }); 64 | it('should not delete items if maxAge is specified and deleteOnExpire is set to "none".', function (done) { 65 | var cache = TestCacheFactory('cache', { maxAge: 10, deleteOnExpire: 'none', recycleFreq: 20 }); 66 | cache.put('item1', 'value1'); 67 | assert.equal(cache.get('item1'), 'value1'); 68 | setTimeout(function () { 69 | assert.equal(cache.get('item1'), 'value1'); 70 | assert.equal(cache.info('item1').isExpired, true); 71 | done(); 72 | }, 100); 73 | }); 74 | it('should remove items if maxAge is specified and deleteOnExpire is set to "aggressive".', function (done) { 75 | var cache = TestCacheFactory('cache', { maxAge: 10, deleteOnExpire: 'aggressive', recycleFreq: 20 }); 76 | cache.put('item1', 'value1'); 77 | assert.equal(cache.get('item1'), 'value1'); 78 | setTimeout(function () { 79 | assert.isUndefined(cache.info('item1')); 80 | assert.isUndefined(cache.get('item1')); 81 | 82 | done(); 83 | }, 100); 84 | }); 85 | it('should should lazy delete an item when maxAge is specified and deleteOnExpire is set to "passive".', function (done) { 86 | var cache = TestCacheFactory('cache', { maxAge: 10, deleteOnExpire: 'passive' }); 87 | cache.put('item1', 'value1'); 88 | assert.equal(cache.get('item1'), 'value1'); 89 | setTimeout(function () { 90 | assert.isTrue(cache.info('item1').isExpired); 91 | assert.isUndefined(cache.get('item1')); 92 | 93 | done(); 94 | }, 100); 95 | }); 96 | it('should touch an item.', function (done) { 97 | var cache = TestCacheFactory('cache', { maxAge: 10, deleteOnExpire: 'passive' }); 98 | cache.put('item1', 'value1'); 99 | assert.equal(cache.get('item1'), 'value1'); 100 | setTimeout(function () { 101 | assert.isTrue(cache.info('item1').isExpired); 102 | cache.touch('item1'); 103 | assert.equal(cache.get('item1'), 'value1'); 104 | 105 | done(); 106 | }, 100); 107 | }); 108 | it('should handle normal promises.', function (done) { 109 | var cache = TestCacheFactory('cache', { 110 | maxAge: 10, 111 | deleteOnExpire: 'passive', 112 | recycleFreq: 20, 113 | storeOnResolve: true, 114 | storeOnReject: true 115 | }); 116 | var deferred = $q.defer(); 117 | var item = cache.put('item1', deferred.promise); 118 | assert.equal(typeof item.then, 'function'); 119 | assert.equal(typeof cache.get('item1').then, 'function'); 120 | setTimeout(function () { 121 | try { 122 | $rootScope.$apply(function () { 123 | deferred.resolve('value1'); 124 | }); 125 | assert.equal(cache.get('item1'), 'value1'); 126 | setTimeout(function () { 127 | assert.isUndefined(cache.get('item1')); 128 | done(); 129 | }, 100); 130 | } catch (err) { 131 | done(err); 132 | } 133 | }, 100); 134 | }); 135 | it('should handle normal promises using localStorage.', function (done) { 136 | var cache = TestCacheFactory('cache', { 137 | maxAge: 10, 138 | deleteOnExpire: 'passive', 139 | recycleFreq: 20, 140 | storageMode: 'localStorage', 141 | storeOnResolve: true, 142 | storeOnReject: true 143 | }); 144 | var deferred = $q.defer(); 145 | var item = cache.put('item1', deferred.promise); 146 | assert.equal(typeof item.then, 'function'); 147 | assert.equal(typeof cache.get('item1').then, 'function'); 148 | setTimeout(function () { 149 | try { 150 | $rootScope.$apply(function () { 151 | deferred.resolve('value1'); 152 | }); 153 | assert.equal(cache.get('item1'), 'value1'); 154 | setTimeout(function () { 155 | assert.isUndefined(cache.get('item1')); 156 | done(); 157 | }, 100); 158 | } catch (err) { 159 | done(err); 160 | } 161 | }, 100); 162 | }); 163 | it('should work with $http promises.', function (done) { 164 | $httpBackend.expectGET('test.com').respond({ name: 'John' }); 165 | var cache = TestCacheFactory('cache', { 166 | storeOnResolve: true, 167 | storeOnReject: true 168 | }); 169 | $http.get('test.com', { 170 | cache: cache 171 | }).success(function (data) { 172 | assert.deepEqual(data, { name: 'John' }); 173 | $http.get('test.com', { 174 | cache: cache 175 | }).success(function (data) { 176 | assert.deepEqual(data, { name: 'John' }); 177 | }); 178 | $rootScope.$safeApply(); 179 | assert.equal(cache.get('test.com')[0], 200); 180 | assert.deepEqual(cache.get('test.com')[1], { name: 'John' }); 181 | done(); 182 | }); 183 | $httpBackend.flush(); 184 | }); 185 | it('should work with $http promises when storeOnResolve is false.', function () { 186 | $httpBackend.expectGET('test.com').respond({ name: 'John' }); 187 | var cache = TestCacheFactory('cache', { storeOnReject: true }); 188 | $http.get('test.com', { 189 | cache: cache 190 | }).success(function (data) { 191 | assert.deepEqual(data, { name: 'John' }); 192 | $rootScope.$safeApply(); 193 | assert.equal(cache.get('test.com')[0], 200); 194 | assert.deepEqual(cache.get('test.com')[1], { name: 'John' }); 195 | }); 196 | $httpBackend.flush(); 197 | }); 198 | it('should work with promises when storeOnResolve is true.', function (done) { 199 | var deferred = $q.defer(); 200 | var cache = TestCacheFactory('cache', { 201 | storeOnResolve: true 202 | }); 203 | cache.put('test', deferred.promise); 204 | deferred.resolve('value'); 205 | $rootScope.$safeApply(); 206 | setTimeout(function () { 207 | assert.equal(cache.get('test'), 'value'); 208 | done(); 209 | }, 30); 210 | }); 211 | it('should work with rejected $http promises when storeOnReject and storeOnResolve are false.', function (done) { 212 | $httpBackend.expectGET('test.com').respond(404, 'Not Found'); 213 | var cache = TestCacheFactory('cache', {}); 214 | $http.get('test.com', { 215 | cache: cache 216 | }).success(function () { 217 | done('Should not have succeeded'); 218 | }).error(function (data) { 219 | assert.deepEqual(data, 'Not Found'); 220 | // should not have cached the 404 221 | $httpBackend.expectGET('test.com').respond(200, { test: 'data' }); 222 | $http.get('test.com', { 223 | cache: cache 224 | }).success(function (data) { 225 | assert.deepEqual(data, { test: 'data' }); 226 | done(); 227 | }).error(function (data) { 228 | console.log(data); 229 | done('Should not have failed'); 230 | }); 231 | //$httpBackend.flush(); 232 | }); 233 | $httpBackend.flush(); 234 | }); 235 | it('should work with rejected $http promises when storeOnReject and storeOnResolve are false and using localStorage.', function (done) { 236 | $httpBackend.expectGET('test.com').respond(404, 'Not Found'); 237 | var cache = TestCacheFactory('cache', { 238 | storageMode: 'localStorage' 239 | }); 240 | $http.get('test.com', { 241 | cache: cache 242 | }).success(function () { 243 | done('Should not have succeeded'); 244 | }).error(function (data) { 245 | assert.deepEqual(data, 'Not Found'); 246 | // should not have cached the 404 247 | $httpBackend.expectGET('test.com').respond(200, { test: 'data' }); 248 | $http.get('test.com', { 249 | cache: cache 250 | }).success(function (data) { 251 | assert.deepEqual(data, { test: 'data' }); 252 | done(); 253 | }).error(function (data) { 254 | console.log(data); 255 | done('Should not have failed'); 256 | }); 257 | //$httpBackend.flush(); 258 | }); 259 | $httpBackend.flush(); 260 | }); 261 | it('should work with rejected promises when storeOnReject is false.', function (done) { 262 | var deferred = $q.defer(); 263 | var cache = TestCacheFactory('cache', { storeOnResolve: true }); 264 | cache.put('test', deferred.promise); 265 | deferred.reject('error'); 266 | $rootScope.$safeApply(); 267 | setTimeout(function () { 268 | assert.equal(typeof cache.get('test').then, 'function'); 269 | done(); 270 | }, 30); 271 | }); 272 | it('should work with rejected promises.', function (done) { 273 | var deferred = $q.defer(); 274 | var cache = TestCacheFactory('cache', { 275 | storeOnResolve: true, 276 | storeOnReject: true 277 | }); 278 | cache.put('test', deferred.promise); 279 | deferred.reject('error'); 280 | $rootScope.$safeApply(); 281 | setTimeout(function () { 282 | assert.equal(cache.get('test'), 'error'); 283 | done(); 284 | }, 30); 285 | }); 286 | it('should work with $http promises using localStorage.', function (done) { 287 | $httpBackend.expectGET('test.com').respond({ name: 'John' }); 288 | var cache = TestCacheFactory('cache', { 289 | storeOnResolve: true, 290 | storeOnReject: true, 291 | storageMode: 'localStorage' 292 | }); 293 | $http.get('test.com', { 294 | cache: cache 295 | }).success(function (data) { 296 | assert.deepEqual(data, { name: 'John' }); 297 | $http.get('test.com', { 298 | cache: cache 299 | }).success(function (data) { 300 | assert.deepEqual(data, { name: 'John' }); 301 | done(); 302 | }).error(done); 303 | $rootScope.$safeApply(); 304 | }).error(done); 305 | $httpBackend.flush(); 306 | }); 307 | it('should work with $http promises with multiple requests.', function (done) { 308 | $httpBackend.expectGET('test.com').respond({ name: 'John' }); 309 | var cache = TestCacheFactory('cache', { 310 | storeOnResolve: true, 311 | storeOnReject: true 312 | }); 313 | $http.get('test.com', { 314 | cache: cache 315 | }); 316 | $rootScope.$safeApply(); 317 | assert.deepEqual(cache.keys(), ['test.com']); 318 | setTimeout(function () { 319 | try { 320 | $rootScope.$safeApply(); 321 | var promise = cache.get('test.com'); 322 | assert.equal(typeof promise.then, 'function'); 323 | $http.get('test.com', { 324 | cache: cache 325 | }); 326 | $rootScope.$safeApply(); 327 | assert.deepEqual(cache.keys(), ['test.com']); 328 | assert.isTrue(promise === cache.get('test.com')); 329 | setTimeout(function () { 330 | try { 331 | $http.get('test.com', { 332 | cache: cache 333 | }); 334 | $rootScope.$safeApply(); 335 | assert.deepEqual(cache.keys(), ['test.com']); 336 | assert.isTrue(promise === cache.get('test.com')); 337 | $httpBackend.flush(); 338 | assert.deepEqual(cache.keys(), ['test.com']); 339 | done(); 340 | } catch (err) { 341 | done(err); 342 | } 343 | }, 20); 344 | } catch (err) { 345 | done(err); 346 | } 347 | }, 20); 348 | }); 349 | it('should work with $http promises with multiple requests using localStorage.', function (done) { 350 | $httpBackend.expectGET('test.com').respond({ name: 'John' }); 351 | var cache = TestCacheFactory('cache', { 352 | storageMode: 'localStorage', 353 | storeOnResolve: true, 354 | storeOnReject: true 355 | }); 356 | $http.get('test.com', { 357 | cache: cache 358 | }); 359 | assert.deepEqual(cache.keys(), []); 360 | setTimeout(function () { 361 | $http.get('test.com', { 362 | cache: cache 363 | }); 364 | assert.deepEqual(cache.keys(), []); 365 | setTimeout(function () { 366 | $http.get('test.com', { 367 | cache: cache 368 | }); 369 | assert.deepEqual(cache.keys(), []); 370 | $httpBackend.flush(); 371 | assert.deepEqual(cache.keys(), ['test.com']); 372 | done(); 373 | }, 20); 374 | }, 20); 375 | }); 376 | it('should save data to localStorage when storageMode is used.', function () { 377 | var localStorageCache = TestCacheFactory('localStorageCache', { storageMode: 'localStorage' }); 378 | var sessionStorageCache = TestCacheFactory('sessionStorageCache', { storageMode: 'sessionStorage' }); 379 | 380 | localStorageCache.put('item1', 'value1'); 381 | sessionStorageCache.put('item1', 'value1'); 382 | 383 | assert.equal(angular.fromJson(localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.data.item1')).value, 'value1'); 384 | assert.equal(localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.keys'), '["item1"]'); 385 | assert.equal(angular.fromJson(sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.data.item1')).value, 'value1'); 386 | assert.equal(sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.keys'), '["item1"]'); 387 | }); 388 | }); 389 | -------------------------------------------------------------------------------- /test/unit/Cache/index.remove.test.js: -------------------------------------------------------------------------------- 1 | describe('Cache#remove(key)', function () { 2 | it('should remove the item with the specified key.', function () { 3 | var cache = TestCacheFactory('cache'); 4 | var value1 = 'value1', 5 | value2 = 2, 6 | value3 = { 7 | value3: 'stuff' 8 | }; 9 | cache.put('item1', value1); 10 | cache.put('item2', value2); 11 | cache.put('item3', value3); 12 | cache.remove('item1'); 13 | assert.isUndefined(cache.get('item1')); 14 | cache.remove('item2'); 15 | assert.isUndefined(cache.get('item2')); 16 | cache.remove('item3'); 17 | assert.isUndefined(cache.get('item3')); 18 | }); 19 | it('should reduce the size of the cache by one if the size is greater than zero.', function () { 20 | var cache = TestCacheFactory('cache'); 21 | cache.put('item1', 'value1'); 22 | assert.equal(cache.info().size, 1); 23 | cache.put('item2', 'value2'); 24 | assert.equal(cache.info().size, 2); 25 | cache.remove('item1'); 26 | assert.equal(cache.info().size, 1); 27 | cache.remove('item2'); 28 | assert.equal(cache.info().size, 0); 29 | cache.remove('item1'); 30 | assert.equal(cache.info().size, 0); 31 | cache.remove('item2'); 32 | assert.equal(cache.info().size, 0); 33 | }); 34 | it('should remove items from localStorage when storageMode is used.', function () { 35 | var localStorageCache = TestCacheFactory('localStorageCache', { storageMode: 'localStorage' }), 36 | sessionStorageCache = TestCacheFactory('sessionStorageCache', { storageMode: 'sessionStorage' }); 37 | 38 | localStorageCache.put('item1', 'value1'); 39 | 40 | localStorageCache.put('2', "value2"); 41 | assert.equal(localStorageCache.keys().length, 2); 42 | localStorageCache.remove(2); 43 | assert.equal(localStorageCache.keys().length, 1); 44 | 45 | sessionStorageCache.put('item1', 'value1'); 46 | 47 | assert.equal(angular.fromJson(localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.data.item1')).value, 'value1'); 48 | assert.equal(localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.keys'), '["item1"]'); 49 | assert.equal(angular.fromJson(sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.data.item1')).value, 'value1'); 50 | assert.equal(sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.keys'), '["item1"]'); 51 | 52 | localStorageCache.remove('item1'); 53 | sessionStorageCache.remove('item1'); 54 | 55 | assert.isNull(angular.fromJson(localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.data.item1'))); 56 | assert.equal(localStorage.getItem(localStorageCache.$$storagePrefix + 'localStorageCache.keys'), '[]'); 57 | assert.isNull(angular.fromJson(sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.data.item1'))); 58 | assert.equal(sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'sessionStorageCache.keys'), '[]'); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/unit/Cache/index.removeAll.test.js: -------------------------------------------------------------------------------- 1 | describe('Cache#removeAll()', function () { 2 | it('should remove all items in the cache.', function () { 3 | var cache = TestCacheFactory('DSCache.removeAll.cache'); 4 | var value1 = 'value1', 5 | value2 = 2, 6 | value3 = { 7 | value3: 'stuff' 8 | }; 9 | cache.put('item1', value1); 10 | cache.put('item2', value2); 11 | cache.put('item3', value3); 12 | cache.removeAll(); 13 | assert.isUndefined(cache.get('item1')); 14 | assert.isUndefined(cache.get('item2')); 15 | assert.isUndefined(cache.get('item3')); 16 | }); 17 | it('should remove items from localStorage when storageMode is used.', function () { 18 | var localStorageCache = TestCacheFactory('DSCache.removeAll.localStorageCache', { storageMode: 'localStorage', storageImpl: localStorage }), 19 | sessionStorageCache = TestCacheFactory('DSCache.removeAll.sessionStorageCache', { storageMode: 'sessionStorage', storageImpl: sessionStorage, storagePrefix: 'affas' }); 20 | 21 | localStorageCache.put('item1', 'value1'); 22 | sessionStorageCache.put('item1', 'value1'); 23 | localStorageCache.put('item2', 'value2'); 24 | sessionStorageCache.put('item2', 'value2'); 25 | 26 | assert.equal(angular.fromJson(localStorage.getItem(localStorageCache.$$storagePrefix + 'DSCache.removeAll.localStorageCache.data.item1')).value, 'value1'); 27 | assert.equal(angular.fromJson(localStorage.getItem(localStorageCache.$$storagePrefix + 'DSCache.removeAll.localStorageCache.data.item2')).value, 'value2'); 28 | assert.equal(localStorage.getItem(localStorageCache.$$storagePrefix + 'DSCache.removeAll.localStorageCache.keys'), '["item1","item2"]'); 29 | assert.equal(angular.fromJson(sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'DSCache.removeAll.sessionStorageCache.data.item1')).value, 'value1'); 30 | assert.equal(angular.fromJson(sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'DSCache.removeAll.sessionStorageCache.data.item2')).value, 'value2'); 31 | assert.equal(sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'DSCache.removeAll.sessionStorageCache.keys'), '["item1","item2"]'); 32 | 33 | localStorageCache.removeAll(); 34 | sessionStorageCache.removeAll(); 35 | 36 | assert.isNull(angular.fromJson(localStorage.getItem(localStorageCache.$$storagePrefix + 'DSCache.removeAll.localStorageCache.data.item1'))); 37 | assert.isNull(angular.fromJson(localStorage.getItem(localStorageCache.$$storagePrefix + 'DSCache.removeAll.localStorageCache.data.item2'))); 38 | assert.equal(localStorage.getItem(localStorageCache.$$storagePrefix + 'DSCache.removeAll.localStorageCache.keys'), '[]'); 39 | assert.isNull(angular.fromJson(sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'DSCache.removeAll.sessionStorageCache.data.item1'))); 40 | assert.isNull(angular.fromJson(sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'DSCache.removeAll.sessionStorageCache.data.item2'))); 41 | assert.equal(sessionStorage.getItem(sessionStorageCache.$$storagePrefix + 'DSCache.removeAll.sessionStorageCache.keys'), '[]'); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/unit/Cache/index.removeExpired.test.js: -------------------------------------------------------------------------------- 1 | describe('Cache#removeExpired()', function () { 2 | it('should remove all expired items when deleteOnExpire is "none".', function (done) { 3 | var cache = TestCacheFactory('cache', { 4 | deleteOnExpire: 'none', 5 | maxAge: 10, 6 | recycleFreq: 20 7 | }); 8 | var value1 = 'value1', 9 | value2 = 2, 10 | value3 = { 11 | value3: 'stuff' 12 | }; 13 | cache.put('item1', value1); 14 | cache.put('item2', value2); 15 | cache.put('item3', value3); 16 | setTimeout(function () { 17 | var expired = cache.removeExpired(); 18 | assert.deepEqual(expired, { 19 | item1: value1, 20 | item2: value2, 21 | item3: value3 22 | }); 23 | assert.equal(cache.info().size, 0); 24 | cache.put('item3', value3); 25 | assert.equal(cache.info().size, 1); 26 | done(); 27 | }, 100); 28 | }); 29 | // it('should remove all expired items when deleteOnExpire is "passive".', function (done) { 30 | // var cache = TestCacheFactory('cache', { 31 | // deleteOnExpire: 'passive', 32 | // maxAge: 10, 33 | // recycleFreq: 20 34 | // }); 35 | // var value1 = 'value1', 36 | // value2 = 2, 37 | // value3 = { 38 | // value3: 'stuff' 39 | // }; 40 | // cache.put('item1', value1); 41 | // cache.put('item2', value2); 42 | // cache.put('item3', value3); 43 | // setTimeout(function () { 44 | // var expired = cache.removeExpired(); 45 | // assert.deepEqual(expired, { 46 | // item1: value1, 47 | // item2: value2, 48 | // item3: value3 49 | // }); 50 | // assert.equal(cache.info().size, 0); 51 | // cache.put('item3', value3); 52 | // assert.equal(cache.info().size, 1); 53 | // done(); 54 | // }, 100); 55 | // }); 56 | // it('should remove all expired items when deleteOnExpire is "aggressive".', function (done) { 57 | // var cache = TestCacheFactory('cache', { 58 | // deleteOnExpire: 'aggressive', 59 | // maxAge: 10, 60 | // recycleFreq: 20 61 | // }); 62 | // var value1 = 'value1', 63 | // value2 = 2, 64 | // value3 = { 65 | // value3: 'stuff' 66 | // }; 67 | // cache.put('item1', value1); 68 | // cache.put('item2', value2); 69 | // cache.put('item3', value3); 70 | // setTimeout(function () { 71 | // var expired = cache.removeExpired(); 72 | // assert.deepEqual(expired, {}); 73 | // assert.equal(cache.info().size, 0); 74 | // cache.put('item3', value3); 75 | // assert.equal(cache.info().size, 1); 76 | // done(); 77 | // }, 100); 78 | // }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/unit/Cache/index.setOptions.test.js: -------------------------------------------------------------------------------- 1 | describe('Cache#setOptions([options][, strict])', function () { 2 | it('should throw an error if "options" is not an object.', function () { 3 | var cache = TestCacheFactory('cache'); 4 | for (var i = 0; i < TYPES_EXCEPT_OBJECT.length; i++) { 5 | try { 6 | cache.setOptions(TYPES_EXCEPT_OBJECT[i]); 7 | if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { 8 | fail(TYPES_EXCEPT_OBJECT[i]); 9 | } 10 | } catch (err) { 11 | assert.equal(err.message, 'cacheOptions must be an object!'); 12 | continue; 13 | } 14 | if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { 15 | fail(TYPES_EXCEPT_OBJECT[i]); 16 | } 17 | } 18 | }); 19 | it('should correctly reset to defaults if strict mode is true', function () { 20 | var onExpire = function () { 21 | }; 22 | var cache = TestCacheFactory('cache', { 23 | maxAge: 100, 24 | cacheFlushInterval: 200, 25 | onExpire: onExpire, 26 | storageMode: 'localStorage', 27 | disabled: true 28 | }); 29 | assert.equal(cache.info().maxAge, 100); 30 | assert.equal(cache.info().cacheFlushInterval, 200); 31 | assert.equal(cache.info().onExpire, onExpire); 32 | assert.equal(cache.info().storageMode, 'localStorage'); 33 | assert.isTrue(cache.info().disabled); 34 | cache.setOptions({ }, true); 35 | assert.equal(cache.info().maxAge, Number.MAX_VALUE); 36 | assert.isUndefined(cache.info().cacheFlushInterval); 37 | assert.isUndefined(cache.info().onExpire); 38 | assert.equal(cache.info().storageMode, 'memory'); 39 | assert.isFalse(cache.info().disabled); 40 | }); 41 | it('should correctly modify the capacity of a cache', function (done) { 42 | var cache = TestCacheFactory('cache'); 43 | assert.equal(cache.info().capacity, Number.MAX_VALUE); 44 | cache.setOptions({ capacity: 5 }, false); 45 | assert.equal(cache.info().capacity, 5); 46 | cache.put('item1', 1); 47 | cache.put('item2', 2); 48 | cache.put('item3', 3); 49 | cache.put('item4', 4); 50 | cache.put('item5', 5); 51 | cache.put('item6', 6); 52 | assert.isUndefined(cache.get('item1')); 53 | setTimeout(function () { 54 | cache.get('item2'); 55 | cache.get('item3'); 56 | cache.get('item6'); 57 | cache.setOptions({ capacity: 3 }, false); 58 | // Least-recently used items over the new capacity should have been removed. 59 | assert.isUndefined(cache.get('item4')); 60 | assert.isUndefined(cache.get('item5')); 61 | assert.equal(cache.info().size, 3); 62 | 63 | done(); 64 | }, 50); 65 | }); 66 | it('should correctly modify the maxAge of a cache', function (done) { 67 | var cache = TestCacheFactory('cache'); 68 | assert.equal(cache.info().maxAge, Number.MAX_VALUE); 69 | cache.setOptions({ maxAge: 1000, deleteOnExpire: 'aggressive', recycleFreq: 20 }, false); 70 | assert.equal(cache.info().maxAge, 1000); 71 | cache.put('item1', 1); 72 | cache.put('item2', 2); 73 | setTimeout(function () { 74 | assert.isDefined(cache.get('item1')); 75 | assert.isDefined(cache.get('item2')); 76 | cache.setOptions({ maxAge: 10, deleteOnExpire: 'aggressive', recycleFreq: 20 }, false); 77 | assert.equal(cache.info().maxAge, 10); 78 | cache.put('item1', 1); 79 | cache.put('item2', 2); 80 | // The new items should be removed after 500 ms (the new maxAge) 81 | setTimeout(function () { 82 | assert.isUndefined(cache.get('item1')); 83 | assert.isUndefined(cache.get('item2')); 84 | cache.put('item1', 1); 85 | cache.put('item2', 2); 86 | cache.setOptions({ maxAge: null, deleteOnExpire: 'none', recycleFreq: 20 }, false); 87 | assert.equal(cache.info().maxAge, Number.MAX_VALUE); 88 | // The new items should be removed after 500 ms (the new maxAge) 89 | setTimeout(function () { 90 | assert.equal(cache.get('item1'), 1); 91 | assert.isNumber(cache.info('item1').expires); 92 | assert.equal(cache.get('item2'), 2); 93 | assert.isNumber(cache.info('item2').expires); 94 | 95 | cache.setOptions({ maxAge: 1000, deleteOnExpire: 'aggressive', recycleFreq: 20 }, false); 96 | cache.put('item1', 1); 97 | cache.put('item2', 2); 98 | // The new items should be removed after 500 ms (the new maxAge) 99 | setTimeout(function () { 100 | cache.setOptions({ maxAge: 10, deleteOnExpire: 'aggressive' }, false); 101 | assert.isUndefined(cache.get('item1')); 102 | assert.isUndefined(cache.get('item2')); 103 | 104 | done(); 105 | }, 100); 106 | }, 100); 107 | }, 100); 108 | }, 100); 109 | }); 110 | it('should correctly modify the cacheFlushInterval of a cache', function (done) { 111 | var cache = TestCacheFactory('cache'); 112 | assert.isUndefined(cache.info().cacheFlushInterval); 113 | cache.setOptions({ cacheFlushInterval: 10 }, false); 114 | assert.equal(cache.info().cacheFlushInterval, 10); 115 | cache.put('item1', 1); 116 | cache.put('item2', 2); 117 | // The first items should be removed after 2000 ms 118 | setTimeout(function () { 119 | assert.isUndefined(cache.get('item1')); 120 | assert.isUndefined(cache.get('item2')); 121 | cache.setOptions({ cacheFlushInterval: 20 }, false); 122 | assert.equal(cache.info().cacheFlushInterval, 20); 123 | cache.put('item1', 1); 124 | cache.put('item2', 2); 125 | // The new items should be removed after 500 ms (the new maxAge) 126 | setTimeout(function () { 127 | assert.isUndefined(cache.get('item1')); 128 | assert.isUndefined(cache.get('item2')); 129 | cache.setOptions({ cacheFlushInterval: 20 }); 130 | assert.equal(cache.info().cacheFlushInterval, 20); 131 | cache.put('item1', 1); 132 | cache.put('item2', 2); 133 | // The new items should be removed after 500 ms (the new maxAge) 134 | setTimeout(function () { 135 | assert.isUndefined(cache.get('item1')); 136 | assert.isUndefined(cache.get('item2')); 137 | 138 | done(); 139 | }, 100); 140 | }, 100); 141 | }, 100); 142 | }); 143 | it('should correctly modify the deleteOnExpire of a cache', function (done) { 144 | var cache = TestCacheFactory('cache', { maxAge: 10 }); 145 | assert.equal(cache.info().deleteOnExpire, 'none'); 146 | cache.setOptions({ deleteOnExpire: 'passive' }); 147 | assert.equal(cache.info().deleteOnExpire, 'passive'); 148 | cache.put('item1', 1); 149 | cache.put('item2', 2); 150 | // The first items should be removed after 2000 ms 151 | setTimeout(function () { 152 | assert.isUndefined(cache.get('item1')); 153 | assert.isUndefined(cache.get('item2')); 154 | cache.setOptions({ maxAge: 10, deleteOnExpire: 'aggressive', recycleFreq: 20 }, false); 155 | assert.equal(cache.info().deleteOnExpire, 'aggressive'); 156 | cache.put('item1', 1); 157 | cache.put('item2', 2); 158 | // The new items should be removed after 500 ms (the new maxAge) 159 | setTimeout(function () { 160 | assert.isUndefined(cache.get('item1')); 161 | assert.isUndefined(cache.get('item2')); 162 | 163 | done(); 164 | }, 100); 165 | }, 100); 166 | }); 167 | it('should correctly set configuration to default when "strict" is true', function () { 168 | var cache = TestCacheFactory('cache', { 169 | capacity: 10, 170 | maxAge: 1000, 171 | cacheFlushInterval: 1000, 172 | deleteOnExpire: 'aggressive', 173 | recycleFreq: 20000, 174 | storageMode: 'localStorage' 175 | }); 176 | cache.setOptions({}, true); 177 | var cacheInfo = cache.info(); 178 | assert.equal(cacheInfo.id, 'cache'); 179 | assert.equal(cacheInfo.capacity, Number.MAX_VALUE); 180 | assert.equal(cacheInfo.size, 0); 181 | assert.equal(cacheInfo.recycleFreq, 1000); 182 | assert.equal(cacheInfo.maxAge, Number.MAX_VALUE); 183 | assert.equal(cacheInfo.cacheFlushInterval, null); 184 | assert.equal(cacheInfo.deleteOnExpire, 'none'); 185 | assert.equal(cacheInfo.storageMode, 'memory'); 186 | assert.equal(cacheInfo.onExpire, null); 187 | }); 188 | }); 189 | -------------------------------------------------------------------------------- /test/unit/DSCacheFactory/DSCacheFactoryProvider.setCacheDefaults.test.js: -------------------------------------------------------------------------------- 1 | describe('CacheFactoryProvider.setCacheDefaults(options)', function () { 2 | it('should have the correct defaults.', function () { 3 | var cache = TestCacheFactory('CacheFactoryProvider.setCacheDefaults.cache'); 4 | assert.isDefined(cache); 5 | assert.equal(cache.info().id, 'CacheFactoryProvider.setCacheDefaults.cache'); 6 | assert.equal(cache.info().capacity, CACHE_DEFAULTS.capacity); 7 | assert.equal(cache.info().maxAge, CACHE_DEFAULTS.maxAge); 8 | assert.equal(cache.info().cacheFlushInterval, CACHE_DEFAULTS.cacheFlushInterval); 9 | assert.equal(cache.info().deleteOnExpire, CACHE_DEFAULTS.deleteOnExpire); 10 | assert.equal(cache.info().onExpire, CACHE_DEFAULTS.onExpire); 11 | assert.equal(cache.info().recycleFreq, CACHE_DEFAULTS.recycleFreq); 12 | assert.equal(cache.info().storageMode, CACHE_DEFAULTS.storageMode); 13 | assert.equal(cache.info().storageImpl, CACHE_DEFAULTS.storageImpl); 14 | assert.equal(cache.info().disabled, CACHE_DEFAULTS.disabled); 15 | }); 16 | it('should set the default options.', function () { 17 | var options = { 18 | capacity: Math.floor((Math.random() * 100000) + 1), 19 | maxAge: Math.floor((Math.random() * 100000) + 1), 20 | cacheFlushInterval: Math.floor((Math.random() * 100000) + 1), 21 | deleteOnExpire: 'aggressive', 22 | storageMode: 'localStorage', 23 | storageImpl: { 24 | setItem: function () { 25 | }, 26 | getItem: function () { 27 | }, 28 | removeItem: function () { 29 | } 30 | }, 31 | verifyIntegrity: false, 32 | recycleFreq: 2000, 33 | disabled: true, 34 | onExpire: function () { 35 | } 36 | }; 37 | angular.extend(TestCacheFactoryProvider.defaults, options); 38 | var cache = TestCacheFactory('cache'); 39 | assert.isDefined(cache); 40 | assert.equal(cache.info().id, 'cache'); 41 | assert.equal(cache.info().capacity, options.capacity); 42 | assert.equal(cache.info().maxAge, options.maxAge); 43 | assert.equal(cache.info().cacheFlushInterval, options.cacheFlushInterval); 44 | assert.equal(cache.info().deleteOnExpire, options.deleteOnExpire); 45 | assert.equal(cache.info().storageMode, options.storageMode); 46 | assert.equal(cache.info().storageImpl, options.storageImpl); 47 | assert.equal(cache.info().onExpire, options.onExpire); 48 | assert.equal(cache.info().disabled, options.disabled); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/unit/DSCacheFactory/index.clearAll.test.js: -------------------------------------------------------------------------------- 1 | describe('CacheFactory.clearAll()', function () { 2 | it('should call "removeAll()" on all caches.', function () { 3 | var cacheKeys = ['CacheFactory.clearAll.cache', 'CacheFactory.clearAll.cache1', 'CacheFactory.clearAll.cache2'], 4 | caches = []; 5 | 6 | caches.push(TestCacheFactory(cacheKeys[0])); 7 | caches[0].put('item', 'value'); 8 | caches[0].put('item2', 'value2'); 9 | caches.push(TestCacheFactory(cacheKeys[1])); 10 | caches[1].put('item', 'value'); 11 | caches[1].put('item2', 'value2'); 12 | caches.push(TestCacheFactory(cacheKeys[2])); 13 | caches[2].put('item', 'value'); 14 | caches[2].put('item2', 'value2'); 15 | 16 | sinon.spy(caches[0], 'removeAll'); 17 | sinon.spy(caches[1], 'removeAll'); 18 | sinon.spy(caches[2], 'removeAll'); 19 | 20 | TestCacheFactory.clearAll(); 21 | 22 | assert.equal(caches[0].removeAll.callCount, 1); 23 | assert.equal(caches[1].removeAll.callCount, 1); 24 | assert.equal(caches[2].removeAll.callCount, 1); 25 | }); 26 | it('should result in each cache being cleared.', function () { 27 | var cacheKeys = ['CacheFactory.clearAll.cache', 'CacheFactory.clearAll.cache1', 'CacheFactory.clearAll.cache2'], 28 | caches = []; 29 | 30 | caches.push(TestCacheFactory(cacheKeys[0])); 31 | caches[0].put('item', 'value'); 32 | caches[0].put('item2', 'value2'); 33 | caches.push(TestCacheFactory(cacheKeys[1])); 34 | caches[1].put('item', 'value'); 35 | caches[1].put('item2', 'value2'); 36 | caches.push(TestCacheFactory(cacheKeys[2])); 37 | caches[2].put('item', 'value'); 38 | caches[2].put('item2', 'value2'); 39 | 40 | assert.isDefined(caches[0].get('item')); 41 | assert.isDefined(caches[1].get('item')); 42 | assert.isDefined(caches[2].get('item')); 43 | assert.isDefined(caches[0].get('item2')); 44 | assert.isDefined(caches[1].get('item2')); 45 | assert.isDefined(caches[2].get('item2')); 46 | 47 | TestCacheFactory.clearAll(); 48 | 49 | assert.isUndefined(caches[0].get('item')); 50 | assert.isUndefined(caches[1].get('item')); 51 | assert.isUndefined(caches[2].get('item')); 52 | assert.isUndefined(caches[0].get('item2')); 53 | assert.isUndefined(caches[1].get('item2')); 54 | assert.isUndefined(caches[2].get('item2')); 55 | assert.equal(caches[0].info().size, 0); 56 | assert.equal(caches[1].info().size, 0); 57 | assert.equal(caches[2].info().size, 0); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/unit/DSCacheFactory/index.destroyAll.test.js: -------------------------------------------------------------------------------- 1 | describe('CacheFactory.destroyAll()', function () { 2 | it('should call "destroy()" on all caches currently owned by the factory.', function (done) { 3 | var cacheKeys = ['CacheFactory.destroyAll.cache', 'CacheFactory.destroyAll.cache1', 'CacheFactory.destroyAll.cache2'], 4 | caches = []; 5 | 6 | caches.push(TestCacheFactory(cacheKeys[0])); 7 | caches.push(TestCacheFactory(cacheKeys[1])); 8 | caches.push(TestCacheFactory(cacheKeys[2])); 9 | 10 | sinon.spy(caches[0], 'destroy'); 11 | sinon.spy(caches[1], 'destroy'); 12 | sinon.spy(caches[2], 'destroy'); 13 | TestCacheFactory.destroyAll(); 14 | 15 | assert.equal(caches[0].destroy.callCount, 1); 16 | assert.equal(caches[1].destroy.callCount, 1); 17 | assert.equal(caches[2].destroy.callCount, 1); 18 | 19 | done(); 20 | }); 21 | it('should result in all caches being removed from CacheFactory.', function (done) { 22 | var cacheKeys = ['CacheFactory.destroyAll.cache', 'CacheFactory.destroyAll.cache1', 'CacheFactory.destroyAll.cache2'], 23 | caches = []; 24 | 25 | caches.push(TestCacheFactory(cacheKeys[0])); 26 | caches.push(TestCacheFactory(cacheKeys[1])); 27 | caches.push(TestCacheFactory(cacheKeys[2])); 28 | 29 | TestCacheFactory.destroyAll(); 30 | 31 | assert.isUndefined(TestCacheFactory.get(cacheKeys[0])); 32 | assert.isUndefined(TestCacheFactory.get(cacheKeys[1])); 33 | assert.isUndefined(TestCacheFactory.get(cacheKeys[2])); 34 | 35 | done(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/unit/DSCacheFactory/index.disableAll.test.js: -------------------------------------------------------------------------------- 1 | describe('CacheFactory.disableAll()', function () { 2 | it('should disable all caches in CacheFactory.', function (done) { 3 | var cacheKeys = ['CacheFactory.disableAll.cache', 'CacheFactory.disableAll.cache1', 'CacheFactory.disableAll.cache2']; 4 | 5 | TestCacheFactory(cacheKeys[0]); 6 | TestCacheFactory(cacheKeys[1], { disabled: true }); 7 | TestCacheFactory(cacheKeys[2]); 8 | 9 | assert.equal(TestCacheFactory.get(cacheKeys[0]).info().disabled, false); 10 | assert.equal(TestCacheFactory.get(cacheKeys[1]).info().disabled, true); 11 | assert.equal(TestCacheFactory.get(cacheKeys[2]).info().disabled, false); 12 | 13 | TestCacheFactory.disableAll(); 14 | 15 | assert.equal(TestCacheFactory.get(cacheKeys[0]).info().disabled, true); 16 | assert.equal(TestCacheFactory.get(cacheKeys[1]).info().disabled, true); 17 | assert.equal(TestCacheFactory.get(cacheKeys[2]).info().disabled, true); 18 | 19 | done(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/unit/DSCacheFactory/index.enableAll.test.js: -------------------------------------------------------------------------------- 1 | describe('CacheFactory.enableAll()', function () { 2 | it('should enable all caches in CacheFactory.', function (done) { 3 | var cacheKeys = ['CacheFactory.enableAll.cache', 'CacheFactory.enableAll.cache1', 'CacheFactory.enableAll.cache2']; 4 | 5 | TestCacheFactory(cacheKeys[0], { disabled: true }); 6 | TestCacheFactory(cacheKeys[1]); 7 | TestCacheFactory(cacheKeys[2], { disabled: true }); 8 | 9 | assert.equal(TestCacheFactory.get(cacheKeys[0]).info().disabled, true); 10 | assert.equal(TestCacheFactory.get(cacheKeys[1]).info().disabled, false); 11 | assert.equal(TestCacheFactory.get(cacheKeys[2]).info().disabled, true); 12 | 13 | TestCacheFactory.enableAll(); 14 | 15 | assert.equal(TestCacheFactory.get(cacheKeys[0]).info().disabled, false); 16 | assert.equal(TestCacheFactory.get(cacheKeys[1]).info().disabled, false); 17 | assert.equal(TestCacheFactory.get(cacheKeys[2]).info().disabled, false); 18 | 19 | done(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/unit/DSCacheFactory/index.get.test.js: -------------------------------------------------------------------------------- 1 | describe('CacheFactory.get(cacheId)', function () { 2 | it('should return "undefined" if the cache does not exist.', function (done) { 3 | assert.isUndefined(TestCacheFactory.get('someNonExistentCache')); 4 | done(); 5 | }); 6 | it('should return the correct cache with the specified cacheId.', function (done) { 7 | var cache = TestCacheFactory('CacheFactory.get.cache'), 8 | cache2 = TestCacheFactory('CacheFactory.get.cache2'); 9 | assert.equal(TestCacheFactory.get('CacheFactory.get.cache'), cache); 10 | assert.equal(TestCacheFactory.get('CacheFactory.get.cache2'), cache2); 11 | assert.notEqual(cache, cache2); 12 | 13 | done(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/unit/DSCacheFactory/index.info.test.js: -------------------------------------------------------------------------------- 1 | describe('CacheFactory.info()', function () { 2 | it('should return the correct info for CacheFactory.', function (done) { 3 | var options = { 4 | capacity: Math.floor((Math.random() * 100000) + 1), 5 | maxAge: Math.floor((Math.random() * 100000) + 1), 6 | cacheFlushInterval: Math.floor((Math.random() * 100000) + 1) 7 | }, 8 | caches = []; 9 | 10 | caches.push(TestCacheFactory('cache')); 11 | caches.push(TestCacheFactory('cache2', { 12 | maxAge: options.maxAge 13 | })); 14 | caches.push(TestCacheFactory('cache3', { 15 | capacity: options.capacity, 16 | cacheFlushInterval: options.cacheFlushInterval 17 | })); 18 | var info = TestCacheFactory.info(); 19 | assert.equal(info.size, 3); 20 | 21 | assert.equal(info.capacity, CACHE_DEFAULTS.capacity); 22 | assert.equal(info.maxAge, CACHE_DEFAULTS.maxAge); 23 | assert.equal(info.cacheFlushInterval, CACHE_DEFAULTS.cacheFlushInterval); 24 | assert.equal(info.deleteOnExpire, CACHE_DEFAULTS.deleteOnExpire); 25 | assert.equal(info.onExpire, CACHE_DEFAULTS.onExpire); 26 | assert.equal(info.recycleFreq, CACHE_DEFAULTS.recycleFreq); 27 | assert.equal(info.storageMode, CACHE_DEFAULTS.storageMode); 28 | assert.equal(info.storageImpl, CACHE_DEFAULTS.storageImpl); 29 | 30 | assert.equal(info.caches.cache.id, caches[0].info().id); 31 | assert.equal(info.caches.cache.capacity, caches[0].info().capacity); 32 | assert.equal(info.caches.cache.size, caches[0].info().size); 33 | 34 | assert.equal(info.caches.cache2.id, caches[1].info().id); 35 | assert.equal(info.caches.cache2.capacity, caches[1].info().capacity); 36 | assert.equal(info.caches.cache2.size, caches[1].info().size); 37 | assert.equal(info.caches.cache2.maxAge, caches[1].info().maxAge); 38 | 39 | assert.equal(info.caches.cache3.id, caches[2].info().id); 40 | assert.equal(info.caches.cache3.capacity, caches[2].info().capacity); 41 | assert.equal(info.caches.cache3.size, caches[2].info().size); 42 | assert.equal(info.caches.cache3.cacheFlushInterval, caches[2].info().cacheFlushInterval); 43 | 44 | done(); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/unit/DSCacheFactory/index.keySet.test.js: -------------------------------------------------------------------------------- 1 | describe('CacheFactory.keySet()', function () { 2 | it('should return the set of keys of all caches in CacheFactory.', function (done) { 3 | var cacheKeys = ['CacheFactory.keySet.cache', 'CacheFactory.keySet.cache1', 'CacheFactory.keySet.cache2']; 4 | 5 | TestCacheFactory(cacheKeys[0]); 6 | TestCacheFactory(cacheKeys[1]); 7 | TestCacheFactory(cacheKeys[2]); 8 | 9 | var keySet = TestCacheFactory.keySet(); 10 | 11 | assert.equal(keySet.hasOwnProperty(cacheKeys[0]), true); 12 | assert.equal(keySet.hasOwnProperty(cacheKeys[1]), true); 13 | assert.equal(keySet.hasOwnProperty(cacheKeys[2]), true); 14 | 15 | assert.equal(keySet[cacheKeys[0]], cacheKeys[0]); 16 | assert.equal(keySet[cacheKeys[1]], cacheKeys[1]); 17 | assert.equal(keySet[cacheKeys[2]], cacheKeys[2]); 18 | 19 | TestCacheFactory.get(cacheKeys[0]).destroy(); 20 | keySet = TestCacheFactory.keySet(); 21 | assert.equal(keySet.hasOwnProperty(cacheKeys[0]), false); 22 | assert.equal(keySet.hasOwnProperty(cacheKeys[1]), true); 23 | assert.equal(keySet.hasOwnProperty(cacheKeys[2]), true); 24 | assert.isUndefined(keySet[cacheKeys[0]]); 25 | assert.equal(keySet[cacheKeys[1]], cacheKeys[1]); 26 | assert.equal(keySet[cacheKeys[2]], cacheKeys[2]); 27 | 28 | TestCacheFactory.get(cacheKeys[1]).destroy(); 29 | keySet = TestCacheFactory.keySet(); 30 | assert.equal(keySet.hasOwnProperty(cacheKeys[0]), false); 31 | assert.equal(keySet.hasOwnProperty(cacheKeys[1]), false); 32 | assert.equal(keySet.hasOwnProperty(cacheKeys[2]), true); 33 | assert.isUndefined(keySet[cacheKeys[0]]); 34 | assert.isUndefined(keySet[cacheKeys[1]]); 35 | assert.equal(keySet[cacheKeys[2]], cacheKeys[2]); 36 | 37 | TestCacheFactory.get(cacheKeys[2]).destroy(); 38 | 39 | keySet = TestCacheFactory.keySet(); 40 | 41 | assert.equal(keySet.hasOwnProperty(cacheKeys[0]), false); 42 | assert.equal(keySet.hasOwnProperty(cacheKeys[1]), false); 43 | assert.equal(keySet.hasOwnProperty(cacheKeys[2]), false); 44 | assert.isUndefined(keySet[cacheKeys[0]]); 45 | assert.isUndefined(keySet[cacheKeys[1]]); 46 | assert.isUndefined(keySet[cacheKeys[2]]); 47 | 48 | done(); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/unit/DSCacheFactory/index.keys.test.js: -------------------------------------------------------------------------------- 1 | describe('CacheFactory.keys()', function () { 2 | it('should return the array of keys of all caches in CacheFactory.', function (done) { 3 | var cacheKeys = ['cache', 'cache1', 'cache2']; 4 | 5 | TestCacheFactory(cacheKeys[0]); 6 | TestCacheFactory(cacheKeys[1]); 7 | TestCacheFactory(cacheKeys[2]); 8 | 9 | var keys = TestCacheFactory.keys(); 10 | assert.equal(keys.length, 3); 11 | assert.equal(keys[0], cacheKeys[0]); 12 | assert.equal(keys[1], cacheKeys[1]); 13 | assert.equal(keys[2], cacheKeys[2]); 14 | 15 | TestCacheFactory.get(cacheKeys[0]).destroy(); 16 | keys = TestCacheFactory.keys(); 17 | assert.equal(keys.length, 2); 18 | assert.notEqual(keys.indexOf(cacheKeys[1]), -1); 19 | assert.notEqual(keys.indexOf(cacheKeys[2]), -1); 20 | 21 | TestCacheFactory.get(cacheKeys[1]).destroy(); 22 | keys = TestCacheFactory.keys(); 23 | assert.equal(keys.length, 1); 24 | assert.notEqual(keys.indexOf(cacheKeys[2]), -1); 25 | 26 | TestCacheFactory.get(cacheKeys[2]).destroy(); 27 | 28 | keys = TestCacheFactory.keys(); 29 | 30 | assert.equal(keys.length, 0); 31 | 32 | done(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/unit/DSCacheFactory/index.test.js: -------------------------------------------------------------------------------- 1 | describe('CacheFactory(cacheId, options)', function () { 2 | it('should be able to create a default cache.', function () { 3 | var cache = TestCacheFactory('CacheFactory.cache'); 4 | assert.isDefined(cache); 5 | assert.equal(cache.info().id, 'CacheFactory.cache'); 6 | assert.equal(cache.info().capacity, CACHE_DEFAULTS.capacity); 7 | assert.equal(cache.info().maxAge, CACHE_DEFAULTS.maxAge); 8 | assert.equal(cache.info().cacheFlushInterval, CACHE_DEFAULTS.cacheFlushInterval); 9 | assert.equal(cache.info().deleteOnExpire, CACHE_DEFAULTS.deleteOnExpire); 10 | assert.equal(cache.info().onExpire, CACHE_DEFAULTS.onExpire); 11 | assert.equal(cache.info().recycleFreq, CACHE_DEFAULTS.recycleFreq); 12 | assert.equal(cache.info().storageMode, CACHE_DEFAULTS.storageMode); 13 | assert.equal(cache.info().storageImpl, CACHE_DEFAULTS.storageImpl); 14 | }); 15 | it('should work with ngResource.', function () { 16 | var cache = TestCacheFactory('CacheFactory.cache'); 17 | cache.put('test', 'value'); 18 | assert.equal(cache.get('test'), 'value'); 19 | var copyCache = angular.copy(cache); 20 | assert.equal(copyCache.get('test'), 'value'); 21 | $httpBackend.expectGET('/api/card').respond(200, { 22 | username: 'test' 23 | }); 24 | var CreditCard = $resource( 25 | '/api/card', 26 | null, 27 | { charge: { method: 'GET', cache: cache } } 28 | ); 29 | var card = new CreditCard(); 30 | card.$charge(); 31 | $httpBackend.flush(); 32 | card.$charge().then(); 33 | }); 34 | it('should be able to create a cache with options.', function () { 35 | var options = { 36 | capacity: Math.floor((Math.random() * 100000) + 1), 37 | maxAge: Math.floor((Math.random() * 100000) + 1), 38 | cacheFlushInterval: Math.floor((Math.random() * 100000) + 1), 39 | deleteOnExpire: 'aggressive', 40 | storageMode: 'localStorage', 41 | storageImpl: { 42 | setItem: function () { 43 | }, 44 | getItem: function () { 45 | }, 46 | removeItem: function () { 47 | } 48 | }, 49 | recycleFreq: 2000, 50 | onExpire: function () { 51 | } 52 | }; 53 | var cache = TestCacheFactory('CacheFactory.cache', options); 54 | assert.isDefined(cache); 55 | assert.equal(cache.info().id, 'CacheFactory.cache'); 56 | assert.equal(cache.info().capacity, options.capacity); 57 | assert.equal(cache.info().maxAge, options.maxAge); 58 | assert.equal(cache.info().cacheFlushInterval, options.cacheFlushInterval); 59 | assert.equal(cache.info().deleteOnExpire, options.deleteOnExpire); 60 | assert.equal(cache.info().storageMode, options.storageMode); 61 | assert.equal(cache.info().storageImpl, options.storageImpl); 62 | assert.equal(cache.info().onExpire, options.onExpire); 63 | }); 64 | it('should not use localStorage if it is not available.', function () { 65 | function setItem() { 66 | throw new Error(); 67 | } 68 | 69 | var options = { 70 | deleteOnExpire: 'aggressive', 71 | storageMode: 'localStorage' 72 | }; 73 | var orig = localStorage.setItem; 74 | localStorage.setItem = setItem; 75 | if (localStorage.setItem === setItem) { 76 | var cache = TestCacheFactory('CacheFactory.cache', options); 77 | assert.isDefined(cache); 78 | assert.equal(cache.info().id, 'CacheFactory.cache'); 79 | assert.equal(cache.info().deleteOnExpire, options.deleteOnExpire); 80 | assert.equal(cache.info().storageMode, 'memory'); 81 | assert.isUndefined(cache.info().storageImpl); 82 | localStorage.setItem = orig; 83 | } else { 84 | console.log('skipping because Firefox!'); 85 | } 86 | }); 87 | it('should not use sessionStorage if it is not available.', function () { 88 | function setItem() { 89 | throw new Error(); 90 | } 91 | 92 | var options = { 93 | deleteOnExpire: 'aggressive', 94 | storageMode: 'sessionStorage' 95 | }; 96 | var orig = sessionStorage.setItem; 97 | sessionStorage.setItem = setItem; 98 | if (sessionStorage.setItem === setItem) { 99 | var cache = TestCacheFactory('CacheFactory.cache', options); 100 | assert.isDefined(cache); 101 | assert.equal(cache.info().id, 'CacheFactory.cache'); 102 | assert.equal(cache.info().deleteOnExpire, options.deleteOnExpire); 103 | assert.equal(cache.info().storageMode, 'memory'); 104 | assert.isUndefined(cache.info().storageImpl); 105 | sessionStorage.setItem = orig; 106 | } else { 107 | console.log('skipping because Firefox!'); 108 | } 109 | }); 110 | it('should throw an exception if "capacity" is not a number or is less than zero.', function () { 111 | var capacity = Math.floor((Math.random() * 100000) + 1) * -1; 112 | try { 113 | TestCacheFactory('capacityCache99', { capacity: capacity }); 114 | fail(); 115 | } catch (err) { 116 | var msg = err.message; 117 | } 118 | assert.equal(msg, 'capacity must be greater than zero!'); 119 | for (var i = 0; i < TYPES_EXCEPT_NUMBER.length; i++) { 120 | if (TYPES_EXCEPT_NUMBER[i] === null) { 121 | continue; 122 | } 123 | try { 124 | TestCacheFactory('capacityCache' + i, { capacity: TYPES_EXCEPT_NUMBER[i] }); 125 | fail(); 126 | } catch (err) { 127 | assert.equal(err.message, 'capacity must be a number!'); 128 | continue; 129 | } 130 | fail(); 131 | } 132 | }); 133 | it('should validate maxAge.', function () { 134 | var maxAge = Math.floor((Math.random() * 100000) + 1) * -1; 135 | try { 136 | TestCacheFactory('maxAgeCache99', { maxAge: maxAge }); 137 | fail(); 138 | } catch (err) { 139 | var msg = err.message; 140 | } 141 | assert.equal(msg, 'maxAge must be greater than zero!'); 142 | for (var i = 0; i < TYPES_EXCEPT_NUMBER.length; i++) { 143 | try { 144 | TestCacheFactory('maxAgeCache' + i, { maxAge: TYPES_EXCEPT_NUMBER[i] }); 145 | if (TYPES_EXCEPT_NUMBER[i] !== null) { 146 | fail(TYPES_EXCEPT_NUMBER[i]); 147 | } 148 | } catch (err) { 149 | assert.equal(err.message, 'maxAge must be a number!'); 150 | continue; 151 | } 152 | if (TYPES_EXCEPT_NUMBER[i] !== null) { 153 | fail(TYPES_EXCEPT_NUMBER[i]); 154 | } 155 | } 156 | }); 157 | it('should validate cacheFlushInterval.', function () { 158 | var cacheFlushInterval = Math.floor((Math.random() * 100000) + 1) * -1; 159 | try { 160 | TestCacheFactory('cacheFlushIntervalCache99', { cacheFlushInterval: cacheFlushInterval }); 161 | fail(); 162 | } catch (err) { 163 | var msg = err.message; 164 | } 165 | assert.equal(msg, 'cacheFlushInterval must be greater than zero!'); 166 | for (var i = 0; i < TYPES_EXCEPT_NUMBER.length; i++) { 167 | try { 168 | TestCacheFactory('cacheFlushIntervalCache' + i, { cacheFlushInterval: TYPES_EXCEPT_NUMBER[i] }); 169 | if (TYPES_EXCEPT_NUMBER[i] !== null) { 170 | fail(); 171 | } 172 | } catch (err) { 173 | assert.equal(err.message, 'cacheFlushInterval must be a number!'); 174 | continue; 175 | } 176 | if (TYPES_EXCEPT_NUMBER[i] !== null) { 177 | fail(); 178 | } 179 | } 180 | }); 181 | it('should validate recycleFreq.', function () { 182 | var recycleFreq = Math.floor((Math.random() * 100000) + 1) * -1; 183 | try { 184 | TestCacheFactory('recycleFreqCache99', { recycleFreq: recycleFreq }); 185 | fail(); 186 | } catch (err) { 187 | var msg = err.message; 188 | } 189 | assert.equal(msg, 'recycleFreq must be greater than zero!'); 190 | for (var i = 0; i < TYPES_EXCEPT_NUMBER.length; i++) { 191 | try { 192 | TestCacheFactory('recycleFreqCache' + i, { recycleFreq: TYPES_EXCEPT_NUMBER[i] }); 193 | if (TYPES_EXCEPT_NUMBER[i] !== null) { 194 | fail(); 195 | } 196 | } catch (err) { 197 | assert.equal(err.message, 'recycleFreq must be a number!'); 198 | continue; 199 | } 200 | if (TYPES_EXCEPT_NUMBER[i] !== null) { 201 | fail(); 202 | } 203 | } 204 | }); 205 | it('should validate onExpire.', function () { 206 | var onExpire = 234; 207 | try { 208 | TestCacheFactory('onExpireCache99', { onExpire: onExpire }); 209 | assert.equal('should not reach this!', false); 210 | } catch (err) { 211 | var msg = err.message; 212 | } 213 | assert.equal(msg, 'onExpire must be a function!'); 214 | for (var i = 0; i < TYPES_EXCEPT_FUNCTION.length; i++) { 215 | try { 216 | if (TYPES_EXCEPT_FUNCTION[i]) { 217 | TestCacheFactory('onExpireCache' + i, { onExpire: TYPES_EXCEPT_FUNCTION[i] }); 218 | } else { 219 | continue; 220 | } 221 | } catch (err) { 222 | assert.equal(err.message, 'onExpire must be a function!'); 223 | continue; 224 | } 225 | if (TYPES_EXCEPT_FUNCTION[i] !== null) { 226 | fail(); 227 | } 228 | } 229 | }); 230 | it('should validate deleteOnExpire.', function () { 231 | var deleteOnExpire = 'fail'; 232 | try { 233 | TestCacheFactory('cache', { deleteOnExpire: deleteOnExpire }); 234 | fail('should not reach this!'); 235 | } catch (err) { 236 | var msg = err.message; 237 | } 238 | assert.equal(msg, 'deleteOnExpire must be "none", "passive" or "aggressive"!'); 239 | for (var i = 0; i < TYPES_EXCEPT_STRING.length; i++) { 240 | if (TYPES_EXCEPT_STRING[i] === null) { 241 | continue; 242 | } 243 | try { 244 | TestCacheFactory('deleteOnExpireCache' + i, { deleteOnExpire: TYPES_EXCEPT_STRING[i] }); 245 | fail(TYPES_EXCEPT_STRING[i]); 246 | } catch (err) { 247 | assert.equal(err.message, 'deleteOnExpire must be a string!'); 248 | continue; 249 | } 250 | fail(TYPES_EXCEPT_STRING[i]); 251 | } 252 | }); 253 | it('should validate storageMode.', function () { 254 | var storageMode = 'fail'; 255 | try { 256 | TestCacheFactory('cache', { storageMode: storageMode }); 257 | assert.equal('should not reach this!', false); 258 | } catch (err) { 259 | var msg = err.message; 260 | } 261 | assert.equal(msg, 'storageMode must be "memory", "localStorage" or "sessionStorage"!'); 262 | for (var i = 0; i < TYPES_EXCEPT_STRING.length; i++) { 263 | if (!TYPES_EXCEPT_STRING[i]) { 264 | continue; 265 | } 266 | try { 267 | TestCacheFactory('storageModeCache' + i, { storageMode: TYPES_EXCEPT_STRING[i] }); 268 | fail(TYPES_EXCEPT_STRING[i]); 269 | } catch (err) { 270 | assert.equal(err.message, 'storageMode must be a string!'); 271 | continue; 272 | } 273 | fail(TYPES_EXCEPT_STRING[i]); 274 | } 275 | }); 276 | it('should validate storageImpl.', function () { 277 | var storageImpl = 'fail'; 278 | try { 279 | TestCacheFactory('cache', { storageMode: 'localStorage', storageImpl: storageImpl }); 280 | assert.equal('should not reach this!', false); 281 | } catch (err) { 282 | var msg = err.message; 283 | } 284 | assert.equal(msg, 'storageImpl must be an object!'); 285 | for (var i = 0; i < TYPES_EXCEPT_OBJECT.length; i++) { 286 | try { 287 | TestCacheFactory('storageImplCache' + i, { storageMode: 'localStorage', storageImpl: TYPES_EXCEPT_OBJECT[i] }); 288 | if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { 289 | fail(TYPES_EXCEPT_OBJECT[i]); 290 | } 291 | } catch (err) { 292 | assert.equal(err.message, 'storageImpl must be an object!'); 293 | continue; 294 | } 295 | if (TYPES_EXCEPT_OBJECT[i] !== null && TYPES_EXCEPT_OBJECT[i] !== undefined && TYPES_EXCEPT_OBJECT[i] !== false) { 296 | fail(TYPES_EXCEPT_OBJECT[i]); 297 | } 298 | } 299 | try { 300 | storageImpl = { 301 | getItem: function () { 302 | }, 303 | removeItem: function () { 304 | } 305 | }; 306 | TestCacheFactory('storageImplCache-noSetItem', { 307 | storageMode: 'localStorage', 308 | storageImpl: storageImpl 309 | }); 310 | fail(); 311 | } catch (err) { 312 | assert.equal(err.message, 'storageImpl must implement "setItem(key, value)"!'); 313 | } 314 | try { 315 | storageImpl = { 316 | setItem: function () { 317 | }, 318 | removeItem: function () { 319 | } 320 | }; 321 | TestCacheFactory('storageImplCache-noGetItem', { 322 | storageMode: 'localStorage', 323 | storageImpl: storageImpl 324 | }); 325 | fail(); 326 | } catch (err) { 327 | assert.equal(err.message, 'storageImpl must implement "getItem(key)"!'); 328 | } 329 | try { 330 | storageImpl = { 331 | getItem: function () { 332 | }, 333 | setItem: function () { 334 | } 335 | }; 336 | TestCacheFactory('storageImplCache-noRemoveItem', { 337 | storageMode: 'localStorage', 338 | storageImpl: storageImpl 339 | }); 340 | fail(); 341 | } catch (err) { 342 | assert.equal(err.message, 'storageImpl must implement "removeItem(key)"!'); 343 | } 344 | }); 345 | it('should prevent a cache from being duplicated.', function () { 346 | try { 347 | TestCacheFactory('cache'); 348 | TestCacheFactory('cache'); 349 | } catch (err) { 350 | var msg = err.message; 351 | } 352 | assert.equal(msg, 'cache already exists!'); 353 | }); 354 | it('should require cacheId to be a string.', function () { 355 | for (var i = 0; i < TYPES_EXCEPT_STRING.length; i++) { 356 | try { 357 | TestCacheFactory(TYPES_EXCEPT_STRING[i]); 358 | fail(TYPES_EXCEPT_STRING[i]); 359 | } catch (err) { 360 | assert.equal(err.message, 'cacheId must be a string!'); 361 | continue; 362 | } 363 | fail(TYPES_EXCEPT_STRING[i]); 364 | } 365 | }); 366 | }); 367 | --------------------------------------------------------------------------------