diff --git a/README.md b/README.md index 1e8da3f..8e26acc 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,8 @@ const myCache = new NodeCache(); _Here's a [simple code example](https://runkit.com/mpneuried/useclones-example-83) showing the different behavior_ - `deleteOnExpire`: *(default: `true`)* whether variables will be deleted automatically when they expire. If `true` the variable will be deleted. If `false` the variable will remain. You are encouraged to handle the variable upon the event `expired` by yourself. -- `enableLegacyCallbacks`: *(default: `false`)* re-enables the usage of callbacks instead of sync functions. adds an additional `cb` argument to each function which resolves to `(err, result)`. will be removed in node-cache v6.x. +- `enableLegacyCallbacks`: *(default: `false`)* re-enables the usage of callbacks instead of sync functions. Adds an additional `cb` argument to each function which resolves to `(err, result)`. will be removed in node-cache v6.x. +- `maxKeys`: *(default: `-1`)* specifies a maximum amount of keys that can be stored in the cache. If a new item is set and the cache is full, an error is thrown and the key will not be saved in the cache. -1 disables the key limit. ```js const NodeCache = require( "node-cache" ); diff --git a/_src/lib/node_cache.coffee b/_src/lib/node_cache.coffee index 3384d8a..f89bfa5 100644 --- a/_src/lib/node_cache.coffee +++ b/_src/lib/node_cache.coffee @@ -31,12 +31,13 @@ module.exports = class NodeCache extends EventEmitter deleteOnExpire: true # enable legacy callbacks enableLegacyCallbacks: false + # max amount of keys that are being stored + maxKeys: -1 , @options ) # generate functions with callbacks (legacy) if (@options.enableLegacyCallbacks) console.warn("WARNING! node-cache legacy callback support will drop in v6.x") - [ "get", "mget", @@ -166,6 +167,11 @@ module.exports = class NodeCache extends EventEmitter # console.log( err, success ) # set: ( key, value, ttl )=> + # check if cache is overflowing + if (@stats.keys >= @options.maxKeys && @options.maxKeys > -1) + _err = @_error( "ECACHEFULL" ) + throw _err + # force the data to string if @options.forceString and not typeof value is "string" value = JSON.stringify( value ) @@ -600,5 +606,6 @@ module.exports = class NodeCache extends EventEmitter _ERRORS: "ENOTFOUND": "Key `__key` not found" + "ECACHEFULL": "Cache max key size exceeded" "EKEYTYPE": "The key argument has to be of type `string` or `number`. Found: `__key`" "EKEYSTYPE": "The keys argument has to be an array." diff --git a/_src/test/mocha_test.coffee b/_src/test/mocha_test.coffee index 6909b66..a180abe 100644 --- a/_src/test/mocha_test.coffee +++ b/_src/test/mocha_test.coffee @@ -19,6 +19,9 @@ localCacheNoClone = new nodeCache({ checkperiod: 0 }) +localCacheMaxKeys = new nodeCache({ + maxKeys: 2 +}) localCacheTTL = new nodeCache({ stdTTL: 0.3, @@ -280,6 +283,40 @@ describe "`#{pkg.name}@#{pkg.version}` on `node@#{process.version}`", () -> return + describe "max key amount", () -> + before () -> + state = + key1: randomString(10) + key2: randomString(10) + key3: randomString(10) + value1: randomString(10) + value2: randomString(10) + value3: randomString(10) + return + + it "exceed max key size", () -> + setKey = localCacheMaxKeys.set(state.key1, state.value1, 0) + true.should.eql setKey + + setKey2 = localCacheMaxKeys.set(state.key2, state.value2, 0) + true.should.eql setKey2 + + (() -> localCacheMaxKeys.set(state.key3, state.value3, 0)).should.throw({ + name: "ECACHEFULL" + message: "Cache max key size exceeded" + }) + return + + it "remove a key and set another one", () -> + del = localCacheMaxKeys.del(state.key1) + 1.should.eql del + + setKey3 = localCacheMaxKeys.set(state.key3, state.value3, 0) + true.should.eql setKey3 + return + + return + describe "correct and incorrect key types", () -> describe "number", () -> before () -> @@ -293,42 +330,32 @@ describe "`#{pkg.name}@#{pkg.version}` on `node@#{process.version}`", () -> it "set", () -> for key in state.keys - localCache.set key, state.val, (err, res) -> - should.not.exist err - true.should.eql res - return + res = localCache.set key, state.val + true.should.eql res return it "get", () -> - localCache.get state.keys[0], (err, res) -> - should.not.exist err - state.val.should.eql res - return + res = localCache.get state.keys[0] + state.val.should.eql res return it "mget", () -> - localCache.mget state.keys[0..1], (err, res) -> - should.not.exist err - # generate prediction - prediction = {} - prediction[state.keys[0]] = state.val - prediction[state.keys[1]] = state.val - prediction.should.eql res - return + res = localCache.mget state.keys[0..1] + # generate prediction + prediction = {} + prediction[state.keys[0]] = state.val + prediction[state.keys[1]] = state.val + prediction.should.eql res return it "del single", () -> - localCache.del state.keys[0], (err, count) -> - should.not.exist err - 1.should.eql count - return + count = localCache.del state.keys[0] + 1.should.eql count return it "del multi", () -> - localCache.del state.keys[1..2], (err, count) -> - should.not.exist err - 2.should.eql count - return + count = localCache.del state.keys[1..2] + 2.should.eql count return it "ttl", (done) -> @@ -372,42 +399,32 @@ describe "`#{pkg.name}@#{pkg.version}` on `node@#{process.version}`", () -> it "set", () -> for key in state.keys - localCache.set key, state.val, (err, res) -> - should.not.exist err - true.should.eql res - return + res = localCache.set key, state.val + true.should.eql res return it "get", () -> - localCache.get state.keys[0], (err, res) -> - should.not.exist err - state.val.should.eql res - return + res = localCache.get state.keys[0] + state.val.should.eql res return it "mget", () -> - localCache.mget state.keys[0..1], (err, res) -> - should.not.exist err - # generate prediction - prediction = {} - prediction[state.keys[0]] = state.val - prediction[state.keys[1]] = state.val - prediction.should.eql res - return + res = localCache.mget state.keys[0..1] + # generate prediction + prediction = {} + prediction[state.keys[0]] = state.val + prediction[state.keys[1]] = state.val + prediction.should.eql res return it "del single", () -> - localCache.del state.keys[0], (err, count) -> - should.not.exist err - 1.should.eql count - return + count = localCache.del state.keys[0] + 1.should.eql count return it "del multi", () -> - localCache.del state.keys[1..2], (err, count) -> - should.not.exist err - 2.should.eql count - return + count = localCache.del state.keys[1..2] + 2.should.eql count return it "ttl", (done) -> @@ -832,13 +849,11 @@ describe "`#{pkg.name}@#{pkg.version}` on `node@#{process.version}`", () -> return it "set a key with ttl", () -> - localCache.set state.key1, state.val, 0.7, (err, res) -> - should.not.exist err - true.should.eql res - ts = localCache.getTtl state.key1 - if state.now < ts < state.now + 300 - throw new Error "Invalid timestamp" - return + res = localCache.set state.key1, state.val, 0.7 + true.should.eql res + ts = localCache.getTtl state.key1 + if state.now < ts < state.now + 300 + throw new Error "Invalid timestamp" return it "check this key immediately", () -> @@ -870,17 +885,13 @@ describe "`#{pkg.name}@#{pkg.version}` on `node@#{process.version}`", () -> return it "set another key with ttl", () -> - localCache.set state.key2, state.val, 0.5, (err, res) -> - should.not.exist err - true.should.eql res - return + res = localCache.set state.key2, state.val, 0.5 + true.should.eql res return it "check this key immediately", () -> - localCache.get state.key2, (err, res) -> - should.not.exist err - state.val.should.eql res - return + res = localCache.get state.key2 + state.val.should.eql res return it "before it times out", () -> @@ -969,10 +980,8 @@ describe "`#{pkg.name}@#{pkg.version}` on `node@#{process.version}`", () -> return it "check if the key still exists", () -> - localCache.get state.key3, (err, res) -> - should.not.exist err - state.val.should.eql res - return + res = localCache.get state.key3 + state.val.should.eql res return it "wait until ttl has ended and check if the key was deleted", (done) ->