initial commit

This commit is contained in:
2025-09-01 22:12:29 +02:00
parent b1873f9c1d
commit 02a54f61c0
5598 changed files with 903558 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
const memoizer = require('../lib/index.js');
const assert = require('chai').assert;
describe('lru-memoizer (bypass)', function () {
var loadTimes = 0, memoized;
beforeEach(function () {
loadTimes = 0;
memoized = memoizer({
load: function (a, b, callback) {
loadTimes++;
callback(null, a + b);
},
hash: function (a, b) {
return a + '-' + b;
},
bypass: function (a, b) {
return a < b;
},
max: 10
});
});
it('should call the load function every time', function (done) {
memoized(1, 2, function (err) {
assert.isNull(err);
assert.strictEqual(loadTimes, 1);
memoized(1, 2, function (err) {
assert.isNull(err);
assert.strictEqual(loadTimes, 2);
done();
});
});
});
});

View File

@@ -0,0 +1,44 @@
const memoizer = require('./..');
const assert = require('chai').assert;
describe('lru-memoizer (clone)', () => {
let loadTimes = 0, memoized;
beforeEach(() => {
loadTimes = 0;
memoized = memoizer({
load: (key, callback) => {
loadTimes++;
callback(null, { foo: key, buffer: Buffer.from('1234') });
},
hash: (key) => {
return key;
},
clone: true
});
});
it('should return a clone every time with the same cached structure', (done) => {
memoized('bar', (err, r1) => {
assert.isNull(err);
assert.strictEqual(loadTimes, 1);
assert.equal(r1.foo, 'bar');
r1.foo = 'bax';
memoized('bar', (err, r2) => {
assert.isNull(err);
assert.strictEqual(loadTimes, 1);
assert.equal(r2.foo, 'bar');
assert.notStrictEqual(r1, r2);
assert.notEqual(r1, r2);
done();
});
});
});
});

View File

@@ -0,0 +1,49 @@
const memoizer = require('./..');
const assert = require('chai').assert;
describe('lru-memoizer (disabled)', function () {
var loadTimes = 0, memoized;
beforeEach(function () {
loadTimes = 0;
memoized = memoizer({
disable: true,
load: function (a, b, callback) {
loadTimes++;
return setTimeout(function () {
if (a === 0) {
return callback(new Error('a cant be 0'));
}
callback(null, a+b);
}, 10);
},
hash: function (a, b) {
return a + '-' + b;
},
max: 10
});
});
it('should call the load function every time', function (done) {
memoized(1,2, function (err, result) {
assert.isNull(err);
assert.strictEqual(result, 3);
assert.strictEqual(loadTimes, 1);
memoized(1,2, function (err, result) {
assert.isNull(err);
assert.strictEqual(result, 3);
assert.strictEqual(loadTimes, 2);
done();
});
});
});
it('should expose hash function', function() {
assert.equal(memoized.hash(1, 2), '1-2');
});
});

View File

