├── .gitignore ├── .jshintrc ├── .npmignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── async.ts ├── freeze.ts ├── index.ts ├── sync.ts └── util.ts ├── test ├── lru-memoizer.bypass.test.js ├── lru-memoizer.clone.test.js ├── lru-memoizer.disable.test.js ├── lru-memoizer.events.test.js ├── lru-memoizer.freeze.test.js ├── lru-memoizer.itemmaxage.test.js ├── lru-memoizer.lock.test.js ├── lru-memoizer.nokey.test.js ├── lru-memoizer.queumaxage.test.js ├── lru-memoizer.sync.clone.test.js ├── lru-memoizer.sync.events.test.js ├── lru-memoizer.sync.freeze.js ├── lru-memoizer.sync.test.js └── lru-memoizer.test.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | lib 41 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "camelcase": false, 3 | "curly": false, 4 | 5 | "node": true, 6 | "esnext": true, 7 | "bitwise": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": false, 12 | "newcap": true, 13 | "noarg": true, 14 | "regexp": true, 15 | "undef": true, 16 | "strict": false, 17 | "smarttabs": true, 18 | "expr": true, 19 | 20 | "evil": true, 21 | "browser": true, 22 | "regexdash": true, 23 | "wsh": true, 24 | "trailing": true, 25 | "sub": true, 26 | "unused": true, 27 | "laxcomma": true, 28 | "nonbsp": true, 29 | 30 | "newcap": false, 31 | 32 | "globals": { 33 | "after": false, 34 | "before": false, 35 | "afterEach": false, 36 | "beforeEach": false, 37 | "describe": false, 38 | "it": false, 39 | "escape": false 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | src 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 JOSE FERNANDO ROMANIELLO (http://joseoncode.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Memoize functions results using an lru-cache. 2 | 3 | ## Installation 4 | 5 | ``` 6 | npm i lru-memoizer --save 7 | ``` 8 | 9 | ## Intro 10 | 11 | This module uses an [lru-cache](https://github.com/isaacs/node-lru-cache) internally to cache the results of an async function. 12 | 13 | The `load` function can have N parameters and the last one must be a callback. The callback should be an errback (first parameter is `err`). 14 | 15 | The `hash` function purpose is generate a custom hash for storing results. It has all the arguments applied to it minus the callback, and must return a synchronous string. 16 | 17 | The `disable` function allows you to conditionally disable the use of the cache. Useful for test environments. 18 | 19 | The `freeze` option (defaults to **false**) allows you to deep-freeze the result of the async function. 20 | 21 | The `clone` option (defaults to **false**) allows you to deep-clone the result every time is returned from the cache. 22 | 23 | ## Usage 24 | 25 | ```javascript 26 | const memoizer = require("lru-memoizer"); 27 | 28 | const memoizedGet = memoizer({ 29 | //defines how to load the resource when 30 | //it is not in the cache. 31 | load: function (options, callback) { 32 | request.get(options, callback); 33 | }, 34 | 35 | //defines how to create a cache key from the params. 36 | hash: function (options) { 37 | return options.url + qs.stringify(options.qs); 38 | }, 39 | 40 | //don't cache in test environment 41 | disable: isTestEnv(), 42 | 43 | //all other params for the LRU cache. 44 | max: 100, 45 | ttl: 1000 * 60, 46 | }); 47 | 48 | memoizedGet( 49 | { 50 | url: "https://google.com", 51 | qs: { foo: 123 }, 52 | }, 53 | function (err, result, body) { 54 | //console.log(body); 55 | } 56 | ); 57 | ``` 58 | 59 | ## Synchronous lru-memoizer 60 | 61 | Use `memoizer.sync` to cache things that are slow to calculate, methods returning promises, or only if you don't want to use a callback and want it synchronous. 62 | 63 | ```javascript 64 | const memoizer = require("lru-memoizer"); 65 | const memoizedGet = memoizer.sync({ 66 | //defines how to load the resource when 67 | //it is not in the cache. 68 | load: function (params) { 69 | return somethingHardToCompute(); 70 | }, 71 | 72 | //defines how to create a cache key from the params. 73 | hash: function (params) { 74 | return params.foo; 75 | }, 76 | 77 | //all other params for the LRU cache. 78 | max: 100, 79 | ttl: 1000 * 60, 80 | }); 81 | ``` 82 | 83 | ## Similar modules 84 | 85 | This module is very similar to [async-cache](https://github.com/isaacs/async-cache)(deprecated), the main difference is the `hash` function. 86 | 87 | ## License 88 | 89 | MIT 2016 - José F. Romaniello 90 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lru-memoizer", 3 | "version": "3.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "lru-memoizer", 9 | "version": "3.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "lodash.clonedeep": "^4.5.0", 13 | "lru-cache": "^11.0.1" 14 | }, 15 | "devDependencies": { 16 | "@types/lodash.clonedeep": "^4.5.9", 17 | "@types/node": "^12.0.10", 18 | "chai": "^3.5.0", 19 | "mocha": "^10.4.0", 20 | "sinon": "^7.3.2", 21 | "typescript": "^4.9.5" 22 | } 23 | }, 24 | "node_modules/@sinonjs/commons": { 25 | "version": "1.8.6", 26 | "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", 27 | "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", 28 | "dev": true, 29 | "dependencies": { 30 | "type-detect": "4.0.8" 31 | } 32 | }, 33 | "node_modules/@sinonjs/commons/node_modules/type-detect": { 34 | "version": "4.0.8", 35 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", 36 | "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", 37 | "dev": true, 38 | "engines": { 39 | "node": ">=4" 40 | } 41 | }, 42 | "node_modules/@sinonjs/formatio": { 43 | "version": "3.2.2", 44 | "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", 45 | "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", 46 | "dev": true, 47 | "dependencies": { 48 | "@sinonjs/commons": "^1", 49 | "@sinonjs/samsam": "^3.1.0" 50 | } 51 | }, 52 | "node_modules/@sinonjs/samsam": { 53 | "version": "3.3.3", 54 | "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", 55 | "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", 56 | "dev": true, 57 | "dependencies": { 58 | "@sinonjs/commons": "^1.3.0", 59 | "array-from": "^2.1.1", 60 | "lodash": "^4.17.15" 61 | } 62 | }, 63 | "node_modules/@sinonjs/text-encoding": { 64 | "version": "0.7.2", 65 | "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", 66 | "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", 67 | "dev": true 68 | }, 69 | "node_modules/@types/lodash": { 70 | "version": "4.14.191", 71 | "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", 72 | "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==", 73 | "dev": true 74 | }, 75 | "node_modules/@types/lodash.clonedeep": { 76 | "version": "4.5.9", 77 | "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.9.tgz", 78 | "integrity": "sha512-19429mWC+FyaAhOLzsS8kZUsI+/GmBAQ0HFiCPsKGU+7pBXOQWhyrY6xNNDwUSX8SMZMJvuFVMF9O5dQOlQK9Q==", 79 | "dev": true, 80 | "dependencies": { 81 | "@types/lodash": "*" 82 | } 83 | }, 84 | "node_modules/@types/node": { 85 | "version": "12.20.55", 86 | "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", 87 | "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", 88 | "dev": true 89 | }, 90 | "node_modules/ansi-colors": { 91 | "version": "4.1.1", 92 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", 93 | "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", 94 | "dev": true, 95 | "engines": { 96 | "node": ">=6" 97 | } 98 | }, 99 | "node_modules/ansi-regex": { 100 | "version": "5.0.1", 101 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 102 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 103 | "dev": true, 104 | "engines": { 105 | "node": ">=8" 106 | } 107 | }, 108 | "node_modules/ansi-styles": { 109 | "version": "4.3.0", 110 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 111 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 112 | "dev": true, 113 | "dependencies": { 114 | "color-convert": "^2.0.1" 115 | }, 116 | "engines": { 117 | "node": ">=8" 118 | }, 119 | "funding": { 120 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 121 | } 122 | }, 123 | "node_modules/anymatch": { 124 | "version": "3.1.3", 125 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 126 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 127 | "dev": true, 128 | "dependencies": { 129 | "normalize-path": "^3.0.0", 130 | "picomatch": "^2.0.4" 131 | }, 132 | "engines": { 133 | "node": ">= 8" 134 | } 135 | }, 136 | "node_modules/argparse": { 137 | "version": "2.0.1", 138 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 139 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 140 | "dev": true 141 | }, 142 | "node_modules/array-from": { 143 | "version": "2.1.1", 144 | "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", 145 | "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==", 146 | "dev": true 147 | }, 148 | "node_modules/assertion-error": { 149 | "version": "1.1.0", 150 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", 151 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", 152 | "dev": true, 153 | "engines": { 154 | "node": "*" 155 | } 156 | }, 157 | "node_modules/balanced-match": { 158 | "version": "1.0.2", 159 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 160 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 161 | "dev": true 162 | }, 163 | "node_modules/binary-extensions": { 164 | "version": "2.2.0", 165 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 166 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 167 | "dev": true, 168 | "engines": { 169 | "node": ">=8" 170 | } 171 | }, 172 | "node_modules/brace-expansion": { 173 | "version": "2.0.1", 174 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 175 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 176 | "dev": true, 177 | "dependencies": { 178 | "balanced-match": "^1.0.0" 179 | } 180 | }, 181 | "node_modules/braces": { 182 | "version": "3.0.2", 183 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 184 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 185 | "dev": true, 186 | "dependencies": { 187 | "fill-range": "^7.0.1" 188 | }, 189 | "engines": { 190 | "node": ">=8" 191 | } 192 | }, 193 | "node_modules/browser-stdout": { 194 | "version": "1.3.1", 195 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 196 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 197 | "dev": true 198 | }, 199 | "node_modules/camelcase": { 200 | "version": "6.3.0", 201 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", 202 | "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", 203 | "dev": true, 204 | "engines": { 205 | "node": ">=10" 206 | }, 207 | "funding": { 208 | "url": "https://github.com/sponsors/sindresorhus" 209 | } 210 | }, 211 | "node_modules/chai": { 212 | "version": "3.5.0", 213 | "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", 214 | "integrity": "sha512-eRYY0vPS2a9zt5w5Z0aCeWbrXTEyvk7u/Xf71EzNObrjSCPgMm1Nku/D/u2tiqHBX5j40wWhj54YJLtgn8g55A==", 215 | "dev": true, 216 | "dependencies": { 217 | "assertion-error": "^1.0.1", 218 | "deep-eql": "^0.1.3", 219 | "type-detect": "^1.0.0" 220 | }, 221 | "engines": { 222 | "node": ">= 0.4.0" 223 | } 224 | }, 225 | "node_modules/chalk": { 226 | "version": "4.1.2", 227 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 228 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 229 | "dev": true, 230 | "dependencies": { 231 | "ansi-styles": "^4.1.0", 232 | "supports-color": "^7.1.0" 233 | }, 234 | "engines": { 235 | "node": ">=10" 236 | }, 237 | "funding": { 238 | "url": "https://github.com/chalk/chalk?sponsor=1" 239 | } 240 | }, 241 | "node_modules/chalk/node_modules/supports-color": { 242 | "version": "7.2.0", 243 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 244 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 245 | "dev": true, 246 | "dependencies": { 247 | "has-flag": "^4.0.0" 248 | }, 249 | "engines": { 250 | "node": ">=8" 251 | } 252 | }, 253 | "node_modules/chokidar": { 254 | "version": "3.5.3", 255 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 256 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 257 | "dev": true, 258 | "funding": [ 259 | { 260 | "type": "individual", 261 | "url": "https://paulmillr.com/funding/" 262 | } 263 | ], 264 | "dependencies": { 265 | "anymatch": "~3.1.2", 266 | "braces": "~3.0.2", 267 | "glob-parent": "~5.1.2", 268 | "is-binary-path": "~2.1.0", 269 | "is-glob": "~4.0.1", 270 | "normalize-path": "~3.0.0", 271 | "readdirp": "~3.6.0" 272 | }, 273 | "engines": { 274 | "node": ">= 8.10.0" 275 | }, 276 | "optionalDependencies": { 277 | "fsevents": "~2.3.2" 278 | } 279 | }, 280 | "node_modules/cliui": { 281 | "version": "7.0.4", 282 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 283 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 284 | "dev": true, 285 | "dependencies": { 286 | "string-width": "^4.2.0", 287 | "strip-ansi": "^6.0.0", 288 | "wrap-ansi": "^7.0.0" 289 | } 290 | }, 291 | "node_modules/color-convert": { 292 | "version": "2.0.1", 293 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 294 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 295 | "dev": true, 296 | "dependencies": { 297 | "color-name": "~1.1.4" 298 | }, 299 | "engines": { 300 | "node": ">=7.0.0" 301 | } 302 | }, 303 | "node_modules/color-name": { 304 | "version": "1.1.4", 305 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 306 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 307 | "dev": true 308 | }, 309 | "node_modules/debug": { 310 | "version": "4.3.4", 311 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 312 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 313 | "dev": true, 314 | "dependencies": { 315 | "ms": "2.1.2" 316 | }, 317 | "engines": { 318 | "node": ">=6.0" 319 | }, 320 | "peerDependenciesMeta": { 321 | "supports-color": { 322 | "optional": true 323 | } 324 | } 325 | }, 326 | "node_modules/debug/node_modules/ms": { 327 | "version": "2.1.2", 328 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 329 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 330 | "dev": true 331 | }, 332 | "node_modules/decamelize": { 333 | "version": "4.0.0", 334 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", 335 | "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", 336 | "dev": true, 337 | "engines": { 338 | "node": ">=10" 339 | }, 340 | "funding": { 341 | "url": "https://github.com/sponsors/sindresorhus" 342 | } 343 | }, 344 | "node_modules/deep-eql": { 345 | "version": "0.1.3", 346 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", 347 | "integrity": "sha512-6sEotTRGBFiNcqVoeHwnfopbSpi5NbH1VWJmYCVkmxMmaVTT0bUTrNaGyBwhgP4MZL012W/mkzIn3Da+iDYweg==", 348 | "dev": true, 349 | "dependencies": { 350 | "type-detect": "0.1.1" 351 | }, 352 | "engines": { 353 | "node": "*" 354 | } 355 | }, 356 | "node_modules/deep-eql/node_modules/type-detect": { 357 | "version": "0.1.1", 358 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", 359 | "integrity": "sha512-5rqszGVwYgBoDkIm2oUtvkfZMQ0vk29iDMU0W2qCa3rG0vPDNczCMT4hV/bLBgLg8k8ri6+u3Zbt+S/14eMzlA==", 360 | "dev": true, 361 | "engines": { 362 | "node": "*" 363 | } 364 | }, 365 | "node_modules/diff": { 366 | "version": "5.0.0", 367 | "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", 368 | "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", 369 | "dev": true, 370 | "engines": { 371 | "node": ">=0.3.1" 372 | } 373 | }, 374 | "node_modules/emoji-regex": { 375 | "version": "8.0.0", 376 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 377 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 378 | "dev": true 379 | }, 380 | "node_modules/escalade": { 381 | "version": "3.1.1", 382 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 383 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", 384 | "dev": true, 385 | "engines": { 386 | "node": ">=6" 387 | } 388 | }, 389 | "node_modules/escape-string-regexp": { 390 | "version": "4.0.0", 391 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 392 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 393 | "dev": true, 394 | "engines": { 395 | "node": ">=10" 396 | }, 397 | "funding": { 398 | "url": "https://github.com/sponsors/sindresorhus" 399 | } 400 | }, 401 | "node_modules/fill-range": { 402 | "version": "7.0.1", 403 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 404 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 405 | "dev": true, 406 | "dependencies": { 407 | "to-regex-range": "^5.0.1" 408 | }, 409 | "engines": { 410 | "node": ">=8" 411 | } 412 | }, 413 | "node_modules/find-up": { 414 | "version": "5.0.0", 415 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 416 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 417 | "dev": true, 418 | "dependencies": { 419 | "locate-path": "^6.0.0", 420 | "path-exists": "^4.0.0" 421 | }, 422 | "engines": { 423 | "node": ">=10" 424 | }, 425 | "funding": { 426 | "url": "https://github.com/sponsors/sindresorhus" 427 | } 428 | }, 429 | "node_modules/flat": { 430 | "version": "5.0.2", 431 | "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", 432 | "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", 433 | "dev": true, 434 | "bin": { 435 | "flat": "cli.js" 436 | } 437 | }, 438 | "node_modules/fs.realpath": { 439 | "version": "1.0.0", 440 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 441 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 442 | "dev": true 443 | }, 444 | "node_modules/fsevents": { 445 | "version": "2.3.2", 446 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 447 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 448 | "dev": true, 449 | "hasInstallScript": true, 450 | "optional": true, 451 | "os": [ 452 | "darwin" 453 | ], 454 | "engines": { 455 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 456 | } 457 | }, 458 | "node_modules/get-caller-file": { 459 | "version": "2.0.5", 460 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 461 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 462 | "dev": true, 463 | "engines": { 464 | "node": "6.* || 8.* || >= 10.*" 465 | } 466 | }, 467 | "node_modules/glob": { 468 | "version": "8.1.0", 469 | "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", 470 | "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", 471 | "dev": true, 472 | "dependencies": { 473 | "fs.realpath": "^1.0.0", 474 | "inflight": "^1.0.4", 475 | "inherits": "2", 476 | "minimatch": "^5.0.1", 477 | "once": "^1.3.0" 478 | }, 479 | "engines": { 480 | "node": ">=12" 481 | }, 482 | "funding": { 483 | "url": "https://github.com/sponsors/isaacs" 484 | } 485 | }, 486 | "node_modules/glob-parent": { 487 | "version": "5.1.2", 488 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 489 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 490 | "dev": true, 491 | "dependencies": { 492 | "is-glob": "^4.0.1" 493 | }, 494 | "engines": { 495 | "node": ">= 6" 496 | } 497 | }, 498 | "node_modules/has-flag": { 499 | "version": "4.0.0", 500 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 501 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 502 | "dev": true, 503 | "engines": { 504 | "node": ">=8" 505 | } 506 | }, 507 | "node_modules/he": { 508 | "version": "1.2.0", 509 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 510 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 511 | "dev": true, 512 | "bin": { 513 | "he": "bin/he" 514 | } 515 | }, 516 | "node_modules/inflight": { 517 | "version": "1.0.6", 518 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 519 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 520 | "dev": true, 521 | "dependencies": { 522 | "once": "^1.3.0", 523 | "wrappy": "1" 524 | } 525 | }, 526 | "node_modules/inherits": { 527 | "version": "2.0.4", 528 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 529 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 530 | "dev": true 531 | }, 532 | "node_modules/is-binary-path": { 533 | "version": "2.1.0", 534 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 535 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 536 | "dev": true, 537 | "dependencies": { 538 | "binary-extensions": "^2.0.0" 539 | }, 540 | "engines": { 541 | "node": ">=8" 542 | } 543 | }, 544 | "node_modules/is-extglob": { 545 | "version": "2.1.1", 546 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 547 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 548 | "dev": true, 549 | "engines": { 550 | "node": ">=0.10.0" 551 | } 552 | }, 553 | "node_modules/is-fullwidth-code-point": { 554 | "version": "3.0.0", 555 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 556 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 557 | "dev": true, 558 | "engines": { 559 | "node": ">=8" 560 | } 561 | }, 562 | "node_modules/is-glob": { 563 | "version": "4.0.3", 564 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 565 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 566 | "dev": true, 567 | "dependencies": { 568 | "is-extglob": "^2.1.1" 569 | }, 570 | "engines": { 571 | "node": ">=0.10.0" 572 | } 573 | }, 574 | "node_modules/is-number": { 575 | "version": "7.0.0", 576 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 577 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 578 | "dev": true, 579 | "engines": { 580 | "node": ">=0.12.0" 581 | } 582 | }, 583 | "node_modules/is-plain-obj": { 584 | "version": "2.1.0", 585 | "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", 586 | "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", 587 | "dev": true, 588 | "engines": { 589 | "node": ">=8" 590 | } 591 | }, 592 | "node_modules/is-unicode-supported": { 593 | "version": "0.1.0", 594 | "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", 595 | "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", 596 | "dev": true, 597 | "engines": { 598 | "node": ">=10" 599 | }, 600 | "funding": { 601 | "url": "https://github.com/sponsors/sindresorhus" 602 | } 603 | }, 604 | "node_modules/isarray": { 605 | "version": "0.0.1", 606 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 607 | "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", 608 | "dev": true 609 | }, 610 | "node_modules/js-yaml": { 611 | "version": "4.1.0", 612 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 613 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 614 | "dev": true, 615 | "dependencies": { 616 | "argparse": "^2.0.1" 617 | }, 618 | "bin": { 619 | "js-yaml": "bin/js-yaml.js" 620 | } 621 | }, 622 | "node_modules/just-extend": { 623 | "version": "4.2.1", 624 | "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", 625 | "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", 626 | "dev": true 627 | }, 628 | "node_modules/locate-path": { 629 | "version": "6.0.0", 630 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 631 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 632 | "dev": true, 633 | "dependencies": { 634 | "p-locate": "^5.0.0" 635 | }, 636 | "engines": { 637 | "node": ">=10" 638 | }, 639 | "funding": { 640 | "url": "https://github.com/sponsors/sindresorhus" 641 | } 642 | }, 643 | "node_modules/lodash": { 644 | "version": "4.17.21", 645 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 646 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 647 | "dev": true 648 | }, 649 | "node_modules/lodash.clonedeep": { 650 | "version": "4.5.0", 651 | "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", 652 | "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" 653 | }, 654 | "node_modules/log-symbols": { 655 | "version": "4.1.0", 656 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", 657 | "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", 658 | "dev": true, 659 | "dependencies": { 660 | "chalk": "^4.1.0", 661 | "is-unicode-supported": "^0.1.0" 662 | }, 663 | "engines": { 664 | "node": ">=10" 665 | }, 666 | "funding": { 667 | "url": "https://github.com/sponsors/sindresorhus" 668 | } 669 | }, 670 | "node_modules/lolex": { 671 | "version": "4.2.0", 672 | "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", 673 | "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", 674 | "dev": true 675 | }, 676 | "node_modules/lru-cache": { 677 | "version": "11.0.1", 678 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", 679 | "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", 680 | "engines": { 681 | "node": "20 || >=22" 682 | } 683 | }, 684 | "node_modules/minimatch": { 685 | "version": "5.0.1", 686 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", 687 | "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", 688 | "dev": true, 689 | "dependencies": { 690 | "brace-expansion": "^2.0.1" 691 | }, 692 | "engines": { 693 | "node": ">=10" 694 | } 695 | }, 696 | "node_modules/mocha": { 697 | "version": "10.4.0", 698 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", 699 | "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", 700 | "dev": true, 701 | "dependencies": { 702 | "ansi-colors": "4.1.1", 703 | "browser-stdout": "1.3.1", 704 | "chokidar": "3.5.3", 705 | "debug": "4.3.4", 706 | "diff": "5.0.0", 707 | "escape-string-regexp": "4.0.0", 708 | "find-up": "5.0.0", 709 | "glob": "8.1.0", 710 | "he": "1.2.0", 711 | "js-yaml": "4.1.0", 712 | "log-symbols": "4.1.0", 713 | "minimatch": "5.0.1", 714 | "ms": "2.1.3", 715 | "serialize-javascript": "6.0.0", 716 | "strip-json-comments": "3.1.1", 717 | "supports-color": "8.1.1", 718 | "workerpool": "6.2.1", 719 | "yargs": "16.2.0", 720 | "yargs-parser": "20.2.4", 721 | "yargs-unparser": "2.0.0" 722 | }, 723 | "bin": { 724 | "_mocha": "bin/_mocha", 725 | "mocha": "bin/mocha.js" 726 | }, 727 | "engines": { 728 | "node": ">= 14.0.0" 729 | } 730 | }, 731 | "node_modules/ms": { 732 | "version": "2.1.3", 733 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 734 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 735 | "dev": true 736 | }, 737 | "node_modules/nise": { 738 | "version": "1.5.3", 739 | "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", 740 | "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", 741 | "dev": true, 742 | "dependencies": { 743 | "@sinonjs/formatio": "^3.2.1", 744 | "@sinonjs/text-encoding": "^0.7.1", 745 | "just-extend": "^4.0.2", 746 | "lolex": "^5.0.1", 747 | "path-to-regexp": "^1.7.0" 748 | } 749 | }, 750 | "node_modules/nise/node_modules/lolex": { 751 | "version": "5.1.2", 752 | "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", 753 | "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", 754 | "dev": true, 755 | "dependencies": { 756 | "@sinonjs/commons": "^1.7.0" 757 | } 758 | }, 759 | "node_modules/normalize-path": { 760 | "version": "3.0.0", 761 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 762 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 763 | "dev": true, 764 | "engines": { 765 | "node": ">=0.10.0" 766 | } 767 | }, 768 | "node_modules/once": { 769 | "version": "1.4.0", 770 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 771 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 772 | "dev": true, 773 | "dependencies": { 774 | "wrappy": "1" 775 | } 776 | }, 777 | "node_modules/p-limit": { 778 | "version": "3.1.0", 779 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 780 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 781 | "dev": true, 782 | "dependencies": { 783 | "yocto-queue": "^0.1.0" 784 | }, 785 | "engines": { 786 | "node": ">=10" 787 | }, 788 | "funding": { 789 | "url": "https://github.com/sponsors/sindresorhus" 790 | } 791 | }, 792 | "node_modules/p-locate": { 793 | "version": "5.0.0", 794 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 795 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 796 | "dev": true, 797 | "dependencies": { 798 | "p-limit": "^3.0.2" 799 | }, 800 | "engines": { 801 | "node": ">=10" 802 | }, 803 | "funding": { 804 | "url": "https://github.com/sponsors/sindresorhus" 805 | } 806 | }, 807 | "node_modules/path-exists": { 808 | "version": "4.0.0", 809 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 810 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 811 | "dev": true, 812 | "engines": { 813 | "node": ">=8" 814 | } 815 | }, 816 | "node_modules/path-to-regexp": { 817 | "version": "1.8.0", 818 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", 819 | "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", 820 | "dev": true, 821 | "dependencies": { 822 | "isarray": "0.0.1" 823 | } 824 | }, 825 | "node_modules/picomatch": { 826 | "version": "2.3.1", 827 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 828 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 829 | "dev": true, 830 | "engines": { 831 | "node": ">=8.6" 832 | }, 833 | "funding": { 834 | "url": "https://github.com/sponsors/jonschlinkert" 835 | } 836 | }, 837 | "node_modules/randombytes": { 838 | "version": "2.1.0", 839 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 840 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 841 | "dev": true, 842 | "dependencies": { 843 | "safe-buffer": "^5.1.0" 844 | } 845 | }, 846 | "node_modules/readdirp": { 847 | "version": "3.6.0", 848 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 849 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 850 | "dev": true, 851 | "dependencies": { 852 | "picomatch": "^2.2.1" 853 | }, 854 | "engines": { 855 | "node": ">=8.10.0" 856 | } 857 | }, 858 | "node_modules/require-directory": { 859 | "version": "2.1.1", 860 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 861 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 862 | "dev": true, 863 | "engines": { 864 | "node": ">=0.10.0" 865 | } 866 | }, 867 | "node_modules/safe-buffer": { 868 | "version": "5.2.1", 869 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 870 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 871 | "dev": true, 872 | "funding": [ 873 | { 874 | "type": "github", 875 | "url": "https://github.com/sponsors/feross" 876 | }, 877 | { 878 | "type": "patreon", 879 | "url": "https://www.patreon.com/feross" 880 | }, 881 | { 882 | "type": "consulting", 883 | "url": "https://feross.org/support" 884 | } 885 | ] 886 | }, 887 | "node_modules/serialize-javascript": { 888 | "version": "6.0.0", 889 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", 890 | "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", 891 | "dev": true, 892 | "dependencies": { 893 | "randombytes": "^2.1.0" 894 | } 895 | }, 896 | "node_modules/sinon": { 897 | "version": "7.5.0", 898 | "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz", 899 | "integrity": "sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q==", 900 | "dev": true, 901 | "dependencies": { 902 | "@sinonjs/commons": "^1.4.0", 903 | "@sinonjs/formatio": "^3.2.1", 904 | "@sinonjs/samsam": "^3.3.3", 905 | "diff": "^3.5.0", 906 | "lolex": "^4.2.0", 907 | "nise": "^1.5.2", 908 | "supports-color": "^5.5.0" 909 | } 910 | }, 911 | "node_modules/sinon/node_modules/diff": { 912 | "version": "3.5.0", 913 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 914 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 915 | "dev": true, 916 | "engines": { 917 | "node": ">=0.3.1" 918 | } 919 | }, 920 | "node_modules/sinon/node_modules/has-flag": { 921 | "version": "3.0.0", 922 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 923 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 924 | "dev": true, 925 | "engines": { 926 | "node": ">=4" 927 | } 928 | }, 929 | "node_modules/sinon/node_modules/supports-color": { 930 | "version": "5.5.0", 931 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 932 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 933 | "dev": true, 934 | "dependencies": { 935 | "has-flag": "^3.0.0" 936 | }, 937 | "engines": { 938 | "node": ">=4" 939 | } 940 | }, 941 | "node_modules/string-width": { 942 | "version": "4.2.3", 943 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 944 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 945 | "dev": true, 946 | "dependencies": { 947 | "emoji-regex": "^8.0.0", 948 | "is-fullwidth-code-point": "^3.0.0", 949 | "strip-ansi": "^6.0.1" 950 | }, 951 | "engines": { 952 | "node": ">=8" 953 | } 954 | }, 955 | "node_modules/strip-ansi": { 956 | "version": "6.0.1", 957 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 958 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 959 | "dev": true, 960 | "dependencies": { 961 | "ansi-regex": "^5.0.1" 962 | }, 963 | "engines": { 964 | "node": ">=8" 965 | } 966 | }, 967 | "node_modules/strip-json-comments": { 968 | "version": "3.1.1", 969 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 970 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 971 | "dev": true, 972 | "engines": { 973 | "node": ">=8" 974 | }, 975 | "funding": { 976 | "url": "https://github.com/sponsors/sindresorhus" 977 | } 978 | }, 979 | "node_modules/supports-color": { 980 | "version": "8.1.1", 981 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", 982 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", 983 | "dev": true, 984 | "dependencies": { 985 | "has-flag": "^4.0.0" 986 | }, 987 | "engines": { 988 | "node": ">=10" 989 | }, 990 | "funding": { 991 | "url": "https://github.com/chalk/supports-color?sponsor=1" 992 | } 993 | }, 994 | "node_modules/to-regex-range": { 995 | "version": "5.0.1", 996 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 997 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 998 | "dev": true, 999 | "dependencies": { 1000 | "is-number": "^7.0.0" 1001 | }, 1002 | "engines": { 1003 | "node": ">=8.0" 1004 | } 1005 | }, 1006 | "node_modules/type-detect": { 1007 | "version": "1.0.0", 1008 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", 1009 | "integrity": "sha512-f9Uv6ezcpvCQjJU0Zqbg+65qdcszv3qUQsZfjdRbWiZ7AMenrX1u0lNk9EoWWX6e1F+NULyg27mtdeZ5WhpljA==", 1010 | "dev": true, 1011 | "engines": { 1012 | "node": "*" 1013 | } 1014 | }, 1015 | "node_modules/typescript": { 1016 | "version": "4.9.5", 1017 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", 1018 | "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", 1019 | "dev": true, 1020 | "bin": { 1021 | "tsc": "bin/tsc", 1022 | "tsserver": "bin/tsserver" 1023 | }, 1024 | "engines": { 1025 | "node": ">=4.2.0" 1026 | } 1027 | }, 1028 | "node_modules/workerpool": { 1029 | "version": "6.2.1", 1030 | "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", 1031 | "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", 1032 | "dev": true 1033 | }, 1034 | "node_modules/wrap-ansi": { 1035 | "version": "7.0.0", 1036 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 1037 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 1038 | "dev": true, 1039 | "dependencies": { 1040 | "ansi-styles": "^4.0.0", 1041 | "string-width": "^4.1.0", 1042 | "strip-ansi": "^6.0.0" 1043 | }, 1044 | "engines": { 1045 | "node": ">=10" 1046 | }, 1047 | "funding": { 1048 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1049 | } 1050 | }, 1051 | "node_modules/wrappy": { 1052 | "version": "1.0.2", 1053 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1054 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 1055 | "dev": true 1056 | }, 1057 | "node_modules/y18n": { 1058 | "version": "5.0.8", 1059 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 1060 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 1061 | "dev": true, 1062 | "engines": { 1063 | "node": ">=10" 1064 | } 1065 | }, 1066 | "node_modules/yargs": { 1067 | "version": "16.2.0", 1068 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", 1069 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", 1070 | "dev": true, 1071 | "dependencies": { 1072 | "cliui": "^7.0.2", 1073 | "escalade": "^3.1.1", 1074 | "get-caller-file": "^2.0.5", 1075 | "require-directory": "^2.1.1", 1076 | "string-width": "^4.2.0", 1077 | "y18n": "^5.0.5", 1078 | "yargs-parser": "^20.2.2" 1079 | }, 1080 | "engines": { 1081 | "node": ">=10" 1082 | } 1083 | }, 1084 | "node_modules/yargs-parser": { 1085 | "version": "20.2.4", 1086 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", 1087 | "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", 1088 | "dev": true, 1089 | "engines": { 1090 | "node": ">=10" 1091 | } 1092 | }, 1093 | "node_modules/yargs-unparser": { 1094 | "version": "2.0.0", 1095 | "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", 1096 | "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", 1097 | "dev": true, 1098 | "dependencies": { 1099 | "camelcase": "^6.0.0", 1100 | "decamelize": "^4.0.0", 1101 | "flat": "^5.0.2", 1102 | "is-plain-obj": "^2.1.0" 1103 | }, 1104 | "engines": { 1105 | "node": ">=10" 1106 | } 1107 | }, 1108 | "node_modules/yocto-queue": { 1109 | "version": "0.1.0", 1110 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 1111 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 1112 | "dev": true, 1113 | "engines": { 1114 | "node": ">=10" 1115 | }, 1116 | "funding": { 1117 | "url": "https://github.com/sponsors/sindresorhus" 1118 | } 1119 | } 1120 | } 1121 | } 1122 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lru-memoizer", 3 | "description": "Memoize functions results using an lru-cache.", 4 | "version": "3.0.0", 5 | "author": "José F. Romaniello (http://joseoncode.com)", 6 | "repository": { 7 | "url": "git://github.com/jfromaniello/lru-memoizer.git" 8 | }, 9 | "keywords": [ 10 | "cache", 11 | "memoize", 12 | "lru" 13 | ], 14 | "main": "./lib/index.js", 15 | "types": "./lib/index.d.ts", 16 | "scripts": { 17 | "prepare": "tsc", 18 | "test": "npm run prepare && mocha" 19 | }, 20 | "dependencies": { 21 | "lodash.clonedeep": "^4.5.0", 22 | "lru-cache": "^11.0.1" 23 | }, 24 | "license": "MIT", 25 | "devDependencies": { 26 | "@types/lodash.clonedeep": "^4.5.9", 27 | "@types/node": "^12.0.10", 28 | "chai": "^3.5.0", 29 | "mocha": "^10.4.0", 30 | "sinon": "^7.3.2", 31 | "typescript": "^4.9.5" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/async.ts: -------------------------------------------------------------------------------- 1 | import { LRUCache } from 'lru-cache'; 2 | import { EventEmitter } from 'events'; 3 | import cloneDeep from 'lodash.clonedeep'; 4 | import { deepFreeze } from './freeze'; 5 | import { syncMemoizer } from './sync'; 6 | import { 7 | INodeStyleCallBack as CB, 8 | ResultBase, 9 | IParamsBase0, 10 | IParamsBase1, 11 | IParamsBase2, 12 | IParamsBase3, 13 | IParamsBase4, 14 | IParamsBase5, 15 | IParamsBase6, 16 | IParamsBasePlus, 17 | } from './util'; 18 | 19 | type Callback = (err?: any, ...args: any[]) => void; 20 | 21 | type PendingLoad = { 22 | queue: Callback[]; 23 | expiresAt: number; 24 | }; 25 | 26 | export interface IMemoized extends ResultBase { 27 | (cb: CB): void; 28 | (a1: T1, cb: CB): void; 29 | (a1: T1, a2: T2, cb: CB): void; 30 | (a1: T1, a2: T2, a3: T3, cb: CB): void; 31 | (a1: T1, a2: T2, a3: T3, a4: T4, cb: CB): void; 32 | (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, cb: CB): void; 33 | (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6, cb: CB): void; 34 | } 35 | 36 | interface IMemoizableFunction0 { 37 | (cb: CB): void; 38 | } 39 | interface IMemoizableFunction1 { 40 | (a1: T1, cb: CB): void; 41 | } 42 | interface IMemoizableFunction2 { 43 | (a1: T1, a2: T2, cb: CB): void; 44 | } 45 | interface IMemoizableFunction3 { 46 | (a1: T1, a2: T2, a3: T3, cb: CB): void; 47 | } 48 | interface IMemoizableFunction4 { 49 | (a1: T1, a2: T2, a3: T3, a4: T4, cb: CB): void; 50 | } 51 | interface IMemoizableFunction5 { 52 | (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, cb: CB): void; 53 | } 54 | interface IMemoizableFunction6 { 55 | (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6, cb: CB): void; 56 | } 57 | interface IMemoizableFunctionPlus { 58 | (...rest: any[]): void; 59 | } 60 | 61 | type AsyncParamsPlus = IParamsBasePlus & { 62 | load: IMemoizableFunctionPlus; 63 | } 64 | type AsyncParams0 = IParamsBase0 & { 65 | load: IMemoizableFunction0; 66 | } 67 | type AsyncParams1 = IParamsBase1 & { 68 | load: IMemoizableFunction1; 69 | } 70 | type AsyncParams2 = IParamsBase2 & { 71 | load: IMemoizableFunction2; 72 | } 73 | type AsyncParams3 74 | = IParamsBase3 & { 75 | load: IMemoizableFunction3; 76 | } 77 | type AsyncParams4 78 | = IParamsBase4 & { 79 | load: IMemoizableFunction4; 80 | } 81 | type AsyncParams5 82 | = IParamsBase5 & { 83 | load: IMemoizableFunction5; 84 | } 85 | type AsyncParams6 86 | = IParamsBase6 & { 87 | /** 88 | * The function that loads the resource when is not in the cache. 89 | */ 90 | load: IMemoizableFunction6; 91 | } 92 | 93 | function asyncMemoizer( 94 | options: AsyncParams0 95 | ): IMemoized; 96 | function asyncMemoizer( 97 | options: AsyncParams1 98 | ): IMemoized; 99 | function asyncMemoizer( 100 | options: AsyncParams2 101 | ): IMemoized; 102 | function asyncMemoizer( 103 | options: AsyncParams3 104 | ): IMemoized; 105 | function asyncMemoizer( 106 | options: AsyncParams4 107 | ): IMemoized; 108 | function asyncMemoizer( 109 | options: AsyncParams5 110 | ): IMemoized; 111 | function asyncMemoizer( 112 | options: AsyncParams6 113 | ): IMemoized; 114 | function asyncMemoizer( 115 | options: AsyncParamsPlus 116 | ): IMemoized { 117 | const cache = new LRUCache(options); 118 | const load = options.load; 119 | const hash = options.hash; 120 | const bypass = options.bypass; 121 | const itemTTL = options.itemTTL; 122 | const freeze = options.freeze; 123 | const clone = options.clone; 124 | const queueTTL = options.queueTTL || 1000; 125 | const loading = new Map(); 126 | const emitter = new EventEmitter(); 127 | 128 | const memoizerMethods = Object.assign({ 129 | del, 130 | reset: () => cache.clear(), 131 | keys: () => [...cache.keys()], 132 | on: emitter.on.bind(emitter), 133 | once: emitter.once.bind(emitter) 134 | }, options); 135 | 136 | if (options.disable) { 137 | return Object.assign(load, memoizerMethods); 138 | } 139 | 140 | function del(...args: any[]) { 141 | const key = hash(...args); 142 | cache.delete(key); 143 | } 144 | 145 | function add(key: string, parameters: any[], result: any[]) { 146 | if (freeze) { 147 | result.forEach(deepFreeze); 148 | } 149 | 150 | if (itemTTL) { 151 | cache.set(key, result, { ttl: itemTTL(...parameters.concat(result)) }); 152 | } else { 153 | cache.set(key, result); 154 | } 155 | } 156 | 157 | function runCallbacks(callbacks: Callback[], args: any[]) { 158 | for (const callback of callbacks) { 159 | // Simulate async call when returning from cache 160 | // and yield between callback resolution 161 | if (clone) { 162 | setImmediate(callback, ...args.map(cloneDeep)); 163 | } else { 164 | setImmediate(callback, ...args); 165 | } 166 | } 167 | } 168 | 169 | function emit(event: string, ...parameters: any[]) { 170 | emitter.emit(event, ...parameters); 171 | } 172 | 173 | function memoizedFunction(...args: any[]) { 174 | const parameters = args.slice(0, -1); 175 | const callback: Callback = args.slice(-1).pop(); 176 | let key: string; 177 | 178 | if (bypass && bypass(...parameters)) { 179 | emit('miss', ...parameters); 180 | return load(...args); 181 | } 182 | 183 | if (parameters.length === 0 && !hash) { 184 | //the load function only receives callback. 185 | key = '_'; 186 | } else { 187 | key = hash(...parameters); 188 | } 189 | 190 | const fromCache = cache.get(key); 191 | if (fromCache) { 192 | emit('hit', ...parameters); 193 | // found, invoke callback 194 | return runCallbacks([callback], [null].concat(fromCache)); 195 | } 196 | 197 | const pendingLoad = loading.get(key); 198 | if (pendingLoad && pendingLoad.expiresAt > Date.now()) { 199 | // request already in progress, queue and return 200 | pendingLoad.queue.push(callback); 201 | emit('queue', ...parameters); 202 | return; 203 | } 204 | 205 | emit('miss', ...parameters); 206 | 207 | const started = Date.now(); 208 | 209 | // no pending request or not resolved before expiration 210 | // create a new queue and invoke load 211 | const queue = [callback]; 212 | loading.set(key, { 213 | queue, 214 | expiresAt: started + queueTTL 215 | }); 216 | 217 | const loadHandler = (...args: any[]) => { 218 | const err = args[0]; 219 | if (!err) { 220 | add(key, parameters, args.slice(1)); 221 | } 222 | 223 | // this can potentially delete a different queue than `queue` if 224 | // this callback was called after expiration. 225 | // that will only cause a new call to be performed and a new queue to be 226 | // created 227 | loading.delete(key); 228 | 229 | emit('loaded', Date.now() - started, ...parameters); 230 | runCallbacks(queue, args); 231 | }; 232 | 233 | load(...parameters, loadHandler); 234 | }; 235 | 236 | return Object.assign(memoizedFunction, memoizerMethods); 237 | } 238 | 239 | asyncMemoizer.sync = syncMemoizer; 240 | 241 | export { asyncMemoizer }; 242 | -------------------------------------------------------------------------------- /src/freeze.ts: -------------------------------------------------------------------------------- 1 | // From https://raw.githubusercontent.com/nikoskalogridis/deep-freeze/fb921b32064dce1645197be2bf975fe0385450b0/index.js 2 | // which is sadly, no longer maintained 3 | 4 | export function deepFreeze (o: any) { 5 | if (o) { 6 | Object.freeze(o); 7 | 8 | Object.getOwnPropertyNames(o).forEach(function (prop) { 9 | if (o.hasOwnProperty(prop) 10 | && o[prop] !== null 11 | && (typeof o[prop] === 'object' || typeof o[prop] === 'function') 12 | && (o[prop].constructor !== Buffer) 13 | && !Object.isFrozen(o[prop])) { 14 | deepFreeze(o[prop]); 15 | } 16 | }); 17 | } 18 | 19 | return o; 20 | }; 21 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { asyncMemoizer } from './async'; 2 | 3 | export = asyncMemoizer; 4 | -------------------------------------------------------------------------------- /src/sync.ts: -------------------------------------------------------------------------------- 1 | import { LRUCache } from 'lru-cache'; 2 | import { EventEmitter } from 'events'; 3 | import deepClone from 'lodash.clonedeep'; 4 | import { deepFreeze } from './freeze'; 5 | import { 6 | ResultBase, 7 | IParamsBase0, 8 | IParamsBase1, 9 | IParamsBase2, 10 | IParamsBase3, 11 | IParamsBase4, 12 | IParamsBase5, 13 | IParamsBase6, 14 | IParamsBasePlus, 15 | } from './util'; 16 | 17 | interface IMemoizedSync extends ResultBase { 18 | (arg1: T1): TResult; 19 | (arg1: T1, arg2: T2): TResult; 20 | (arg1: T1, arg2: T2, arg3: T3): TResult; 21 | (arg1: T1, arg2: T2, arg3: T3, arg4: T4): TResult; 22 | (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5): TResult; 23 | (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, arg6: T6): TResult; 24 | } 25 | 26 | interface IMemoizableFunctionSync0 { 27 | (): TResult; 28 | } 29 | interface IMemoizableFunctionSync1 { 30 | (arg1: T1): TResult; 31 | } 32 | interface IMemoizableFunctionSync2 { 33 | (arg1: T1, arg2: T2): TResult; 34 | } 35 | interface IMemoizableFunctionSync3 { 36 | (arg1: T1, arg2: T2, arg3: T3): TResult; 37 | } 38 | interface IMemoizableFunctionSync4 { 39 | (arg1: T1, arg2: T2, arg3: T3, arg4: T4): TResult; 40 | } 41 | interface IMemoizableFunctionSync5 { 42 | (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5): TResult; 43 | } 44 | interface IMemoizableFunctionSync6 { 45 | (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6): TResult; 46 | } 47 | interface IMemoizableFunctionSyncPlus { 48 | (...args: any[]): TResult; 49 | } 50 | 51 | export type SyncParams0 = IParamsBase0 & { 52 | load: IMemoizableFunctionSync0; 53 | } 54 | export type SyncParams1 = IParamsBase1 & { 55 | load: IMemoizableFunctionSync1; 56 | } 57 | export type SyncParams2 58 | = IParamsBase2 & { 59 | load: IMemoizableFunctionSync2; 60 | } 61 | export type SyncParams3 62 | = IParamsBase3 & { 63 | load: IMemoizableFunctionSync3; 64 | } 65 | export type SyncParams4 66 | = IParamsBase4 & { 67 | load: IMemoizableFunctionSync4; 68 | } 69 | export type SyncParams5 70 | = IParamsBase5 & { 71 | load: IMemoizableFunctionSync5; 72 | } 73 | export type SyncParams6 74 | = IParamsBase6 & { 75 | load: IMemoizableFunctionSync6; 76 | } 77 | export type SyncParamsPlus = IParamsBasePlus & { 78 | load: IMemoizableFunctionSyncPlus; 79 | } 80 | export function syncMemoizer( 81 | options: SyncParams0 82 | ): IMemoizedSync; 83 | export function syncMemoizer( 84 | options: SyncParams1 85 | ): IMemoizedSync; 86 | export function syncMemoizer( 87 | options: SyncParams2 88 | ): IMemoizedSync; 89 | export function syncMemoizer( 90 | options: SyncParams3 91 | ): IMemoizedSync; 92 | export function syncMemoizer( 93 | options: SyncParams4 94 | ): IMemoizedSync; 95 | export function syncMemoizer( 96 | options: SyncParams5 97 | ): IMemoizedSync; 98 | export function syncMemoizer( 99 | options: SyncParams6 100 | ): IMemoizedSync; 101 | export function syncMemoizer( 102 | options: SyncParamsPlus 103 | ): IMemoizedSync { 104 | const cache = new LRUCache(options); 105 | const load = options.load; 106 | const hash = options.hash; 107 | const bypass = options.bypass; 108 | const itemTTL = options.itemTTL; 109 | const freeze = options.freeze; 110 | const clone = options.clone; 111 | const emitter = new EventEmitter(); 112 | 113 | const defaultResult = Object.assign({ 114 | del, 115 | reset: () => cache.clear(), 116 | keys: () => [...cache.keys()], 117 | on: emitter.on.bind(emitter), 118 | once: emitter.once.bind(emitter), 119 | }, options); 120 | 121 | if (options.disable) { 122 | return Object.assign(load, defaultResult); 123 | } 124 | 125 | function del() { 126 | const key = hash(...arguments); 127 | cache.delete(key); 128 | } 129 | 130 | function emit(event: string, ...parameters: any[]) { 131 | emitter.emit(event, ...parameters); 132 | } 133 | 134 | function isPromise(result: any): boolean { 135 | // detect native, bluebird, A+ promises 136 | return result && result.then && typeof result.then === 'function'; 137 | } 138 | 139 | function processResult(result: any) { 140 | let res = result; 141 | 142 | if (clone) { 143 | if (isPromise(res)) { 144 | res = res.then(deepClone); 145 | } else { 146 | res = deepClone(res); 147 | } 148 | } 149 | 150 | if (freeze) { 151 | if (isPromise(res)) { 152 | res = res.then(deepFreeze); 153 | } else { 154 | deepFreeze(res); 155 | } 156 | } 157 | 158 | return res; 159 | } 160 | 161 | const result: IMemoizableFunctionSync6 = function ( 162 | ...args: any[] 163 | ) { 164 | if (bypass && bypass(...args)) { 165 | emit('miss', ...args); 166 | return load(...args); 167 | } 168 | 169 | var key = hash(...args); 170 | 171 | var fromCache = cache.get(key); 172 | 173 | if (fromCache) { 174 | emit('hit', ...args); 175 | 176 | return processResult(fromCache); 177 | } 178 | 179 | emit('miss', ...args); 180 | const result = load(...args); 181 | 182 | if (itemTTL) { 183 | // @ts-ignore 184 | cache.set(key, result, { ttl: itemTTL(...args.concat([result])) }); 185 | } else { 186 | cache.set(key, result); 187 | } 188 | 189 | return processResult(result); 190 | }; 191 | 192 | return Object.assign(result, defaultResult) as any; 193 | } 194 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import { LRUCache } from 'lru-cache'; 2 | export type Listener = (...as: any[]) => void; 3 | export type INodeStyleCallBack = ( 4 | err: Error | null, 5 | result?: Success 6 | ) => void; 7 | 8 | export interface ResultBase { 9 | /** 10 | * Returns all keys in the cache. 11 | */ 12 | keys: () => string[]; 13 | 14 | /** 15 | * Clear the cache. 16 | */ 17 | reset: () => void; 18 | 19 | /** 20 | * Delete an item given the parameters. 21 | */ 22 | del: ( 23 | a1?: T1, 24 | a2?: T2, 25 | a3?: T3, 26 | a4?: T4, 27 | a5?: T5, 28 | a6?: T6 29 | ) => void; 30 | on(event: 'hit', handler: Listener): void; 31 | on(event: 'miss', handler: Listener): void; 32 | on(event: 'queue', handler: Listener): void; 33 | } 34 | 35 | export interface IHashingFunction0 { 36 | (): string; 37 | } 38 | export interface IHashingFunction1 { 39 | (a1: T1): string; 40 | } 41 | export interface IHashingFunction2 { 42 | (a1: T1, a2: T2): string; 43 | } 44 | export interface IHashingFunction3 { 45 | (a1: T1, a2: T2, a3: T3): string; 46 | } 47 | export interface IHashingFunction4 { 48 | (a1: T1, a2: T2, a3: T3, a4: T4): string; 49 | } 50 | export interface IHashingFunction5 { 51 | (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5): string; 52 | } 53 | export interface IHashingFunction6 { 54 | (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6): string; 55 | } 56 | export interface IHashingFunctionPlus<> { 57 | (...rest: any[]): string; 58 | } 59 | 60 | export interface IBypassFunction0 { 61 | (): boolean; 62 | } 63 | export interface IBypassFunction1 { 64 | (a1: T1): boolean; 65 | } 66 | export interface IBypassFunction2 { 67 | (a1: T1, a2: T2): boolean; 68 | } 69 | export interface IBypassFunction3 { 70 | (a1: T1, a2: T2, a3: T3): boolean; 71 | } 72 | export interface IBypassFunction4 { 73 | (a1: T1, a2: T2, a3: T3, a4: T4): boolean; 74 | } 75 | export interface IBypassFunction5 { 76 | (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5): boolean; 77 | } 78 | export interface IBypassFunction6 { 79 | (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6): boolean; 80 | } 81 | export interface IBypassFunctionPlus { 82 | (...rest: any[]): boolean; 83 | } 84 | 85 | export interface ITTLFunction0 { 86 | (res: TResult): number; 87 | } 88 | export interface ITTLFunction1 { 89 | (a1: T1, res: TResult): number; 90 | } 91 | export interface ITTLFunction2 { 92 | (a1: T1, a2: T2, res: TResult): number; 93 | } 94 | export interface ITTLFunction3 { 95 | (a1: T1, a2: T2, a3: T3, res: TResult): number; 96 | } 97 | export interface ITTLFunction4 { 98 | (a1: T1, a2: T2, a3: T3, a4: T4, res: TResult): number; 99 | } 100 | export interface ITTLFunction5 { 101 | (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, res: TResult): number; 102 | } 103 | export interface ITTLFunction6 { 104 | (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5, a6: T6, res: TResult): number; 105 | } 106 | export interface ITTLFunctionPlus { 107 | (...rest: any[]): number; 108 | } 109 | 110 | export type IParamsBase0 = IParamsBaseCommons & { 111 | hash: IHashingFunction0; 112 | bypass?: IBypassFunction0; 113 | itemTTL?: ITTLFunction0; 114 | } 115 | export type IParamsBase1 = IParamsBaseCommons & { 116 | hash: IHashingFunction1; 117 | bypass?: IBypassFunction1; 118 | itemTTL?: ITTLFunction1; 119 | } 120 | export type IParamsBase2 = IParamsBaseCommons & { 121 | hash: IHashingFunction2; 122 | bypass?: IBypassFunction2; 123 | itemTTL?: ITTLFunction2; 124 | } 125 | export type IParamsBase3 = IParamsBaseCommons & { 126 | hash: IHashingFunction3; 127 | bypass?: IBypassFunction3; 128 | itemTTL?: ITTLFunction3; 129 | } 130 | export type IParamsBase4 131 | = IParamsBaseCommons & { 132 | hash: IHashingFunction4; 133 | bypass?: IBypassFunction4; 134 | itemTTL?: ITTLFunction4; 135 | } 136 | export type IParamsBase5 137 | = IParamsBaseCommons & { 138 | hash: IHashingFunction5; 139 | bypass?: IBypassFunction5; 140 | itemTTL?: ITTLFunction5; 141 | } 142 | export type IParamsBase6 143 | = IParamsBaseCommons & { 144 | /** 145 | * A function to generate the key of the cache. 146 | */ 147 | hash: IHashingFunction6; 148 | 149 | /** 150 | * Return true if the result should not be retrieved from the cache. 151 | */ 152 | bypass?: IBypassFunction6; 153 | 154 | /** 155 | * An optional function to indicate the ttl of an specific item. 156 | */ 157 | itemTTL?: ITTLFunction6; 158 | } 159 | export type IParamsBasePlus = IParamsBaseCommons & { 160 | hash: IHashingFunctionPlus; 161 | bypass?: IBypassFunctionPlus; 162 | itemTTL?: ITTLFunctionPlus; 163 | } 164 | type IParamsBaseCommons = LRUCache.Options & { 165 | /** 166 | * Indicates if the resource should be freezed. 167 | */ 168 | freeze?: boolean; 169 | 170 | /** 171 | * Indicates if the resource should be cloned before is returned. 172 | */ 173 | clone?: boolean; 174 | 175 | /** 176 | * Disable the cache and executes the load logic directly. 177 | */ 178 | disable?: boolean; 179 | 180 | /** 181 | * Do not queue requests if initial call is more than `queueTTL` milliseconds old. 182 | * Instead, invoke `load` again and create a new queue. 183 | * Defaults to 1000ms. 184 | */ 185 | queueTTL?: number; 186 | } 187 | -------------------------------------------------------------------------------- /test/lru-memoizer.bypass.test.js: -------------------------------------------------------------------------------- 1 | const memoizer = require('../lib/index.js'); 2 | const assert = require('chai').assert; 3 | 4 | describe('lru-memoizer (bypass)', function () { 5 | var loadTimes = 0, memoized; 6 | 7 | beforeEach(function () { 8 | loadTimes = 0; 9 | 10 | memoized = memoizer({ 11 | load: function (a, b, callback) { 12 | loadTimes++; 13 | callback(null, a + b); 14 | }, 15 | hash: function (a, b) { 16 | return a + '-' + b; 17 | }, 18 | bypass: function (a, b) { 19 | return a < b; 20 | }, 21 | max: 10 22 | }); 23 | 24 | 25 | }); 26 | 27 | it('should call the load function every time', function (done) { 28 | memoized(1, 2, function (err) { 29 | assert.isNull(err); 30 | assert.strictEqual(loadTimes, 1); 31 | memoized(1, 2, function (err) { 32 | assert.isNull(err); 33 | assert.strictEqual(loadTimes, 2); 34 | done(); 35 | }); 36 | }); 37 | }); 38 | }); 39 | 40 | -------------------------------------------------------------------------------- /test/lru-memoizer.clone.test.js: -------------------------------------------------------------------------------- 1 | const memoizer = require('./..'); 2 | const assert = require('chai').assert; 3 | 4 | describe('lru-memoizer (clone)', () => { 5 | let loadTimes = 0, memoized; 6 | 7 | beforeEach(() => { 8 | loadTimes = 0; 9 | 10 | memoized = memoizer({ 11 | load: (key, callback) => { 12 | loadTimes++; 13 | callback(null, { foo: key, buffer: Buffer.from('1234') }); 14 | }, 15 | hash: (key) => { 16 | return key; 17 | }, 18 | clone: true, 19 | max: 10 20 | }); 21 | }); 22 | 23 | it('should return a clone every time with the same cached structure', (done) => { 24 | memoized('bar', (err, r1) => { 25 | 26 | assert.isNull(err); 27 | assert.strictEqual(loadTimes, 1); 28 | assert.equal(r1.foo, 'bar'); 29 | r1.foo = 'bax'; 30 | 31 | memoized('bar', (err, r2) => { 32 | assert.isNull(err); 33 | 34 | assert.strictEqual(loadTimes, 1); 35 | assert.equal(r2.foo, 'bar'); 36 | assert.notStrictEqual(r1, r2); 37 | assert.notEqual(r1, r2); 38 | 39 | done(); 40 | }); 41 | }); 42 | }); 43 | 44 | }); 45 | 46 | -------------------------------------------------------------------------------- /test/lru-memoizer.disable.test.js: -------------------------------------------------------------------------------- 1 | const memoizer = require('./..'); 2 | const assert = require('chai').assert; 3 | 4 | describe('lru-memoizer (disabled)', function () { 5 | var loadTimes = 0, memoized; 6 | 7 | beforeEach(function () { 8 | loadTimes = 0; 9 | 10 | memoized = memoizer({ 11 | disable: true, 12 | load: function (a, b, callback) { 13 | loadTimes++; 14 | return setTimeout(function () { 15 | if (a === 0) { 16 | return callback(new Error('a cant be 0')); 17 | } 18 | callback(null, a+b); 19 | }, 10); 20 | }, 21 | hash: function (a, b) { 22 | return a + '-' + b; 23 | }, 24 | max: 10 25 | }); 26 | }); 27 | 28 | it('should call the load function every time', function (done) { 29 | memoized(1,2, function (err, result) { 30 | assert.isNull(err); 31 | assert.strictEqual(result, 3); 32 | assert.strictEqual(loadTimes, 1); 33 | memoized(1,2, function (err, result) { 34 | assert.isNull(err); 35 | assert.strictEqual(result, 3); 36 | assert.strictEqual(loadTimes, 2); 37 | done(); 38 | }); 39 | }); 40 | 41 | }); 42 | 43 | 44 | it('should expose hash function', function() { 45 | assert.equal(memoized.hash(1, 2), '1-2'); 46 | }); 47 | 48 | }); 49 | 50 | -------------------------------------------------------------------------------- /test/lru-memoizer.events.test.js: -------------------------------------------------------------------------------- 1 | const memoizer = require('./..'); 2 | const sinon = require('sinon'); 3 | 4 | describe('lru-memoizer (events)', function () { 5 | let memoized; 6 | let onMiss, onHit, onQueue; 7 | 8 | beforeEach(function () { 9 | loadTimes = 0; 10 | onMiss = sinon.stub(); 11 | onHit = sinon.stub(); 12 | onQueue = sinon.stub(); 13 | memoized = memoizer({ 14 | load: function (a, b, bypass, callback) { 15 | return setTimeout(function () { 16 | if (a === 0) { 17 | return callback(new Error('a cant be 0')); 18 | } 19 | callback(null, a+b); 20 | }, 10); 21 | }, 22 | hash: function (a, b) { 23 | return a + '-' + b; 24 | }, 25 | bypass: function(a, b, bypass) { 26 | return bypass; 27 | }, 28 | max: 10 29 | }); 30 | memoized.on('hit', onHit); 31 | memoized.on('miss', onMiss); 32 | memoized.on('queue', onQueue); 33 | }); 34 | 35 | describe('when the result is not in the cache', () => { 36 | beforeEach((done) => { 37 | memoized(1, 2, false, done); 38 | }); 39 | 40 | it('should not call onHit', () => { 41 | sinon.assert.notCalled(onHit); 42 | }); 43 | 44 | it('should not call onQueue', () => { 45 | sinon.assert.notCalled(onQueue); 46 | }); 47 | 48 | it('should call onMiss with the load arguments', () => { 49 | sinon.assert.calledOnce(onMiss); 50 | sinon.assert.calledWith(onMiss, 1, 2, false); 51 | }); 52 | }); 53 | 54 | describe('when the result is in the cache', () => { 55 | beforeEach((done) => { 56 | memoized(1,2, false, () => { 57 | onHit.reset(); 58 | onMiss.reset(); 59 | onQueue.reset(); 60 | memoized(1, 2, false, done); 61 | }); 62 | }); 63 | 64 | it('should call onHit with the load arguments', () => { 65 | sinon.assert.calledOnce(onHit); 66 | sinon.assert.calledWith(onHit, 1, 2, false); 67 | }); 68 | 69 | it('should not call onQueue', () => { 70 | sinon.assert.notCalled(onQueue); 71 | }); 72 | 73 | it('should not call onMiss', () => { 74 | sinon.assert.notCalled(onQueue); 75 | }); 76 | }); 77 | 78 | describe('when the cache is by passed', () => { 79 | beforeEach((done) => { 80 | memoized(1,2, false, () => { 81 | onHit.reset(); 82 | onMiss.reset(); 83 | onQueue.reset(); 84 | memoized(1, 2, true, done); 85 | }); 86 | }); 87 | 88 | it('should not call onHit', () => { 89 | sinon.assert.notCalled(onHit); 90 | }); 91 | 92 | it('should not call onQueue', () => { 93 | sinon.assert.notCalled(onQueue); 94 | }); 95 | 96 | it('should call onMiss with the load arguments', () => { 97 | sinon.assert.calledOnce(onMiss); 98 | sinon.assert.calledWith(onMiss, 1, 2, true); 99 | }); 100 | }); 101 | 102 | describe('when the result is pending', () => { 103 | beforeEach((done) => { 104 | let pending = 2; 105 | function onDone() { 106 | pending -= 1; 107 | if (pending === 0) { 108 | done(); 109 | } 110 | } 111 | memoized(1, 2, false, onDone); 112 | onHit.reset(); 113 | onMiss.reset(); 114 | onQueue.reset(); 115 | memoized(1, 2, false, onDone); 116 | }); 117 | 118 | it('should not call onHit', () => { 119 | sinon.assert.notCalled(onHit); 120 | }); 121 | 122 | it('should call onQueue with the load arguments', () => { 123 | sinon.assert.calledOnce(onQueue); 124 | sinon.assert.calledWith(onQueue, 1, 2, false); 125 | }); 126 | 127 | it('should not call onMiss', () => { 128 | sinon.assert.notCalled(onMiss); 129 | }); 130 | }); 131 | }); 132 | 133 | -------------------------------------------------------------------------------- /test/lru-memoizer.freeze.test.js: -------------------------------------------------------------------------------- 1 | const memoizer = require("./.."); 2 | const assert = require("chai").assert; 3 | 4 | describe("lru-memoizer (freeze)", function () { 5 | var loadTimes = 0, 6 | memoized; 7 | 8 | beforeEach(function () { 9 | loadTimes = 0; 10 | 11 | memoized = memoizer({ 12 | load: function (key, callback) { 13 | loadTimes++; 14 | callback(null, { foo: "bar", buffer: Buffer.from("1234") }); 15 | }, 16 | hash: function (key) { 17 | return key; 18 | }, 19 | freeze: true, 20 | max: 10 21 | }); 22 | }); 23 | 24 | it("should return a freeze every time with the same cached structure", function (done) { 25 | memoized("test", function (err, r1) { 26 | assert.isNull(err); 27 | assert.strictEqual(loadTimes, 1); 28 | assert.equal(r1.foo, "bar"); 29 | r1.foo = "bax"; 30 | assert.isFrozen(r1); 31 | 32 | memoized("test", function (err, r2) { 33 | assert.isNull(err); 34 | 35 | assert.strictEqual(loadTimes, 1); 36 | assert.equal(r2.foo, "bar"); 37 | assert.strictEqual(r1, r2); 38 | assert.isFrozen(r2); 39 | 40 | done(); 41 | }); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/lru-memoizer.itemmaxage.test.js: -------------------------------------------------------------------------------- 1 | var memoizer = require('./..'); 2 | var assert = require('chai').assert; 3 | 4 | describe('lru-memoizer (itemTTL)', function () { 5 | var loadTimes = 0, memoized; 6 | 7 | beforeEach(function () { 8 | loadTimes = 0; 9 | }); 10 | 11 | it('should use default behavior if not configured', function (done) { 12 | memoized = memoizer({ 13 | load: function (a, b, callback) { 14 | loadTimes++; 15 | setTimeout(function () { 16 | callback(null, a + b); 17 | }, 100); 18 | }, 19 | hash: function (a, b) { 20 | return a + '-' + b; 21 | }, 22 | max: 10, 23 | ttl: 500, 24 | }); 25 | 26 | memoized(1,2, function (err, result) { 27 | assert.isNull(err); 28 | assert.strictEqual(result, 3); 29 | assert.strictEqual(loadTimes, 1); 30 | 31 | // Not expired yet. 32 | setTimeout(function() { 33 | memoized(1,2, function (err, result) { 34 | assert.isNull(err); 35 | assert.strictEqual(result, 3); 36 | assert.strictEqual(loadTimes, 1); 37 | 38 | // Expired, load times will increase. 39 | setTimeout(function() { 40 | memoized(1,2, function (err, result) { 41 | assert.isNull(err); 42 | assert.strictEqual(result, 3); 43 | assert.strictEqual(loadTimes, 2); 44 | done(); 45 | }); 46 | }, 300); 47 | }); 48 | }, 400); 49 | }); 50 | }); 51 | 52 | it('should return all args and the result in the itemTTL function', function (done) { 53 | var args; 54 | memoized = memoizer({ 55 | load: function (a, b, callback) { 56 | loadTimes++; 57 | setTimeout(function () { 58 | callback(null, a + b); 59 | }, 100); 60 | }, 61 | itemTTL: function (a, b, result) { 62 | args = arguments; 63 | return 1000; 64 | }, 65 | hash: function (a, b) { 66 | return a + '-' + b; 67 | }, 68 | max: 10, 69 | ttl: 600 70 | }); 71 | 72 | memoized(1,2, function (err, result) { 73 | assert.isNull(err); 74 | assert.strictEqual(args[0], 1); 75 | assert.strictEqual(args[1], 2); 76 | assert.strictEqual(args[2], 3); 77 | done(); 78 | }); 79 | }); 80 | 81 | it('should overwrite the default behavior if configured', function (done) { 82 | var ttl = 0; 83 | var lastKey = null; 84 | memoized = memoizer({ 85 | load: function (a, b, callback) { 86 | loadTimes++; 87 | setTimeout(function () { 88 | callback(null, a + b); 89 | }, 100); 90 | }, 91 | itemTTL: function (a, b, result) { 92 | lastKey = a + '-' + b; 93 | // In this test, we set the ttl of the current item to (result*100). 94 | // If the result is 3, the max age of this item will be 300. 95 | ttl = result * 100; 96 | return ttl; 97 | }, 98 | hash: function (a, b) { 99 | return a + '-' + b; 100 | }, 101 | max: 10, 102 | ttl: 600 103 | }); 104 | 105 | memoized(1,2, function (err, result) { 106 | assert.isNull(err); 107 | assert.strictEqual(ttl, 300); 108 | assert.strictEqual(lastKey, '1-2'); 109 | assert.strictEqual(result, 3); 110 | assert.strictEqual(loadTimes, 1); 111 | 112 | // Not expired yet after 200 ms, because the expiration is 300 113 | setTimeout(function() { 114 | memoized(1,2, function (err, result) { 115 | assert.isNull(err); 116 | assert.strictEqual(ttl, 300); 117 | assert.strictEqual(lastKey, '1-2'); 118 | assert.strictEqual(result, 3); 119 | assert.strictEqual(loadTimes, 1); 120 | 121 | // Expired because now we are at 350 ms (even though gloabl expiration has been set to 600) 122 | setTimeout(function() { 123 | memoized(1,2, function (err, result) { 124 | assert.isNull(err); 125 | assert.strictEqual(ttl, 300); 126 | assert.strictEqual(lastKey, '1-2'); 127 | assert.strictEqual(result, 3); 128 | assert.strictEqual(loadTimes, 2); 129 | 130 | // Expired again, because 350ms have passed again. 131 | setTimeout(function() { 132 | memoized(1,2, function (err, result) { 133 | assert.isNull(err); 134 | assert.strictEqual(ttl, 300); 135 | assert.strictEqual(lastKey, '1-2'); 136 | assert.strictEqual(result, 3); 137 | assert.strictEqual(loadTimes, 3); 138 | done(); 139 | }); 140 | }, 350); 141 | }); 142 | }, 150); 143 | }); 144 | }, 200); 145 | }); 146 | }); 147 | 148 | it('should overwrite the default behavior if configured (sync)', function (done) { 149 | var ttl = 0; 150 | var lastKey = null; 151 | memoized = memoizer.sync({ 152 | load: function (a, b) { 153 | loadTimes++; 154 | return a + b; 155 | }, 156 | itemTTL: function (a, b, result) { 157 | lastKey = a + '-' + b; 158 | // In this test, we set the ttl of the current item to (result*100). 159 | // If the result is 3, the max age of this item will be 300. 160 | ttl = result * 100; 161 | return ttl; 162 | }, 163 | hash: function (a, b) { 164 | return a + '-' + b; 165 | }, 166 | max: 10, 167 | ttl: 600 168 | }); 169 | 170 | var result = memoized(1, 2); 171 | assert.strictEqual(ttl, 300); 172 | assert.strictEqual(lastKey, '1-2'); 173 | assert.strictEqual(result, 3); 174 | assert.strictEqual(loadTimes, 1); 175 | 176 | // Not expired yet after 200 ms, because the expiration is 300 177 | setTimeout(function() { 178 | result = memoized(1, 2); 179 | assert.strictEqual(ttl, 300); 180 | assert.strictEqual(lastKey, '1-2'); 181 | assert.strictEqual(result, 3); 182 | assert.strictEqual(loadTimes, 1); 183 | 184 | // Expired because now we are at 350 ms (even though gloabl expiration has been set to 600) 185 | setTimeout(function() { 186 | result = memoized(1,2); 187 | assert.strictEqual(ttl, 300); 188 | assert.strictEqual(lastKey, '1-2'); 189 | assert.strictEqual(result, 3); 190 | assert.strictEqual(loadTimes, 2); 191 | 192 | // Expired again, because 350ms have passed again. 193 | setTimeout(function() { 194 | result = memoized(1,2); 195 | assert.strictEqual(ttl, 300); 196 | assert.strictEqual(lastKey, '1-2'); 197 | assert.strictEqual(result, 3); 198 | assert.strictEqual(loadTimes, 3); 199 | done(); 200 | }, 350); 201 | }, 150); 202 | }, 200); 203 | }); 204 | }); 205 | -------------------------------------------------------------------------------- /test/lru-memoizer.lock.test.js: -------------------------------------------------------------------------------- 1 | const memoizer = require('./..'); 2 | const assert = require('chai').assert; 3 | const _ = require('lodash'); 4 | 5 | describe('lru-simultaneos calls', function () { 6 | var loadTimes = 0, memoized; 7 | 8 | beforeEach(function () { 9 | loadTimes = 0; 10 | 11 | memoized = memoizer({ 12 | load: function (a, b, callback) { 13 | loadTimes++; 14 | setTimeout(function () { 15 | callback(null, a + b); 16 | }, 100); 17 | }, 18 | hash: function (a, b) { 19 | return a + '-' + b; 20 | }, 21 | max: 10 22 | }); 23 | }); 24 | 25 | it('should call once', function (done) { 26 | memoized(1, 2, _.noop); 27 | memoized(1, 2, _.noop); 28 | memoized(1, 2, function (err, result) { 29 | if (err) { return done(err); } 30 | assert.strictEqual(loadTimes, 1); 31 | assert.strictEqual(result, 3); 32 | done(); 33 | }); 34 | }); 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /test/lru-memoizer.nokey.test.js: -------------------------------------------------------------------------------- 1 | var memoizer = require('./..'); 2 | var assert = require('chai').assert; 3 | 4 | describe('lru-memoizer (no key)', function () { 5 | var loadTimes = 0, memoized; 6 | 7 | beforeEach(function () { 8 | loadTimes = 0; 9 | 10 | memoized = memoizer({ 11 | max: 10, 12 | load: function (callback) { 13 | loadTimes++; 14 | return setTimeout(function () { 15 | callback(null, loadTimes); 16 | }, 10); 17 | } 18 | }); 19 | }); 20 | 21 | it('should cache the result of an async function', function (done) { 22 | memoized(function (err, result) { 23 | assert.isNull(err); 24 | assert.equal(result, 1); 25 | assert.equal(loadTimes, 1); 26 | memoized(function (err, result) { 27 | assert.isNull(err); 28 | assert.equal(result, 1); 29 | assert.equal(loadTimes, 1); 30 | done(); 31 | }); 32 | }); 33 | 34 | }); 35 | 36 | it('should use the hash function for keys', function (done) { 37 | memoized(function () { 38 | memoized(function () { 39 | assert.includeMembers(memoized.keys(), ['_']); 40 | done(); 41 | }); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/lru-memoizer.queumaxage.test.js: -------------------------------------------------------------------------------- 1 | var memoizer = require('./..'); 2 | var assert = require('chai').assert; 3 | 4 | describe('lru-memoizer (queueTTL)', function () { 5 | var loadTimes = 0, memoized; 6 | 7 | beforeEach(function () { 8 | loadTimes = 0; 9 | }); 10 | 11 | function observer() { 12 | const listeners = []; 13 | return { 14 | listen(listener) { 15 | listeners.push(listener); 16 | }, 17 | trigger() { 18 | listeners.forEach(listener => listener()); 19 | } 20 | } 21 | } 22 | 23 | it('should create a new queue once expired', function (done) { 24 | memoized = memoizer({ 25 | load: function (a, b, onResolve, callback) { 26 | loadTimes++; 27 | onResolve(() => callback(null, a + b)); 28 | }, 29 | queueTTL: 10, 30 | hash: function (a, b) { 31 | return a + '-' + b; 32 | }, 33 | max: 10 34 | }); 35 | 36 | const observer1 = observer(); 37 | const observer2 = observer(); 38 | const observer3 = observer(); 39 | const resolved = []; 40 | 41 | memoized(1, 2, observer1.listen, function (err, result) { 42 | assert.isNull(err); 43 | assert.strictEqual(result, 3); 44 | resolved.push('A'); 45 | }); 46 | 47 | assert.strictEqual(loadTimes, 1); 48 | 49 | memoized(1, 2, assert.fail, function (err, result) { 50 | assert.isNull(err); 51 | assert.strictEqual(result, 3); 52 | resolved.push('B'); 53 | }); 54 | 55 | assert.strictEqual(loadTimes, 1); 56 | 57 | setTimeout(() => { 58 | // previous queue expired, this calls will be added to a new queue. 59 | memoized(1, 2, observer2.listen, function (err, result) { 60 | assert.isNull(err); 61 | assert.strictEqual(result, 3); 62 | resolved.push('C'); 63 | }); 64 | 65 | memoized(1, 2, assert.fail, function (err, result) { 66 | assert.isNull(err); 67 | assert.strictEqual(result, 3); 68 | resolved.push('D'); 69 | }); 70 | 71 | // only one new invocation to load 72 | assert.strictEqual(loadTimes, 2); 73 | 74 | setTimeout(() => { 75 | // second queue expired, this calls will be added to a third queue. 76 | memoized(1, 2, observer3.listen, function (err, result) { 77 | assert.isNull(err); 78 | assert.strictEqual(result, 3); 79 | resolved.push('E'); 80 | }); 81 | 82 | memoized(1, 2, assert.fail.listen, function (err, result) { 83 | assert.isNull(err); 84 | assert.strictEqual(result, 3); 85 | resolved.push('F'); 86 | }); 87 | 88 | assert.strictEqual(loadTimes, 3); 89 | 90 | observer1.trigger(); 91 | setImmediate(() => { 92 | // first queue was resolved 93 | assert.deepEqual(['A', 'B'], resolved); 94 | 95 | observer3.trigger(); 96 | setImmediate(() => { 97 | // third queue was resolved 98 | assert.deepEqual(['A', 'B', 'E', 'F'], resolved); 99 | 100 | observer2.trigger(); 101 | setImmediate(() => { 102 | // second queue was resolved 103 | assert.deepEqual(['A', 'B', 'E', 'F', 'C', 'D'], resolved); 104 | done(); 105 | }); 106 | }); 107 | }); 108 | }, 100); 109 | }, 100); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/lru-memoizer.sync.clone.test.js: -------------------------------------------------------------------------------- 1 | const memoizer = require('./..'); 2 | const assert = require('chai').assert; 3 | 4 | describe('lru-memoizer sync (clone)', () => { 5 | 6 | describe('call', () => { 7 | let loadTimes = 0, memoized; 8 | 9 | beforeEach(() => { 10 | loadTimes = 0; 11 | 12 | memoized = memoizer.sync({ 13 | load: (key) => { 14 | loadTimes++; 15 | return { foo: key , buffer: Buffer.from('1234') }; 16 | }, 17 | hash: (key) => { 18 | return key; 19 | }, 20 | clone: true, 21 | max: 10 22 | }); 23 | }); 24 | 25 | it('should return a clone every time with the same cached structure', () => { 26 | const r1 = memoized('bar'); 27 | assert.strictEqual(loadTimes, 1); 28 | assert.equal(r1.foo, 'bar'); 29 | r1.foo = 'bax'; 30 | 31 | const r2 = memoized('bar'); 32 | assert.strictEqual(loadTimes, 1); 33 | assert.equal(r2.foo, 'bar'); 34 | assert.notStrictEqual(r1, r2); 35 | assert.notEqual(r1, r2); 36 | }); 37 | }); 38 | 39 | describe('Promise', () => { 40 | let loadTimes = 0, memoized; 41 | 42 | beforeEach(() => { 43 | loadTimes = 0; 44 | 45 | memoized = memoizer.sync({ 46 | load: (key) => { 47 | loadTimes++; 48 | return Promise.resolve({ foo: key, buffer: Buffer.from('1234') }); 49 | }, 50 | hash: (key) => { 51 | return key; 52 | }, 53 | clone: true, 54 | max: 10 55 | }); 56 | }); 57 | 58 | it('should return a clone every time with the same cached structure', (done) => { 59 | memoized('bar').then(r1 => { 60 | assert.strictEqual(loadTimes, 1); 61 | assert.equal(r1.foo, 'bar'); 62 | r1.foo = 'bax'; 63 | 64 | memoized('bar').then(r2 => { 65 | assert.strictEqual(loadTimes, 1); 66 | assert.equal(r2.foo, 'bar'); 67 | assert.notStrictEqual(r1, r2); 68 | assert.notEqual(r1, r2); 69 | 70 | done(); 71 | }); 72 | }) 73 | .catch(done); 74 | }); 75 | }); 76 | 77 | }); 78 | 79 | -------------------------------------------------------------------------------- /test/lru-memoizer.sync.events.test.js: -------------------------------------------------------------------------------- 1 | const memoizer = require('./..'); 2 | const sinon = require('sinon'); 3 | 4 | describe('lru-memoizer sync (events)', function () { 5 | let memoized; 6 | let onMiss, onHit, onQueue; 7 | 8 | beforeEach(function () { 9 | loadTimes = 0; 10 | onMiss = sinon.stub(); 11 | onHit = sinon.stub(); 12 | onQueue = sinon.stub(); 13 | memoized = memoizer.sync({ 14 | load: function (a, b, bypass) { 15 | return a + b; 16 | }, 17 | hash: function (a, b, bypass) { 18 | return a + '-' + b; 19 | }, 20 | bypass: function(a, b, bypass) { 21 | return bypass; 22 | }, 23 | max: 10 24 | }); 25 | memoized.on('hit', onHit); 26 | memoized.on('miss', onMiss); 27 | memoized.on('queue', onQueue); 28 | }); 29 | 30 | describe('when the result is not in the cache', () => { 31 | beforeEach(() => { 32 | memoized(1, 2, false); 33 | }); 34 | 35 | it('should not call onHit', () => { 36 | sinon.assert.notCalled(onHit); 37 | }); 38 | 39 | it('should not call onQueue', () => { 40 | sinon.assert.notCalled(onQueue); 41 | }); 42 | 43 | it('should call onMiss with the load arguments', () => { 44 | sinon.assert.calledOnce(onMiss); 45 | sinon.assert.calledWith(onMiss, 1, 2, false); 46 | }); 47 | }); 48 | 49 | describe('when the result is in the cache', () => { 50 | beforeEach(() => { 51 | memoized(1,2, false); 52 | onHit.reset(); 53 | onMiss.reset(); 54 | onQueue.reset(); 55 | memoized(1, 2, false); 56 | }); 57 | 58 | it('should call onHit with the load arguments', () => { 59 | sinon.assert.calledOnce(onHit); 60 | sinon.assert.calledWith(onHit, 1, 2, false); 61 | }); 62 | 63 | it('should not call onQueue', () => { 64 | sinon.assert.notCalled(onQueue); 65 | }); 66 | 67 | it('should not call onMiss', () => { 68 | sinon.assert.notCalled(onQueue); 69 | }); 70 | }); 71 | 72 | describe('when the cache is by passed', () => { 73 | beforeEach(() => { 74 | memoized(1,2, false); 75 | onHit.reset(); 76 | onMiss.reset(); 77 | onQueue.reset(); 78 | memoized(1, 2, true); 79 | }); 80 | 81 | it('should not call onHit', () => { 82 | sinon.assert.notCalled(onHit); 83 | }); 84 | 85 | it('should not call onQueue', () => { 86 | sinon.assert.notCalled(onQueue); 87 | }); 88 | 89 | it('should call onMiss with the load arguments', () => { 90 | sinon.assert.calledOnce(onMiss); 91 | sinon.assert.calledWith(onMiss, 1, 2, true); 92 | }); 93 | }); 94 | }); 95 | 96 | -------------------------------------------------------------------------------- /test/lru-memoizer.sync.freeze.js: -------------------------------------------------------------------------------- 1 | const memoizer = require('./..'); 2 | const assert = require('chai').assert; 3 | 4 | describe('lru-memoizer sync (freeze)', () => { 5 | 6 | describe('call', () => { 7 | let loadTimes = 0, memoized; 8 | 9 | beforeEach(() => { 10 | loadTimes = 0; 11 | 12 | memoized = memoizer.sync({ 13 | load: (key) => { 14 | loadTimes++; 15 | return { foo: key , buffer: Buffer.from('1234') }; 16 | }, 17 | hash: (key) => { 18 | return key; 19 | }, 20 | freeze: true, 21 | max: 10 22 | }); 23 | }); 24 | 25 | it('should return a freeze every time with the same cached structure', () => { 26 | const r1 = memoized('bar'); 27 | assert.strictEqual(loadTimes, 1); 28 | assert.equal(r1.foo, 'bar'); 29 | assert.isFrozen(r1); 30 | 31 | const r2 = memoized('bar'); 32 | assert.strictEqual(loadTimes, 1); 33 | assert.equal(r2.foo, 'bar'); 34 | assert.isFrozen(r2); 35 | }); 36 | }); 37 | 38 | describe('Promise', () => { 39 | let loadTimes = 0, memoized; 40 | 41 | beforeEach(() => { 42 | loadTimes = 0; 43 | 44 | memoized = memoizer.sync({ 45 | load: (key) => { 46 | loadTimes++; 47 | return Promise.resolve({ foo: key, buffer: Buffer.from('1234') }); 48 | }, 49 | hash: (key) => { 50 | return key; 51 | }, 52 | freeze: true, 53 | max: 10 54 | }); 55 | }); 56 | 57 | it('should return a freeze every time with the same cached structure', (done) => { 58 | memoized('bar').then(r1 => { 59 | assert.strictEqual(loadTimes, 1); 60 | assert.equal(r1.foo, 'bar'); 61 | assert.isFrozen(r1); 62 | 63 | memoized('bar').then(r2 => { 64 | assert.strictEqual(loadTimes, 1); 65 | assert.equal(r2.foo, 'bar'); 66 | assert.isFrozen(r2); 67 | 68 | done(); 69 | }); 70 | }) 71 | .catch(done); 72 | }); 73 | }); 74 | 75 | }); 76 | 77 | -------------------------------------------------------------------------------- /test/lru-memoizer.sync.test.js: -------------------------------------------------------------------------------- 1 | var memoizer = require('./..'); 2 | var assert = require('chai').assert; 3 | 4 | describe('lru-memoizer sync', function () { 5 | var loadTimes = 0, memoized; 6 | 7 | beforeEach(function () { 8 | loadTimes = 0; 9 | 10 | memoized = memoizer.sync({ 11 | load: function (a, b) { 12 | loadTimes++; 13 | if (a === 0) { 14 | throw new Error('a cant be 0'); 15 | } 16 | return a + b; 17 | }, 18 | hash: function (a, b) { 19 | return a + '-' + b; 20 | }, 21 | max: 10 22 | }); 23 | }); 24 | 25 | it('should cache the result of an async function', function () { 26 | var result = memoized(1, 2); 27 | assert.equal(result, 3); 28 | assert.equal(loadTimes, 1); 29 | 30 | var result2 = memoized(1,2); 31 | assert.equal(result2, 3); 32 | assert.equal(loadTimes, 1); 33 | }); 34 | 35 | it('shuld use the hash function for keys', function () { 36 | memoized(1, 2); 37 | memoized(2, 3); 38 | assert.includeMembers(memoized.keys(), ['1-2', '2-3']); 39 | }); 40 | 41 | it('should not cache errored funcs', function () { 42 | try { 43 | memoized(0, 2); 44 | } catch(err) {} 45 | assert.notInclude(memoized.keys(), ['0-2']); 46 | }); 47 | }); -------------------------------------------------------------------------------- /test/lru-memoizer.test.js: -------------------------------------------------------------------------------- 1 | var memoizer = require('./..'); 2 | var assert = require('chai').assert; 3 | 4 | describe('lru-memoizer', function () { 5 | var loadTimes = 0, memoized; 6 | 7 | beforeEach(function () { 8 | loadTimes = 0; 9 | 10 | memoized = memoizer({ 11 | load: function (a, b, callback) { 12 | loadTimes++; 13 | return setTimeout(function () { 14 | if (a === 0) { 15 | return callback(new Error('a cant be 0')); 16 | } 17 | callback(null, a+b); 18 | }, 10); 19 | }, 20 | hash: function (a, b) { 21 | return a + '-' + b; 22 | }, 23 | max: 10 24 | }); 25 | }); 26 | 27 | it('should cache the result of an async function', function (done) { 28 | memoized(1,2, function (err, result) { 29 | assert.isNull(err); 30 | assert.strictEqual(result, 3); 31 | assert.strictEqual(loadTimes, 1); 32 | memoized(1,2, function (err, result) { 33 | assert.isNull(err); 34 | assert.strictEqual(result, 3); 35 | assert.strictEqual(loadTimes, 1); 36 | done(); 37 | }); 38 | }); 39 | 40 | }); 41 | 42 | it('should use the hash function for keys', function (done) { 43 | memoized(1, 2, function () { 44 | memoized(2,3, function () { 45 | assert.includeMembers(memoized.keys(), ['1-2', '2-3']); 46 | done(); 47 | }); 48 | }); 49 | }); 50 | 51 | it('should not cache errored funcs', function (done) { 52 | memoized(0, 2, function (err) { 53 | assert.isNotNull(err); 54 | assert.notInclude(memoized.keys(), ['0-2']); 55 | done(); 56 | }); 57 | }); 58 | 59 | it('should expose the hash function', function() { 60 | assert.equal(memoized.hash(0, 2), '0-2'); 61 | }); 62 | 63 | it('should expose the load function', function(done) { 64 | memoized.load(1, 2, (err, result) => { 65 | assert.equal(result, 3); 66 | done(); 67 | }); 68 | }); 69 | 70 | it('should expose the max prop', function() { 71 | assert.equal(memoized.max, 10); 72 | }); 73 | 74 | it('should allow to del a key', function(done) { 75 | memoized(1,2, () => { 76 | assert.strictEqual(loadTimes, 1); 77 | memoized.del(1,2); 78 | memoized(1,2, (err, result) => { 79 | assert.isNull(err); 80 | assert.strictEqual(result, 3); 81 | assert.strictEqual(loadTimes, 2); 82 | done(); 83 | }); 84 | }); 85 | }); 86 | 87 | }); 88 | 89 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationMap": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "inlineSources": true, 8 | "jsx": "react", 9 | "module": "nodenext", 10 | "moduleResolution": "nodenext", 11 | "noUncheckedIndexedAccess": true, 12 | "resolveJsonModule": true, 13 | "skipLibCheck": true, 14 | "sourceMap": true, 15 | "strict": true, 16 | "target": "es2022", 17 | "outDir": "./lib" 18 | } 19 | } --------------------------------------------------------------------------------