safe-memory-cache
Advanced tools
+96
| function createMem(number, limit) { | ||
| var mem = Object.create(bucketsProto) | ||
| mem.N = number | ||
| mem.max = limit | ||
| mem.clear() | ||
| return mem | ||
| } | ||
| var bucketsProto = { | ||
| clear: function clear() { | ||
| this.size = 0 | ||
| this.buckets=[]; | ||
| for (var i = 0; i < this.N; i++) { | ||
| this.spawnBucket() | ||
| } | ||
| }, | ||
| spawnBucket: function spawnBucket() { | ||
| this.buckets.unshift(Object.create(null)) | ||
| }, | ||
| rotateBuckets: function rotateBuckets() { | ||
| var dropped = this.buckets.pop() | ||
| this.spawnBucket() | ||
| this.size = 0 | ||
| if(this.rotationHook){ | ||
| this.rotationHook(dropped) | ||
| } | ||
| }, | ||
| set: function set(key, value) { | ||
| if (!(key in this.buckets[0])) { | ||
| this.size++; | ||
| if (this.max && this.size >= Math.ceil(this.max / this.buckets.length)) { | ||
| this.rotateBuckets() | ||
| } | ||
| } | ||
| this.buckets[0][key] = value | ||
| return value | ||
| }, | ||
| get: function get(key) { | ||
| for (var i = 0; i < this.buckets.length; i++) { | ||
| if (key in this.buckets[i]) { | ||
| //todo: this should be configurable | ||
| if (i) { | ||
| //put a reference in the newest bucket | ||
| return this.set(key,this.buckets[i][key]) | ||
| } | ||
| return this.buckets[i][key] | ||
| } | ||
| } | ||
| } | ||
| } | ||
| var protoRegex = /__proto__/g; | ||
| function sanitizeSimple(key) { | ||
| return '' + key.replace(protoRegex, 'z__proto__') | ||
| } | ||
| function sanitizeHeavy(key) { | ||
| return ('' + key).split('').map(function(char) { | ||
| return char.charCodeAt(0).toString(32) | ||
| }).join('z') | ||
| } | ||
| module.exports = function(opts) { | ||
| var buckets = ~~(opts.buckets) || 2; | ||
| var mem = createMem(buckets, opts.limit) | ||
| mem.rotationHook = opts.cleanupListener || null | ||
| var sanitize = (opts.strongSanitizer ? sanitizeHeavy : sanitizeSimple) | ||
| if (opts.maxTTL) { | ||
| var intervalHandle = setInterval(mem.rotateBuckets.bind(mem), ~~(opts.maxTTL / buckets)) | ||
| } | ||
| return { | ||
| set: function(key, value) { | ||
| return mem.set(sanitize(key), value) | ||
| }, | ||
| get: function(key) { | ||
| return mem.get(sanitize(key)) | ||
| }, | ||
| clear: mem.clear.bind(mem), | ||
| destroy: function() { | ||
| mem.rotationHook = null | ||
| clearInterval(intervalHandle) | ||
| }, | ||
| _get_buckets: function(){ | ||
| return mem.buckets | ||
| }, | ||
| _rotate_buckets: function() { | ||
| return mem.rotateBuckets() | ||
| } | ||
| } | ||
| } |
+30
-46
@@ -12,3 +12,3 @@ function createMem(number, limit) { | ||
| this.size = 0 | ||
| this.buckets=[]; | ||
| this.buckets = []; | ||
| for (var i = 0; i < this.N; i++) { | ||
@@ -19,3 +19,3 @@ this.spawnBucket() | ||
| spawnBucket: function spawnBucket() { | ||
| this.buckets.unshift(Object.create(null)) | ||
| this.buckets.unshift(new Map()) | ||
| }, | ||
@@ -26,3 +26,3 @@ rotateBuckets: function rotateBuckets() { | ||
| this.size = 0 | ||
| if(this.rotationHook){ | ||
| if (this.rotationHook) { | ||
| this.rotationHook(dropped) | ||
@@ -32,3 +32,3 @@ } | ||
| set: function set(key, value) { | ||
| if (!(key in this.buckets[0])) { | ||
| if (!(this.buckets[0].has(key))) { | ||
| this.size++; | ||
@@ -39,3 +39,3 @@ if (this.max && this.size >= Math.ceil(this.max / this.buckets.length)) { | ||
| } | ||
| this.buckets[0][key] = value | ||
| this.buckets[0].set(key, value) | ||
| return value | ||
@@ -45,9 +45,9 @@ }, | ||
| for (var i = 0; i < this.buckets.length; i++) { | ||
| if (key in this.buckets[i]) { | ||
| if (this.buckets[i].has(key)) { | ||
| //todo: this should be configurable | ||
| if (i) { | ||
| //put a reference in the newest bucket | ||
| return this.set(key,this.buckets[i][key]) | ||
| return this.set(key, this.buckets[i].get(key)) | ||
| } | ||
| return this.buckets[i][key] | ||
| return this.buckets[i].get(key) | ||
| } | ||
@@ -58,46 +58,30 @@ } | ||
| var protoRegex = /__proto__/g; | ||
| function sanitizeSimple(key) { | ||
| return '' + key.replace(protoRegex, 'z__proto__') | ||
| } | ||
| function sanitizeHeavy(key) { | ||
| return ('' + key).split('').map(function(char) { | ||
| return char.charCodeAt(0).toString(32) | ||
| }).join('z') | ||
| } | ||
| module.exports = { | ||
| safeMemoryCache(opts) { | ||
| var buckets = ~~(opts.buckets) || 2; | ||
| var mem = createMem(buckets, opts.limit) | ||
| mem.rotationHook = opts.cleanupListener || null | ||
| if (opts.maxTTL) { | ||
| var intervalHandle = setInterval(mem.rotateBuckets.bind(mem), ~~(opts.maxTTL / buckets)) | ||
| } | ||
| module.exports = function(opts) { | ||
| var buckets = ~~(opts.buckets) || 2; | ||
| var mem = createMem(buckets, opts.limit) | ||
| mem.rotationHook = opts.cleanupListener || null | ||
| var sanitize = (opts.strongSanitizer ? sanitizeHeavy : sanitizeSimple) | ||
| return { | ||
| set: mem.set.bind(mem), | ||
| get: mem.get.bind(mem), | ||
| clear: mem.clear.bind(mem), | ||
| destroy: function () { | ||
| clearInterval(intervalHandle) | ||
| }, | ||
| _get_buckets: function () { | ||
| return mem.buckets | ||
| }, | ||
| _rotate_buckets: function () { | ||
| return mem.rotateBuckets() | ||
| } | ||
| } | ||
| if (opts.maxTTL) { | ||
| var intervalHandle = setInterval(mem.rotateBuckets.bind(mem), ~~(opts.maxTTL / buckets)) | ||
| } | ||
| return { | ||
| set: function(key, value) { | ||
| return mem.set(sanitize(key), value) | ||
| }, | ||
| get: function(key) { | ||
| return mem.get(sanitize(key)) | ||
| }, | ||
| clear: mem.clear.bind(mem), | ||
| destroy: function() { | ||
| mem.rotationHook = null | ||
| clearInterval(intervalHandle) | ||
| }, | ||
| _get_buckets: function(){ | ||
| return mem.buckets | ||
| }, | ||
| _rotate_buckets: function() { | ||
| return mem.rotateBuckets() | ||
| } | ||
| } | ||
| } |
+1
-1
| { | ||
| "name": "safe-memory-cache", | ||
| "version": "1.5.1", | ||
| "version": "2.0.0", | ||
| "description": "Secure and size-limited in-memory cache for Node.js and browsers", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
+4
-5
@@ -8,4 +8,3 @@ # safe-memory-cache | ||
| - Can't be broken by a malicious key (`__proto__`) | ||
| - Can't be affected by modifications to native prototypes | ||
| - Limits the number of items without the use of `delete` (and no memory leaks caused by `delete`), plays well with garbage collector. But also doesn't drop the whole cache when full, frees up gradually | ||
| - Limits the number of items without the use of `delete` (and no memory leaks caused by `delete`), plays well with garbage collector. But also **doesn't drop the whole cache when full, frees up gradually** | ||
| - Doesn't waste your eventloop ticks with timeouts set to remove single items from cache, but still deletes oldest items first | ||
@@ -16,3 +15,3 @@ | ||
| ``` | ||
| var safeMemoryCache = require('safe-memory-cache') | ||
| var {safeMemoryCache} = require('safe-memory-cache') | ||
| var cache = safeMemoryCache(options) | ||
@@ -29,6 +28,6 @@ | ||
| If your engine supports `Map`, you can use the map based version. It doesn't need (nor have) sanitization on keys and it uses Maps as buckets for storage. | ||
| If your engine doesn't support `Map`, you can use the legacy version. It does manual sanitization on keys and it uses plain objects as buckets for storage. | ||
| ``` | ||
| const safeMemoryCache = require('safe-memory-cache/map') | ||
| const safeMemoryCache = require('safe-memory-cache/legacy') | ||
| ``` | ||
@@ -35,0 +34,0 @@ |
+3
-3
@@ -5,4 +5,4 @@ var assert = require('assert') | ||
| console.log('# Main implementation') | ||
| var safeMemoryCache = require('./index') | ||
| console.log('# Legacy implementation') | ||
| var safeMemoryCache = require('./legacy') | ||
@@ -53,3 +53,3 @@ var c = safeMemoryCache({ | ||
| var safeMemoryCache = require('./map') | ||
| var {safeMemoryCache} = require('./index') | ||
@@ -56,0 +56,0 @@ |
-79
| function createMem(number, limit) { | ||
| var mem = Object.create(bucketsProto) | ||
| mem.N = number | ||
| mem.max = limit | ||
| mem.clear() | ||
| return mem | ||
| } | ||
| var bucketsProto = { | ||
| clear: function clear() { | ||
| this.size = 0 | ||
| this.buckets=[]; | ||
| for (var i = 0; i < this.N; i++) { | ||
| this.spawnBucket() | ||
| } | ||
| }, | ||
| spawnBucket: function spawnBucket() { | ||
| this.buckets.unshift(new Map()) | ||
| }, | ||
| rotateBuckets: function rotateBuckets() { | ||
| var dropped = this.buckets.pop() | ||
| this.spawnBucket() | ||
| this.size = 0 | ||
| if(this.rotationHook){ | ||
| this.rotationHook(dropped) | ||
| } | ||
| }, | ||
| set: function set(key, value) { | ||
| if (!(this.buckets[0].has(key))) { | ||
| this.size++; | ||
| if (this.max && this.size >= Math.ceil(this.max / this.buckets.length)) { | ||
| this.rotateBuckets() | ||
| } | ||
| } | ||
| this.buckets[0].set(key, value) | ||
| return value | ||
| }, | ||
| get: function get(key) { | ||
| for (var i = 0; i < this.buckets.length; i++) { | ||
| if (this.buckets[i].has(key)) { | ||
| //todo: this should be configurable | ||
| if (i) { | ||
| //put a reference in the newest bucket | ||
| return this.set(key,this.buckets[i].get(key)) | ||
| } | ||
| return this.buckets[i].get(key) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| module.exports = function(opts) { | ||
| var buckets = ~~(opts.buckets) || 2; | ||
| var mem = createMem(buckets, opts.limit) | ||
| mem.rotationHook = opts.cleanupListener || null | ||
| if (opts.maxTTL) { | ||
| var intervalHandle = setInterval(mem.rotateBuckets.bind(mem), ~~(opts.maxTTL / buckets)) | ||
| } | ||
| return { | ||
| set: mem.set.bind(mem), | ||
| get: mem.get.bind(mem), | ||
| clear: mem.clear.bind(mem), | ||
| destroy: function() { | ||
| clearInterval(intervalHandle) | ||
| }, | ||
| _get_buckets: function() { | ||
| return mem.buckets | ||
| }, | ||
| _rotate_buckets: function() { | ||
| return mem.rotateBuckets() | ||
| } | ||
| } | ||
| } |
11821
0.55%226
0.89%74
-1.33%