@@ -0,0 +1,132 @@
const memoizer = require('./..');
const sinon = require('sinon');
describe('lru-memoizer (events)', function () {
let memoized;
let onMiss, onHit, onQueue;
beforeEach(function () {
loadTimes = 0;
onMiss = sinon.stub();
onHit = sinon.stub();
onQueue = sinon.stub();
memoized = memoizer({
load: function (a, b, bypass, callback) {
return setTimeout(function () {
if (a === 0) {
return callback(new Error('a cant be 0'));
}
callback(null, a+b);
}, 10);
},
hash: function (a, b) {
return a + '-' + b;
},
bypass: function(a, b, bypass) {
return bypass;
},
max: 10
});
memoized.on('hit', onHit);
memoized.on('miss', onMiss);
memoized.on('queue', onQueue);
});
describe('when the result is not in the cache', () => {
beforeEach((done) => {
memoized(1, 2, false, done);
});
it('should not call onHit', () => {
sinon.assert.notCalled(onHit);
});
it('should not call onQueue', () => {
sinon.assert.notCalled(onQueue);
});
it('should call onMiss with the load arguments', () => {
sinon.assert.calledOnce(onMiss);
sinon.assert.calledWith(onMiss, 1, 2, false);
});
});
describe('when the result is in the cache', () => {
beforeEach((done) => {
memoized(1,2, false, () => {
onHit.reset();
onMiss.reset();
onQueue.reset();
memoized(1, 2, false, done);
});
});
it('should call onHit with the load arguments', () => {
sinon.assert.calledOnce(onHit);
sinon.assert.calledWith(onHit, 1, 2, false);
});
it('should not call onQueue', () => {
sinon.assert.notCalled(onQueue);
});
it('should not call onMiss', () => {
sinon.assert.notCalled(onQueue);
});
});
describe('when the cache is by passed', () => {
beforeEach((done) => {
memoized(1,2, false, () => {
onHit.reset();
onMiss.reset();
onQueue.reset();
memoized(1, 2, true, done);
});
});
it('should not call onHit', () => {
sinon.assert.notCalled(onHit);
});
it('should not call onQueue', () => {
sinon.assert.notCalled(onQueue);
});
it('should call onMiss with the load arguments', () => {
sinon.assert.calledOnce(onMiss);
sinon.assert.calledWith(onMiss, 1, 2, true);
});
});
describe('when the result is pending', () => {
beforeEach((done) => {
let pending = 2;
function onDone() {
pending -= 1;
if (pending === 0) {
done();
}
}
memoized(1, 2, false, onDone);
onHit.reset();
onMiss.reset();
onQueue.reset();
memoized(1, 2, false, onDone);
});
it('should not call onHit', () => {
sinon.assert.notCalled(onHit);
});
it('should call onQueue with the load arguments', () => {
sinon.assert.calledOnce(onQueue);
sinon.assert.calledWith(onQueue, 1, 2, false);
});
it('should not call onMiss', () => {
sinon.assert.notCalled(onMiss);
});
});
});

View File

@@ -0,0 +1,43 @@
const memoizer = require("./..");
const assert = require("chai").assert;
describe("lru-memoizer (freeze)", function () {
var loadTimes = 0,
memoized;
beforeEach(function () {
loadTimes = 0;
memoized = memoizer({
load: function (key, callback) {
loadTimes++;
callback(null, { foo: "bar", buffer: Buffer.from("1234") });
},
hash: function (key) {
return key;
},
freeze: true,
});
});
it("should return a freeze every time with the same cached structure", function (done) {
memoized("test", function (err, r1) {
assert.isNull(err);
assert.strictEqual(loadTimes, 1);
assert.equal(r1.foo, "bar");
r1.foo = "bax";
assert.isFrozen(r1);
memoized("test", function (err, r2) {
assert.isNull(err);
assert.strictEqual(loadTimes, 1);
assert.equal(r2.foo, "bar");
assert.strictEqual(r1, r2);
assert.isFrozen(r2);
done();
});
});
});
});

View File

@@ -0,0 +1,204 @@
var memoizer = require('./..');
var assert = require('chai').assert;
describe('lru-memoizer (itemMaxAge)', function () {
var loadTimes = 0, memoized;
beforeEach(function () {
loadTimes = 0;
});
it('should use default behavior if not configured', function (done) {
memoized = memoizer({
load: function (a, b, callback) {
loadTimes++;
setTimeout(function () {
callback(null, a + b);
}, 100);
},
hash: function (a, b) {
return a + '-' + b;
},
max: 10,
maxAge: 500
});
memoized(1,2, function (err, result) {
assert.isNull(err);
assert.strictEqual(result, 3);
assert.strictEqual(loadTimes, 1);
// Not expired yet.
setTimeout(function() {
memoized(1,2, function (err, result) {
assert.isNull(err);
assert.strictEqual(result, 3);
assert.strictEqual(loadTimes, 1);
// Expired, load times will increase.
setTimeout(function() {
memoized(1,2, function (err, result) {
assert.isNull(err);
assert.strictEqual(result, 3);
assert.strictEqual(loadTimes, 2);
done();
});
}, 200);
});
}, 400);
});
});
it('should return all args and the result in the itemMaxAge function', function (done) {
var args;
memoized = memoizer({
load: function (a, b, callback) {
loadTimes++;
setTimeout(function () {
callback(null, a + b);
}, 100);
},
itemMaxAge: function (a, b, result) {
args = arguments;
return 1000;
},
hash: function (a, b) {
return a + '-' + b;
},
max: 10,
maxAge: 600
});
memoized(1,2, function (err, result) {
assert.isNull(err);
assert.strictEqual(args[0], 1);
assert.strictEqual(args[1], 2);
assert.strictEqual(args[2], 3);
done();
});
});
it('should overwrite the default behavior if configured', function (done) {
var maxAge = 0;
var lastKey = null;
memoized = memoizer({
load: function (a, b, callback) {
loadTimes++;
setTimeout(function () {
callback(null, a + b);
}, 100);
},
itemMaxAge: function (a, b, result) {
lastKey = a + '-' + b;
// In this test, we set the maxAge of the current item to (result*100).
// If the result is 3, the max age of this item will be 300.
maxAge = result * 100;
return maxAge;
},
hash: function (a, b) {
return a + '-' + b;
},
max: 10,
maxAge: 600
});
memoized(1,2, function (err, result) {
assert.isNull(err);
assert.strictEqual(maxAge, 300);
assert.strictEqual(lastKey, '1-2');
assert.strictEqual(result, 3);
assert.strictEqual(loadTimes, 1);
// Not expired yet after 200 ms, because the expiration is 300
setTimeout(function() {
memoized(1,2, function (err, result) {
assert.isNull(err);
assert.strictEqual(maxAge, 300);
assert.strictEqual(lastKey, '1-2');
assert.strictEqual(result, 3);
assert.strictEqual(loadTimes, 1);
// Expired because now we are at 350 ms (even though gloabl expiration has been set to 600)
setTimeout(function() {
memoized(1,2, function (err, result) {
assert.isNull(err);
assert.strictEqual(maxAge, 300);
assert.strictEqual(lastKey, '1-2');
assert.strictEqual(result, 3);
assert.strictEqual(loadTimes, 2);
// Expired again, because 350ms have passed again.
setTimeout(function() {
memoized(1,2, function (err, result) {
assert.isNull(err);
assert.strictEqual(maxAge, 300);
assert.strictEqual(lastKey, '1-2');
assert.strictEqual(result, 3);
assert.strictEqual(loadTimes, 3);
done();
});
}, 350);
});
}, 150);
});
}, 200);
});
});
it('should overwrite the default behavior if configured (sync)', function (done) {
var maxAge = 0;
var lastKey = null;
memoized = memoizer.sync({
load: function (a, b) {
loadTimes++;
return a + b;
},
itemMaxAge: function (a, b, result) {
lastKey = a + '-' + b;
// In this test, we set the maxAge of the current item to (result*100).
// If the result is 3, the max age of this item will be 300.
maxAge = result * 100;
return maxAge;
},
hash: function (a, b) {
return a + '-' + b;
},
max: 10,
maxAge: 600
});
var result = memoized(1, 2);
assert.strictEqual(maxAge, 300);
assert.strictEqual(lastKey, '1-2');
assert.strictEqual(result, 3);
assert.strictEqual(loadTimes, 1);
// Not expired yet after 200 ms, because the expiration is 300
setTimeout(function() {
result = memoized(1, 2);
assert.strictEqual(maxAge, 300);
assert.strictEqual(lastKey, '1-2');
assert.strictEqual(result, 3);
assert.strictEqual(loadTimes, 1);
// Expired because now we are at 350 ms (even though gloabl expiration has been set to 600)
setTimeout(function() {
result = memoized(1,2);
assert.strictEqual(maxAge, 300);
assert.strictEqual(lastKey, '1-2');
assert.strictEqual(result, 3);
assert.strictEqual(loadTimes, 2);
// Expired again, because 350ms have passed again.
setTimeout(function() {
result = memoized(1,2);
assert.strictEqual(maxAge, 300);
assert.strictEqual(lastKey, '1-2');
assert.strictEqual(result, 3);
assert.strictEqual(loadTimes, 3);
done();
}, 350);
}, 150);
}, 200);
});
});

View File

@@ -0,0 +1,36 @@
const memoizer = require('./..');
const assert = require('chai').assert;
const _ = require('lodash');
describe('lru-simultaneos calls', function () {
var loadTimes = 0, memoized;
beforeEach(function () {
loadTimes = 0;
memoized = memoizer({
load: function (a, b, callback) {
loadTimes++;
setTimeout(function () {
callback(null, a + b);
}, 100);
},
hash: function (a, b) {
return a + '-' + b;
},
max: 10
});
});
it('should call once', function (done) {
memoized(1, 2, _.noop);
memoized(1, 2, _.noop);
memoized(1, 2, function (err, result) {
if (err) { return done(err); }
assert.strictEqual(loadTimes, 1);
assert.strictEqual(result, 3);
done();
});
});
});

View File

@@ -0,0 +1,43 @@
var memoizer = require('./..');
var assert = require('chai').assert;
describe('lru-memoizer (no key)', function () {
var loadTimes = 0, memoized;
beforeEach(function () {
loadTimes = 0;
memoized = memoizer({
load: function (callback) {
loadTimes++;
return setTimeout(function () {
callback(null, loadTimes);
}, 10);
}
});
});
it('should cache the result of an async function', function (done) {
memoized(function (err, result) {
assert.isNull(err);
assert.equal(result, 1);
assert.equal(loadTimes, 1);
memoized(function (err, result) {
assert.isNull(err);
assert.equal(result, 1);
assert.equal(loadTimes, 1);
done();
});
});
});
it('should use the hash function for keys', function (done) {
memoized(function () {
memoized(function () {
assert.includeMembers(memoized.keys(), ['_']);
done();
});
});
});
});

View File

@@ -0,0 +1,110 @@
var memoizer = require('./..');
var assert = require('chai').assert;
describe('lru-memoizer (queueMaxAge)', function () {
var loadTimes = 0, memoized;
beforeEach(function () {
loadTimes = 0;
});
function observer() {
const listeners = [];
return {
listen(listener) {
listeners.push(listener);
},
trigger() {
listeners.forEach(listener => listener());
}
}
}
it('should create a new queue once expired', function (done) {
memoized = memoizer({
load: function (a, b, onResolve, callback) {
loadTimes++;
onResolve(() => callback(null, a + b));
},
queueMaxAge: 10,
hash: function (a, b) {
return a + '-' + b;
}
});
const observer1 = observer();
const observer2 = observer();
const observer3 = observer();
const resolved = [];
memoized(1, 2, observer1.listen, function (err, result) {
assert.isNull(err);
assert.strictEqual(result, 3);
resolved.push('A');
});
assert.strictEqual(loadTimes, 1);
memoized(1, 2, assert.fail, function (err, result) {
assert.isNull(err);
assert.strictEqual(result, 3);
resolved.push('B');
});
assert.strictEqual(loadTimes, 1);
setTimeout(() => {
// previous queue expired, this calls will be added to a new queue.
memoized(1, 2, observer2.listen, function (err, result) {
assert.isNull(err);
assert.strictEqual(result, 3);
resolved.push('C');
});
memoized(1, 2, assert.fail, function (err, result) {
assert.isNull(err);
assert.strictEqual(result, 3);
resolved.push('D');
});
// only one new invocation to load
assert.strictEqual(loadTimes, 2);
setTimeout(() => {
// second queue expired, this calls will be added to a third queue.
memoized(1, 2, observer3.listen, function (err, result) {
assert.isNull(err);
assert.strictEqual(result, 3);
resolved.push('E');
});
memoized(1, 2, assert.fail.listen, function (err, result) {
assert.isNull(err);
assert.strictEqual(result, 3);
resolved.push('F');
});
assert.strictEqual(loadTimes, 3);
observer1.trigger();
setImmediate(() => {
// first queue was resolved
assert.deepEqual(['A', 'B'], resolved);
observer3.trigger();
setImmediate(() => {
// third queue was resolved
assert.deepEqual(['A', 'B', 'E', 'F'], resolved);
observer2.trigger();
setImmediate(() => {
// second queue was resolved
assert.deepEqual(['A', 'B', 'E', 'F', 'C', 'D'], resolved);
done();
});
});
});
}, 100);
}, 100);
});
});

View File

@@ -0,0 +1,76 @@
const memoizer = require('./..');
const assert = require('chai').assert;
describe('lru-memoizer sync (clone)', () => {
describe('call', () => {
let loadTimes = 0, memoized;
beforeEach(() => {
loadTimes = 0;
memoized = memoizer.sync({
load: (key) => {
loadTimes++;
return { foo: key , buffer: Buffer.from('1234') };
},
hash: (key) => {
return key;
},
clone: true
});
});
it('should return a clone every time with the same cached structure', () => {
const r1 = memoized('bar');
assert.strictEqual(loadTimes, 1);
assert.equal(r1.foo, 'bar');
r1.foo = 'bax';
const r2 = memoized('bar');
assert.strictEqual(loadTimes, 1);
assert.equal(r2.foo, 'bar');
assert.notStrictEqual(r1, r2);
assert.notEqual(r1, r2);
});
});
describe('Promise', () => {
let loadTimes = 0, memoized;
beforeEach(() => {
loadTimes = 0;
memoized = memoizer.sync({
load: (key) => {
loadTimes++;
return Promise.resolve({ foo: key, buffer: Buffer.from('1234') });
},
hash: (key) => {
return key;
},
clone: true
});
});
it('should return a clone every time with the same cached structure', (done) => {
memoized('bar').then(r1 => {
assert.strictEqual(loadTimes, 1);
assert.equal(r1.foo, 'bar');
r1.foo = 'bax';
memoized('bar').then(r2 => {
assert.strictEqual(loadTimes, 1);
assert.equal(r2.foo, 'bar');
assert.notStrictEqual(r1, r2);
assert.notEqual(r1, r2);
done();
});
})
.catch(done);
});
});
});

View File

@@ -0,0 +1,95 @@
const memoizer = require('./..');
const sinon = require('sinon');
describe('lru-memoizer sync (events)', function () {
let memoized;
let onMiss, onHit, onQueue;
beforeEach(function () {
loadTimes = 0;
onMiss = sinon.stub();
onHit = sinon.stub();
onQueue = sinon.stub();
memoized = memoizer.sync({
load: function (a, b, bypass) {
return a + b;
},
hash: function (a, b, bypass) {
return a + '-' + b;
},
bypass: function(a, b, bypass) {
return bypass;
},
max: 10
});
memoized.on('hit', onHit);
memoized.on('miss', onMiss);
memoized.on('queue', onQueue);
});
describe('when the result is not in the cache', () => {
beforeEach(() => {
memoized(1, 2, false);
});
it('should not call onHit', () => {
sinon.assert.notCalled(onHit);
});
it('should not call onQueue', () => {
sinon.assert.notCalled(onQueue);
});
it('should call onMiss with the load arguments', () => {
sinon.assert.calledOnce(onMiss);
sinon.assert.calledWith(onMiss, 1, 2, false);
});
});
describe('when the result is in the cache', () => {
beforeEach(() => {
memoized(1,2, false);
onHit.reset();
onMiss.reset();
onQueue.reset();
memoized(1, 2, false);
});
it('should call onHit with the load arguments', () => {
sinon.assert.calledOnce(onHit);
sinon.assert.calledWith(onHit, 1, 2, false);
});
it('should not call onQueue', () => {
sinon.assert.notCalled(onQueue);
});
it('should not call onMiss', () => {
sinon.assert.notCalled(onQueue);
});
});
describe('when the cache is by passed', () => {
beforeEach(() => {
memoized(1,2, false);
onHit.reset();
onMiss.reset();
onQueue.reset();
memoized(1, 2, true);
});
it('should not call onHit', () => {
sinon.assert.notCalled(onHit);
});
it('should not call onQueue', () => {
sinon.assert.notCalled(onQueue);
});
it('should call onMiss with the load arguments', () => {
sinon.assert.calledOnce(onMiss);
sinon.assert.calledWith(onMiss, 1, 2, true);
});
});
});

View File

@@ -0,0 +1,74 @@
const memoizer = require('./..');
const assert = require('chai').assert;
describe('lru-memoizer sync (freeze)', () => {
describe('call', () => {
let loadTimes = 0, memoized;
beforeEach(() => {
loadTimes = 0;
memoized = memoizer.sync({
load: (key) => {
loadTimes++;
return { foo: key , buffer: Buffer.from('1234') };
},
hash: (key) => {
return key;
},
freeze: true
});
});
it('should return a freeze every time with the same cached structure', () => {
const r1 = memoized('bar');
assert.strictEqual(loadTimes, 1);
assert.equal(r1.foo, 'bar');
assert.isFrozen(r1);
const r2 = memoized('bar');
assert.strictEqual(loadTimes, 1);
assert.equal(r2.foo, 'bar');
assert.isFrozen(r2);
});
});
describe('Promise', () => {
let loadTimes = 0, memoized;
beforeEach(() => {
loadTimes = 0;
memoized = memoizer.sync({
load: (key) => {
loadTimes++;
return Promise.resolve({ foo: key, buffer: Buffer.from('1234') });
},
hash: (key) => {
return key;
},
freeze: true
});
});
it('should return a freeze every time with the same cached structure', (done) => {
memoized('bar').then(r1 => {
assert.strictEqual(loadTimes, 1);
assert.equal(r1.foo, 'bar');
assert.isFrozen(r1);
memoized('bar').then(r2 => {
assert.strictEqual(loadTimes, 1);
assert.equal(r2.foo, 'bar');
assert.isFrozen(r2);
done();
});
})
.catch(done);
});
});
});

View File

@@ -0,0 +1,47 @@
var memoizer = require('./..');
var assert = require('chai').assert;
describe('lru-memoizer sync', function () {
var loadTimes = 0, memoized;
beforeEach(function () {
loadTimes = 0;
memoized = memoizer.sync({
load: function (a, b) {
loadTimes++;
if (a === 0) {
throw new Error('a cant be 0');
}
return a + b;
},
hash: function (a, b) {
return a + '-' + b;
},
max: 10
});
});
it('should cache the result of an async function', function () {
var result = memoized(1, 2);
assert.equal(result, 3);
assert.equal(loadTimes, 1);
var result2 = memoized(1,2);
assert.equal(result2, 3);
assert.equal(loadTimes, 1);
});
it('shuld use the hash function for keys', function () {
memoized(1, 2);
memoized(2, 3);
assert.includeMembers(memoized.keys(), ['1-2', '2-3']);
});
it('should not cache errored funcs', function () {
try {
memoized(0, 2);
} catch(err) {}
assert.notInclude(memoized.keys(), ['0-2']);
});
});

View File

@@ -0,0 +1,88 @@
var memoizer = require('./..');
var assert = require('chai').assert;
describe('lru-memoizer', function () {
var loadTimes = 0, memoized;
beforeEach(function () {
loadTimes = 0;
memoized = memoizer({
load: function (a, b, callback) {
loadTimes++;
return setTimeout(function () {
if (a === 0) {
return callback(new Error('a cant be 0'));
}
callback(null, a+b);
}, 10);
},
hash: function (a, b) {
return a + '-' + b;
},
max: 10
});
});
it('should cache the result of an async function', function (done) {
memoized(1,2, function (err, result) {
assert.isNull(err);
assert.strictEqual(result, 3);
assert.strictEqual(loadTimes, 1);
memoized(1,2, function (err, result) {
assert.isNull(err);
assert.strictEqual(result, 3);
assert.strictEqual(loadTimes, 1);
done();
});
});
});
it('should use the hash function for keys', function (done) {
memoized(1, 2, function () {
memoized(2,3, function () {
assert.includeMembers(memoized.keys(), ['1-2', '2-3']);
done();
});
});
});
it('should not cache errored funcs', function (done) {
memoized(0, 2, function (err) {
assert.isNotNull(err);
assert.notInclude(memoized.keys(), ['0-2']);
done();
});
});
it('should expose the hash function', function() {
assert.equal(memoized.hash(0, 2), '0-2');
});
it('should expose the load function', function(done) {
memoized.load(1, 2, (err, result) => {
assert.equal(result, 3);
done();
});
});
it('should expose the max prop', function() {
assert.equal(memoized.max, 10);
});
it('should allow to del a key', function(done) {
memoized(1,2, () => {
assert.strictEqual(loadTimes, 1);
memoized.del(1,2);
memoized(1,2, (err, result) => {
assert.isNull(err);
assert.strictEqual(result, 3);
assert.strictEqual(loadTimes, 2);
done();
});
});
});
});