Skip to content

Commit 1710557

Browse files
committedMar 24, 2023
cachejs
1 parent e5f6482 commit 1710557

25 files changed

+3409
-0
lines changed
 

Diff for: ‎.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
package-lock.json
3+

Diff for: ‎README.md

+222
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
## Cachejs
2+
3+
[![NPM Version][npm-version-image]][npm-url]
4+
[![NPM Install Size][npm-install-size-image]][npm-install-size-url]
5+
[![NPM Downloads][npm-downloads-image]][npm-downloads-url]
6+
7+
Cachejs is a fast and lightweight caching library for javascript.
8+
9+
## Features
10+
11+
- Super Fast
12+
- Lightweight
13+
- Multiple cache eviction policy (FIFO, LIFO, LRU, MRU)
14+
- TTL support
15+
- Custom cache-miss value
16+
17+
## Installation
18+
19+
Install using npm:
20+
21+
```console
22+
$ npm i @opensnip/cachejs
23+
```
24+
25+
Install using yarn:
26+
27+
```console
28+
$ yarn add @opensnip/cachejs
29+
```
30+
31+
## Example
32+
33+
A simple cachejs cache example:
34+
35+
```js
36+
const Cache = require("@opensnip/cachejs");
37+
38+
// Create cache object
39+
const cache = new Cache();
40+
41+
// Add data in cache
42+
cache.set("a", 10);
43+
44+
// Check data exists in cache
45+
cache.has("a");
46+
47+
// Get data from cache
48+
console.log(cache.get("a"));
49+
50+
// Get all data from cache
51+
cache.forEach(function (data) {
52+
console.log(data); // { a: 10 }
53+
});
54+
55+
// Get all data to array
56+
console.log(cache.toArray());
57+
58+
// Delete data from cache
59+
cache.delete("a");
60+
61+
// Delete all data from cache
62+
cache.clear();
63+
```
64+
65+
## Create a new cache object
66+
67+
To create a new cache we need to create a new instance of cachejs. While creating a new cache we can set the configuration like eviction policy, cache max length and ttl, but it is not mandatory and if we not set any configuration then the default values are used.
68+
69+
Defination:
70+
```js
71+
const cache = new Cache(options);
72+
```
73+
74+
Where options are the following:
75+
- `evictionPolicy` : eviction policy is can be any valid cache eviction policy, supported eviction policy are FIFO, LIFO, LRU, MRU
76+
- `maxLength` : max length is a cache max length, max length is a positive integer value. The default value is 0, if the value is 0 then it will not check the max length.
77+
- `ttl` : is cache expires time in milliseconds, the default value is 0 and if value if 0 it will not check the ttl.
78+
- `interval` : interval is the time interval in milliseconds, after every interval all the expired values are automatically removed. Default value is 0 and if value is 0 the it will not removes expired values automatically, but don't worry expired items are treated as missing, and deleted when they are fetched.
79+
- `enableInterval` : enableInterval is a boolean value that is used to enable and disable the interval, the default value is false and if value is explicitly set false then it will not run the interval even if the interval time is set.
80+
81+
Cachejs support TTL, but it is not a TTL cache, and also does not make strong TTL guarantees. When interval is set expired values are removed from cache periodically.
82+
83+
Example:
84+
```js
85+
const Cache = require("@opensnip/cachejs");
86+
87+
// Create cache object
88+
const cache = new Cache({
89+
evictionPolicy: "LRU",
90+
maxLength: 10,
91+
ttl: 100,
92+
interval: 1000,
93+
});
94+
```
95+
96+
## Set a new data
97+
98+
In cachejs any value (both objects and primitive values) may be used as either a key or a value, duplicate keys not allowed and if duplicate item is inserted it will be replaced by the new item.
99+
100+
```js
101+
// Add new data in cache
102+
cache.set("a", 10);
103+
104+
// Add new data in cache
105+
cache.set("user", { name: "abc" });
106+
107+
// Add duplicate data
108+
cache.set("a", 20); // Replace the old value
109+
110+
## Set ttl for single data
111+
112+
By default the configuration TTL value is used for every item, but we can set TTL for a single item.
113+
114+
```js
115+
// Add new data in cache
116+
cache.set("b", 10, { ttl: 200 }); // Expires after 200 ms
117+
```
118+
119+
## Get data from cache
120+
121+
By default on cache miss cachejs returns undefined value, but undefined also can be used as a value for item. In this case you can return a custom value on cache miss.
122+
123+
```js
124+
// Add new data in cache
125+
cache.set("a", 10);
126+
127+
// Get data
128+
cache.get("a"); // 10
129+
```
130+
131+
Customize cache miss value:
132+
133+
```js
134+
// Add new data in cache
135+
cache.set("a", undefined);
136+
137+
cache.get("a"); // undefined
138+
cache.get("b"); // undefined
139+
140+
// Set custom return value
141+
cache.get("a", function (err, value) {
142+
if (err) return null;
143+
return value;
144+
}); // undefined
145+
146+
cache.get("b", function (err, value) {
147+
if (err) return null;
148+
return value;
149+
}); // null
150+
```
151+
152+
## Check data exists in cache
153+
154+
Check weather item exists in the cache or not.
155+
156+
```js
157+
// Add new data in cache
158+
cache.set("a", undefined);
159+
160+
// Check data exists or not
161+
cache.has("a"); // true
162+
cache.has("b"); // false
163+
```
164+
165+
## Delete data from cache
166+
167+
Remove data from cache.
168+
169+
```js
170+
// Delete data
171+
cache.delete("a");
172+
```
173+
174+
## Delete all data from cache
175+
176+
Remove all data from the cache.
177+
178+
```js
179+
// Delete all data
180+
cache.clear();
181+
```
182+
183+
## Get all data from cache
184+
185+
Get all data from the cache.
186+
187+
```js
188+
// Add new data in cache
189+
cache.set("a", 10);
190+
191+
// Get all data
192+
cache.forEach(function (data) {
193+
console.log(data); // { a: 10 }
194+
});
195+
196+
// OR
197+
198+
for (let data of cache) {
199+
console.log(data); // { a: 10 }
200+
}
201+
```
202+
203+
## Get data as array
204+
205+
```js
206+
// Add new data in cache
207+
cache.set("a", 10);
208+
209+
// Get all data
210+
console.log(cache.toArray()); // [ { a: 10 } ]
211+
```
212+
213+
## License
214+
215+
[MIT License](https://github.com/opensnip/cachejs/blob/main/LICENSE)
216+
217+
[npm-downloads-image]: https://badgen.net/npm/dm/@opensnip/cachejs
218+
[npm-downloads-url]: https://npmcharts.com/compare/@opensnip/cachejs?minimal=true
219+
[npm-install-size-image]: https://badgen.net/packagephobia/install/@opensnip/cachejs
220+
[npm-install-size-url]: https://packagephobia.com/result?p=@opensnip/cachejs
221+
[npm-url]: https://npmjs.org/package/@opensnip/cachejs
222+
[npm-version-image]: https://badgen.net/npm/v/@opensnip/cachejs

Diff for: ‎index.cjs

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const Cache = require("./src/cache.cjs");
2+
module.exports = Cache;

Diff for: ‎index.d.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
declare type cacheEvictionPolicy = "FIFO" | "LIFO" | "LRU" | "MRU";
2+
3+
declare class Cache {
4+
length: number;
5+
constructor(options?: {
6+
evictionPolicy?: cacheEvictionPolicy;
7+
maxLength?: number;
8+
ttl?: number;
9+
interval?: number;
10+
enableInterval?: boolean;
11+
});
12+
set: (key: any, value: any, options?: { ttl?: number }) => void;
13+
startInterval: () => void;
14+
clearInterval: () => void;
15+
get: (
16+
key: any,
17+
callback?: (err: Error | undefined, value: any) => any
18+
) => any;
19+
delete: (key: any) => void;
20+
clear: () => void;
21+
has: (key: any) => boolean;
22+
forEach: (callback: (element: object, index?: number) => void) => void;
23+
toArray: () => any[];
24+
}
25+
26+
export default Cache;

Diff for: ‎index.mjs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from "./src/cache.mjs";

Diff for: ‎package.json

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"name": "@opensnip/cachejs",
3+
"version": "1.0.0",
4+
"description": "Fast and lightweight caching library for javascript",
5+
"main": "index.mjs",
6+
"type": "module",
7+
"exports": {
8+
"types": "./index.d.ts",
9+
"node": {
10+
"import": "./index.mjs",
11+
"require": "./index.cjs"
12+
},
13+
"default": "./index.cjs"
14+
},
15+
"directories": {
16+
"test": "tests"
17+
},
18+
"scripts": {
19+
"test": "jest"
20+
},
21+
"repository": {
22+
"type": "git",
23+
"url": "git+https://github.com/opensnip/cachejs.git"
24+
},
25+
"keywords": [
26+
"opensnip",
27+
"cachejs",
28+
"lru cache",
29+
"mru cache",
30+
"lfu cache",
31+
"mfu cache",
32+
"fifo cache",
33+
"lifo cache",
34+
"cache",
35+
"nodejs",
36+
"library"
37+
],
38+
"author": "Rajkumar Dusad",
39+
"license": "MIT",
40+
"types": "./index.d.ts",
41+
"bugs": {
42+
"url": "https://github.com/opensnip/cachejs/issues"
43+
},
44+
"homepage": "https://github.com/opensnip/cachejs#readme",
45+
"devDependencies": {
46+
"jest": "^29.4.2"
47+
},
48+
"files": [
49+
"src/",
50+
"LICENSE",
51+
"README.md",
52+
"index.cjs",
53+
"index.mjs",
54+
"index.d.ts"
55+
]
56+
}

Diff for: ‎src/cache.cjs

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
const FIFO = require("./fifo.cjs");
2+
const LIFO = require("./lifo.cjs");
3+
const LRU = require("./lru.cjs");
4+
const MRU = require("./mru.cjs");
5+
6+
module.exports = class Cache {
7+
#cache = null;
8+
#config = {
9+
evictionPolicy: "LRU",
10+
ttl: 0,
11+
maxLength: 0,
12+
interval: 0,
13+
intervalId: null,
14+
enableInterval: false,
15+
};
16+
17+
constructor(options = {}) {
18+
if (
19+
typeof options.evictionPolicy !== "undefined" &&
20+
typeof options.evictionPolicy !== "string"
21+
) {
22+
throw new TypeError("evictionPolicy should be string");
23+
}
24+
25+
if (
26+
(typeof options.maxLength !== "undefined" &&
27+
typeof options.maxLength !== "number") ||
28+
options.maxLength < 0
29+
) {
30+
throw new TypeError("maxLength should be positive integer value");
31+
}
32+
33+
if (
34+
(typeof options.ttl !== "undefined" && typeof options.ttl !== "number") ||
35+
options.ttl < 0
36+
) {
37+
throw new TypeError("ttl should be positive integer value");
38+
}
39+
40+
if (
41+
typeof options.enableInterval !== "undefined" &&
42+
typeof options.enableInterval !== "boolean"
43+
) {
44+
throw new TypeError("enableInterval should be boolean");
45+
}
46+
47+
if (
48+
(typeof options.interval !== "undefined" &&
49+
typeof options.interval !== "number") ||
50+
options.interval < 0
51+
) {
52+
throw new TypeError("interval should be positive integer value");
53+
}
54+
55+
options.evictionPolicy = options.evictionPolicy || "LRU";
56+
options.maxLength =
57+
typeof options.maxLength === "number" ? options.maxLength : 1000;
58+
options.ttl = typeof options.ttl === "number" ? options.ttl : 0;
59+
if (
60+
typeof options.interval === "number" &&
61+
typeof options.enableInterval !== "boolean"
62+
) {
63+
options.enableInterval = true;
64+
}
65+
options.interval =
66+
typeof options.interval === "number" ? options.interval : 1000 * 60;
67+
options.enableInterval =
68+
typeof options.enableInterval === "boolean"
69+
? options.enableInterval
70+
: false;
71+
72+
this.#config.evictionPolicy = options.evictionPolicy;
73+
this.#config.maxLength = options.maxLength;
74+
this.#config.ttl = options.ttl;
75+
this.#config.interval = options.interval;
76+
this.#config.enableInterval =
77+
options.interval > 0 ? options.enableInterval : false;
78+
79+
switch (options.evictionPolicy.toUpperCase()) {
80+
case "LRU":
81+
this.#cache = new LRU(options);
82+
break;
83+
case "MRU":
84+
this.#cache = new MRU(options);
85+
break;
86+
case "FIFO":
87+
this.#cache = new FIFO(options);
88+
break;
89+
case "LIFO":
90+
this.#cache = new LIFO(options);
91+
break;
92+
default:
93+
throw new TypeError(
94+
options.evictionPolicy + " cache eviction policy is not supported"
95+
);
96+
}
97+
// Automatically remove expires cache
98+
this.startInterval();
99+
}
100+
101+
get length() {
102+
return this.#cache.length;
103+
}
104+
105+
get(key, callback = undefined) {
106+
return this.#cache.get(key, callback);
107+
}
108+
109+
set(key, value, options = {}) {
110+
return this.#cache.set(key, value, options);
111+
}
112+
113+
delete(key) {
114+
return this.#cache.delete(key);
115+
}
116+
117+
clear() {
118+
return this.#cache.clear();
119+
}
120+
121+
startInterval() {
122+
// Interval already running
123+
if (this.#config.intervalId) return;
124+
// Interval is disabled
125+
if (!this.#config.enableInterval) return;
126+
127+
this.#config.intervalId = setInterval(
128+
function (cache) {
129+
if (cache.length == 0) return;
130+
cache.forEach(function (data) {
131+
// Automatically invalidate expired cache
132+
});
133+
},
134+
this.#config.interval,
135+
this.#cache
136+
);
137+
}
138+
139+
clearInterval() {
140+
if (this.#config.intervalId) {
141+
clearInterval(this.#config.intervalId);
142+
}
143+
}
144+
145+
has(key) {
146+
return this.#cache.has(key);
147+
}
148+
149+
forEach(callback) {
150+
return this.#cache.forEach(callback);
151+
}
152+
153+
toArray() {
154+
return this.#cache.toArray();
155+
}
156+
157+
// Iterator to iterate over cache with a 'for...of' loop
158+
*[Symbol.iterator]() {
159+
for (let cache of this.#cache) {
160+
yield cache;
161+
}
162+
}
163+
};

Diff for: ‎src/cache.mjs

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import FIFO from "./fifo.mjs";
2+
import LIFO from "./lifo.mjs";
3+
import LRU from "./lru.mjs";
4+
import MRU from "./mru.mjs";
5+
6+
export default class Cache {
7+
#cache = null;
8+
#config = {
9+
evictionPolicy: "LRU",
10+
ttl: 0,
11+
maxLength: 0,
12+
interval: 0,
13+
intervalId: null,
14+
enableInterval: false,
15+
};
16+
17+
constructor(options = {}) {
18+
if (
19+
typeof options.evictionPolicy !== "undefined" &&
20+
typeof options.evictionPolicy !== "string"
21+
) {
22+
throw new TypeError("evictionPolicy should be string");
23+
}
24+
25+
if (
26+
(typeof options.maxLength !== "undefined" &&
27+
typeof options.maxLength !== "number") ||
28+
options.maxLength < 0
29+
) {
30+
throw new TypeError("maxLength should be positive integer value");
31+
}
32+
33+
if (
34+
(typeof options.ttl !== "undefined" && typeof options.ttl !== "number") ||
35+
options.ttl < 0
36+
) {
37+
throw new TypeError("ttl should be positive integer value");
38+
}
39+
40+
if (
41+
typeof options.enableInterval !== "undefined" &&
42+
typeof options.enableInterval !== "boolean"
43+
) {
44+
throw new TypeError("enableInterval should be boolean");
45+
}
46+
47+
if (
48+
(typeof options.interval !== "undefined" &&
49+
typeof options.interval !== "number") ||
50+
options.interval < 0
51+
) {
52+
throw new TypeError("interval should be positive integer value");
53+
}
54+
55+
options.evictionPolicy = options.evictionPolicy || "LRU";
56+
options.maxLength =
57+
typeof options.maxLength === "number" ? options.maxLength : 1000;
58+
options.ttl = typeof options.ttl === "number" ? options.ttl : 0;
59+
if (
60+
typeof options.interval === "number" &&
61+
typeof options.enableInterval !== "boolean"
62+
) {
63+
options.enableInterval = true;
64+
}
65+
options.interval =
66+
typeof options.interval === "number" ? options.interval : 1000 * 60;
67+
options.enableInterval =
68+
typeof options.enableInterval === "boolean"
69+
? options.enableInterval
70+
: false;
71+
72+
this.#config.evictionPolicy = options.evictionPolicy;
73+
this.#config.maxLength = options.maxLength;
74+
this.#config.ttl = options.ttl;
75+
this.#config.interval = options.interval;
76+
this.#config.enableInterval =
77+
options.interval > 0 ? options.enableInterval : false;
78+
79+
switch (options.evictionPolicy.toUpperCase()) {
80+
case "LRU":
81+
this.#cache = new LRU(options);
82+
break;
83+
case "MRU":
84+
this.#cache = new MRU(options);
85+
break;
86+
case "FIFO":
87+
this.#cache = new FIFO(options);
88+
break;
89+
case "LIFO":
90+
this.#cache = new LIFO(options);
91+
break;
92+
default:
93+
throw new TypeError(
94+
options.evictionPolicy + " cache eviction policy is not supported"
95+
);
96+
}
97+
// Automatically remove expires cache
98+
this.startInterval();
99+
}
100+
101+
get length() {
102+
return this.#cache.length;
103+
}
104+
105+
get(key, callback = undefined) {
106+
return this.#cache.get(key, callback);
107+
}
108+
109+
set(key, value, options = {}) {
110+
return this.#cache.set(key, value, options);
111+
}
112+
113+
delete(key) {
114+
return this.#cache.delete(key);
115+
}
116+
117+
clear() {
118+
return this.#cache.clear();
119+
}
120+
121+
startInterval() {
122+
// Interval already running
123+
if (this.#config.intervalId) return;
124+
// Interval is disabled
125+
if (!this.#config.enableInterval) return;
126+
127+
this.#config.intervalId = setInterval(
128+
function (cache) {
129+
if (cache.length == 0) return;
130+
cache.forEach(function (data) {
131+
// Automatically invalidate expired cache
132+
});
133+
},
134+
this.#config.interval,
135+
this.#cache
136+
);
137+
}
138+
139+
clearInterval() {
140+
if (this.#config.intervalId) {
141+
clearInterval(this.#config.intervalId);
142+
}
143+
}
144+
145+
has(key) {
146+
return this.#cache.has(key);
147+
}
148+
149+
forEach(callback) {
150+
return this.#cache.forEach(callback);
151+
}
152+
153+
toArray() {
154+
return this.#cache.toArray();
155+
}
156+
157+
// Iterator to iterate over cache with a 'for...of' loop
158+
*[Symbol.iterator]() {
159+
for (let cache of this.#cache) {
160+
yield cache;
161+
}
162+
}
163+
}

Diff for: ‎src/fifo.cjs

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
const LinkedList = require("./linkedlist/index.cjs");
2+
const Node = require("./linkedlist/node.cjs");
3+
4+
module.exports = class FIFO {
5+
#linkedList = null;
6+
#cache = null;
7+
#ttl = 0;
8+
#maxLength = 0;
9+
10+
constructor(options = {}) {
11+
if (
12+
(typeof options.maxLength !== "undefined" &&
13+
typeof options.maxLength !== "number") ||
14+
options.maxLength < 0
15+
) {
16+
throw new TypeError("maxLength should be positive integer value");
17+
}
18+
19+
if (
20+
(typeof options.ttl !== "undefined" && typeof options.ttl !== "number") ||
21+
options.ttl < 0
22+
) {
23+
throw new TypeError("ttl should be positive integer value");
24+
}
25+
26+
if (
27+
(typeof options.interval !== "undefined" &&
28+
typeof options.interval !== "number") ||
29+
options.interval < 0
30+
) {
31+
throw new TypeError("interval should be positive integer value");
32+
}
33+
34+
options.maxLength =
35+
typeof options.maxLength === "number" ? options.maxLength : 1000;
36+
options.ttl = typeof options.ttl === "number" ? options.ttl : 0;
37+
38+
this.#linkedList = new LinkedList();
39+
this.#cache = new Map();
40+
this.#ttl = options.ttl;
41+
this.#maxLength = options.maxLength;
42+
}
43+
44+
get length() {
45+
return this.#cache.size;
46+
}
47+
48+
set(key, value, options = {}) {
49+
if (
50+
(typeof options.ttl !== "undefined" && typeof options.ttl !== "number") ||
51+
options.ttl < 0
52+
) {
53+
throw new TypeError("ttl should be positive integer value");
54+
}
55+
56+
options.ttl = typeof options.ttl === "number" ? options.ttl : this.#ttl;
57+
58+
const nodeValue = {
59+
key: key,
60+
value: value,
61+
createdAt: Date.now(),
62+
expiresAt: null,
63+
ttl: options.ttl,
64+
frequency: 0,
65+
};
66+
if (nodeValue.ttl > 0) {
67+
nodeValue.expiresAt = nodeValue.createdAt + nodeValue.ttl;
68+
}
69+
70+
// Insert a new node at head
71+
const existingNode = this.#cache.get(key);
72+
// Update node data if node is already exists
73+
if (existingNode instanceof Node) {
74+
existingNode.value = nodeValue;
75+
} else {
76+
// Remove node if cache is full
77+
if (this.length === this.#maxLength) {
78+
this.#evict();
79+
}
80+
// Create new node and make attach it to the head
81+
const node = this.#linkedList.insertHead(nodeValue);
82+
this.#cache.set(key, node);
83+
}
84+
}
85+
86+
get(key, callback = null) {
87+
try {
88+
if (callback && typeof callback !== "function") {
89+
throw new TypeError("callback should be a function");
90+
}
91+
92+
const node = this.#cache.get(key);
93+
94+
if (node instanceof Node) {
95+
// Check node is live or not
96+
if (this.#isStale(node)) {
97+
this.delete(key);
98+
throw new Error(key + " Key not found");
99+
}
100+
101+
if (callback) {
102+
return callback(null, node.value.value);
103+
} else {
104+
return node.value.value;
105+
}
106+
}
107+
108+
throw new Error(key + " Key not found");
109+
} catch (err) {
110+
if (callback) {
111+
return callback(err, undefined);
112+
} else {
113+
return;
114+
}
115+
}
116+
}
117+
118+
delete(key) {
119+
const node = this.#cache.get(key);
120+
121+
if (node instanceof Node) {
122+
this.#linkedList.delete(node);
123+
// Delete node
124+
this.#cache.delete(key);
125+
}
126+
}
127+
128+
#evict() {
129+
if (this.#linkedList.tail == null) return;
130+
if (this.length !== this.#maxLength) return;
131+
this.delete(this.#linkedList.tail.value.key);
132+
}
133+
134+
clear() {
135+
// Delete all data from cache
136+
this.#linkedList.clear();
137+
this.#cache.clear();
138+
}
139+
140+
has(key) {
141+
const node = this.#cache.get(key);
142+
143+
if (node instanceof Node) {
144+
// Check node is live or not
145+
if (this.#isStale(node)) {
146+
this.delete(key);
147+
} else {
148+
return true;
149+
}
150+
}
151+
return false;
152+
}
153+
154+
// Iterate over cache using forEach loop
155+
forEach(callback) {
156+
if (callback && typeof callback !== "function") {
157+
throw new TypeError("callback should be a function");
158+
}
159+
160+
let node = this.#linkedList.head;
161+
let index = 0;
162+
while (node) {
163+
let next = node.next;
164+
if (this.has(node.value.key)) {
165+
callback({ [node.value.key]: node.value.value }, index);
166+
}
167+
node = next;
168+
index++;
169+
}
170+
}
171+
172+
toArray() {
173+
let values = [];
174+
this.forEach(function (data) {
175+
values.push(data);
176+
});
177+
return values;
178+
}
179+
180+
#isStale(node) {
181+
if (!node.value.expiresAt) return false;
182+
return node.value.expiresAt - Date.now() <= 0;
183+
}
184+
185+
// Iterator to iterate over cache with a 'for...of' loop
186+
*[Symbol.iterator]() {
187+
let node = this.#linkedList.head;
188+
while (node) {
189+
let next = node.next;
190+
if (this.has(node.value.key)) {
191+
yield { [node.value.key]: node.value.value };
192+
}
193+
node = next;
194+
}
195+
}
196+
};

Diff for: ‎src/fifo.mjs

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import LinkedList from "./linkedlist/index.mjs";
2+
import Node from "./linkedlist/node.mjs";
3+
4+
export default class FIFO {
5+
#linkedList = null;
6+
#cache = null;
7+
#ttl = 0;
8+
#maxLength = 0;
9+
10+
constructor(options = {}) {
11+
if (
12+
(typeof options.maxLength !== "undefined" &&
13+
typeof options.maxLength !== "number") ||
14+
options.maxLength < 0
15+
) {
16+
throw new TypeError("maxLength should be positive integer value");
17+
}
18+
19+
if (
20+
(typeof options.ttl !== "undefined" && typeof options.ttl !== "number") ||
21+
options.ttl < 0
22+
) {
23+
throw new TypeError("ttl should be positive integer value");
24+
}
25+
26+
if (
27+
(typeof options.interval !== "undefined" &&
28+
typeof options.interval !== "number") ||
29+
options.interval < 0
30+
) {
31+
throw new TypeError("interval should be positive integer value");
32+
}
33+
34+
options.maxLength =
35+
typeof options.maxLength === "number" ? options.maxLength : 1000;
36+
options.ttl = typeof options.ttl === "number" ? options.ttl : 0;
37+
38+
this.#linkedList = new LinkedList();
39+
this.#cache = new Map();
40+
this.#ttl = options.ttl;
41+
this.#maxLength = options.maxLength;
42+
}
43+
44+
get length() {
45+
return this.#cache.size;
46+
}
47+
48+
set(key, value, options = {}) {
49+
if (
50+
(typeof options.ttl !== "undefined" && typeof options.ttl !== "number") ||
51+
options.ttl < 0
52+
) {
53+
throw new TypeError("ttl should be positive integer value");
54+
}
55+
56+
options.ttl = typeof options.ttl === "number" ? options.ttl : this.#ttl;
57+
58+
const nodeValue = {
59+
key: key,
60+
value: value,
61+
createdAt: Date.now(),
62+
expiresAt: null,
63+
ttl: options.ttl,
64+
frequency: 0,
65+
};
66+
if (nodeValue.ttl > 0) {
67+
nodeValue.expiresAt = nodeValue.createdAt + nodeValue.ttl;
68+
}
69+
70+
// Insert a new node at head
71+
const existingNode = this.#cache.get(key);
72+
// Update node data if node is already exists
73+
if (existingNode instanceof Node) {
74+
existingNode.value = nodeValue;
75+
} else {
76+
// Remove node if cache is full
77+
if (this.length === this.#maxLength) {
78+
this.#evict();
79+
}
80+
// Create new node and make attach it to the head
81+
const node = this.#linkedList.insertHead(nodeValue);
82+
this.#cache.set(key, node);
83+
}
84+
}
85+
86+
get(key, callback = null) {
87+
try {
88+
if (callback && typeof callback !== "function") {
89+
throw new TypeError("callback should be a function");
90+
}
91+
92+
const node = this.#cache.get(key);
93+
94+
if (node instanceof Node) {
95+
// Check node is live or not
96+
if (this.#isStale(node)) {
97+
this.delete(key);
98+
throw new Error(key + " Key not found");
99+
}
100+
101+
if (callback) {
102+
return callback(null, node.value.value);
103+
} else {
104+
return node.value.value;
105+
}
106+
}
107+
108+
throw new Error(key + " Key not found");
109+
} catch (err) {
110+
if (callback) {
111+
return callback(err, undefined);
112+
} else {
113+
return;
114+
}
115+
}
116+
}
117+
118+
delete(key) {
119+
const node = this.#cache.get(key);
120+
121+
if (node instanceof Node) {
122+
this.#linkedList.delete(node);
123+
// Delete node
124+
this.#cache.delete(key);
125+
}
126+
}
127+
128+
#evict() {
129+
if (this.#linkedList.tail == null) return;
130+
if (this.length !== this.#maxLength) return;
131+
this.delete(this.#linkedList.tail.value.key);
132+
}
133+
134+
clear() {
135+
// Delete all data from cache
136+
this.#linkedList.clear();
137+
this.#cache.clear();
138+
}
139+
140+
has(key) {
141+
const node = this.#cache.get(key);
142+
143+
if (node instanceof Node) {
144+
// Check node is live or not
145+
if (this.#isStale(node)) {
146+
this.delete(key);
147+
} else {
148+
return true;
149+
}
150+
}
151+
return false;
152+
}
153+
154+
// Iterate over cache using forEach loop
155+
forEach(callback) {
156+
if (callback && typeof callback !== "function") {
157+
throw new TypeError("callback should be a function");
158+
}
159+
160+
let node = this.#linkedList.head;
161+
let index = 0;
162+
while (node) {
163+
let next = node.next;
164+
if (this.has(node.value.key)) {
165+
callback({ [node.value.key]: node.value.value }, index);
166+
}
167+
node = next;
168+
index++;
169+
}
170+
}
171+
172+
toArray() {
173+
let values = [];
174+
this.forEach(function (data) {
175+
values.push(data);
176+
});
177+
return values;
178+
}
179+
180+
#isStale(node) {
181+
if (!node.value.expiresAt) return false;
182+
return node.value.expiresAt - Date.now() <= 0;
183+
}
184+
185+
// Iterator to iterate over cache with a 'for...of' loop
186+
*[Symbol.iterator]() {
187+
let node = this.#linkedList.head;
188+
while (node) {
189+
let next = node.next;
190+
if (this.has(node.value.key)) {
191+
yield { [node.value.key]: node.value.value };
192+
}
193+
node = next;
194+
}
195+
}
196+
}

Diff for: ‎src/lifo.cjs

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
const LinkedList = require("./linkedlist/index.cjs");
2+
const Node = require("./linkedlist/node.cjs");
3+
4+
module.exports = class LIFO {
5+
#linkedList = null;
6+
#cache = null;
7+
#ttl = 0;
8+
#maxLength = 0;
9+
10+
constructor(options = {}) {
11+
if (
12+
(typeof options.maxLength !== "undefined" &&
13+
typeof options.maxLength !== "number") ||
14+
options.maxLength < 0
15+
) {
16+
throw new TypeError("maxLength should be positive integer value");
17+
}
18+
19+
if (
20+
(typeof options.ttl !== "undefined" && typeof options.ttl !== "number") ||
21+
options.ttl < 0
22+
) {
23+
throw new TypeError("ttl should be positive integer value");
24+
}
25+
26+
if (
27+
(typeof options.interval !== "undefined" &&
28+
typeof options.interval !== "number") ||
29+
options.interval < 0
30+
) {
31+
throw new TypeError("interval should be positive integer value");
32+
}
33+
34+
options.maxLength =
35+
typeof options.maxLength === "number" ? options.maxLength : 1000;
36+
options.ttl = typeof options.ttl === "number" ? options.ttl : 0;
37+
38+
this.#linkedList = new LinkedList();
39+
this.#cache = new Map();
40+
this.#ttl = options.ttl;
41+
this.#maxLength = options.maxLength;
42+
}
43+
44+
get length() {
45+
return this.#cache.size;
46+
}
47+
48+
set(key, value, options = {}) {
49+
if (
50+
(typeof options.ttl !== "undefined" && typeof options.ttl !== "number") ||
51+
options.ttl < 0
52+
) {
53+
throw new TypeError("ttl should be positive integer value");
54+
}
55+
56+
options.ttl = typeof options.ttl === "number" ? options.ttl : this.#ttl;
57+
58+
const nodeValue = {
59+
key: key,
60+
value: value,
61+
createdAt: Date.now(),
62+
expiresAt: null,
63+
ttl: options.ttl,
64+
frequency: 0,
65+
};
66+
if (nodeValue.ttl > 0) {
67+
nodeValue.expiresAt = nodeValue.createdAt + nodeValue.ttl;
68+
}
69+
70+
// Insert a new node at head
71+
const existingNode = this.#cache.get(key);
72+
// Update node data if node is already exists
73+
if (existingNode instanceof Node) {
74+
existingNode.value = nodeValue;
75+
} else {
76+
// Remove node if cache is full
77+
if (this.length === this.#maxLength) {
78+
this.#evict();
79+
}
80+
// Create new node and make attach it to the head
81+
const node = this.#linkedList.insertHead(nodeValue);
82+
this.#cache.set(key, node);
83+
}
84+
}
85+
86+
get(key, callback = null) {
87+
try {
88+
if (callback && typeof callback !== "function") {
89+
throw new TypeError("callback should be a function");
90+
}
91+
92+
const node = this.#cache.get(key);
93+
94+
if (node instanceof Node) {
95+
// Check node is live or not
96+
if (this.#isStale(node)) {
97+
this.delete(key);
98+
throw new Error(key + " Key not found");
99+
}
100+
101+
if (callback) {
102+
return callback(null, node.value.value);
103+
} else {
104+
return node.value.value;
105+
}
106+
}
107+
108+
throw new Error(key + " Key not found");
109+
} catch (err) {
110+
if (callback) {
111+
return callback(err, undefined);
112+
} else {
113+
return;
114+
}
115+
}
116+
}
117+
118+
delete(key) {
119+
const node = this.#cache.get(key);
120+
121+
if (node instanceof Node) {
122+
this.#linkedList.delete(node);
123+
// Delete node
124+
this.#cache.delete(key);
125+
}
126+
}
127+
128+
#evict() {
129+
if (this.#linkedList.head == null) return;
130+
if (this.length !== this.#maxLength) return;
131+
this.delete(this.#linkedList.head.value.key);
132+
}
133+
134+
clear() {
135+
// Delete all data from cache
136+
this.#linkedList.clear();
137+
this.#cache.clear();
138+
}
139+
140+
has(key) {
141+
const node = this.#cache.get(key);
142+
143+
if (node instanceof Node) {
144+
// Check node is live or not
145+
if (this.#isStale(node)) {
146+
this.delete(key);
147+
} else {
148+
return true;
149+
}
150+
}
151+
return false;
152+
}
153+
154+
// Iterate over cache using forEach loop
155+
forEach(callback) {
156+
if (callback && typeof callback !== "function") {
157+
throw new TypeError("callback should be a function");
158+
}
159+
160+
let node = this.#linkedList.head;
161+
let index = 0;
162+
while (node) {
163+
let next = node.next;
164+
if (this.has(node.value.key)) {
165+
callback({ [node.value.key]: node.value.value }, index);
166+
}
167+
node = next;
168+
index++;
169+
}
170+
}
171+
172+
toArray() {
173+
let values = [];
174+
this.forEach(function (data) {
175+
values.push(data);
176+
});
177+
return values;
178+
}
179+
180+
#isStale(node) {
181+
if (!node.value.expiresAt) return false;
182+
return node.value.expiresAt - Date.now() <= 0;
183+
}
184+
185+
// Iterator to iterate over cache with a 'for...of' loop
186+
*[Symbol.iterator]() {
187+
let node = this.#linkedList.head;
188+
while (node) {
189+
let next = node.next;
190+
if (this.has(node.value.key)) {
191+
yield { [node.value.key]: node.value.value };
192+
}
193+
node = next;
194+
}
195+
}
196+
};

Diff for: ‎src/lifo.mjs

+196
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import LinkedList from "./linkedlist/index.mjs";
2+
import Node from "./linkedlist/node.mjs";
3+
4+
export default class LIFO {
5+
#linkedList = null;
6+
#cache = null;
7+
#ttl = 0;
8+
#maxLength = 0;
9+
10+
constructor(options = {}) {
11+
if (
12+
(typeof options.maxLength !== "undefined" &&
13+
typeof options.maxLength !== "number") ||
14+
options.maxLength < 0
15+
) {
16+
throw new TypeError("maxLength should be positive integer value");
17+
}
18+
19+
if (
20+
(typeof options.ttl !== "undefined" && typeof options.ttl !== "number") ||
21+
options.ttl < 0
22+
) {
23+
throw new TypeError("ttl should be positive integer value");
24+
}
25+
26+
if (
27+
(typeof options.interval !== "undefined" &&
28+
typeof options.interval !== "number") ||
29+
options.interval < 0
30+
) {
31+
throw new TypeError("interval should be positive integer value");
32+
}
33+
34+
options.maxLength =
35+
typeof options.maxLength === "number" ? options.maxLength : 1000;
36+
options.ttl = typeof options.ttl === "number" ? options.ttl : 0;
37+
38+
this.#linkedList = new LinkedList();
39+
this.#cache = new Map();
40+
this.#ttl = options.ttl;
41+
this.#maxLength = options.maxLength;
42+
}
43+
44+
get length() {
45+
return this.#cache.size;
46+
}
47+
48+
set(key, value, options = {}) {
49+
if (
50+
(typeof options.ttl !== "undefined" && typeof options.ttl !== "number") ||
51+
options.ttl < 0
52+
) {
53+
throw new TypeError("ttl should be positive integer value");
54+
}
55+
56+
options.ttl = typeof options.ttl === "number" ? options.ttl : this.#ttl;
57+
58+
const nodeValue = {
59+
key: key,
60+
value: value,
61+
createdAt: Date.now(),
62+
expiresAt: null,
63+
ttl: options.ttl,
64+
frequency: 0,
65+
};
66+
if (nodeValue.ttl > 0) {
67+
nodeValue.expiresAt = nodeValue.createdAt + nodeValue.ttl;
68+
}
69+
70+
// Insert a new node at head
71+
const existingNode = this.#cache.get(key);
72+
// Update node data if node is already exists
73+
if (existingNode instanceof Node) {
74+
existingNode.value = nodeValue;
75+
} else {
76+
// Remove node if cache is full
77+
if (this.length === this.#maxLength) {
78+
this.#evict();
79+
}
80+
// Create new node and make attach it to the head
81+
const node = this.#linkedList.insertHead(nodeValue);
82+
this.#cache.set(key, node);
83+
}
84+
}
85+
86+
get(key, callback = null) {
87+
try {
88+
if (callback && typeof callback !== "function") {
89+
throw new TypeError("callback should be a function");
90+
}
91+
92+
const node = this.#cache.get(key);
93+
94+
if (node instanceof Node) {
95+
// Check node is live or not
96+
if (this.#isStale(node)) {
97+
this.delete(key);
98+
throw new Error(key + " Key not found");
99+
}
100+
101+
if (callback) {
102+
return callback(null, node.value.value);
103+
} else {
104+
return node.value.value;
105+
}
106+
}
107+
108+
throw new Error(key + " Key not found");
109+
} catch (err) {
110+
if (callback) {
111+
return callback(err, undefined);
112+
} else {
113+
return;
114+
}
115+
}
116+
}
117+
118+
delete(key) {
119+
const node = this.#cache.get(key);
120+
121+
if (node instanceof Node) {
122+
this.#linkedList.delete(node);
123+
// Delete node
124+
this.#cache.delete(key);
125+
}
126+
}
127+
128+
#evict() {
129+
if (this.#linkedList.head == null) return;
130+
if (this.length !== this.#maxLength) return;
131+
this.delete(this.#linkedList.head.value.key);
132+
}
133+
134+
clear() {
135+
// Delete all data from cache
136+
this.#linkedList.clear();
137+
this.#cache.clear();
138+
}
139+
140+
has(key) {
141+
const node = this.#cache.get(key);
142+
143+
if (node instanceof Node) {
144+
// Check node is live or not
145+
if (this.#isStale(node)) {
146+
this.delete(key);
147+
} else {
148+
return true;
149+
}
150+
}
151+
return false;
152+
}
153+
154+
// Iterate over cache using forEach loop
155+
forEach(callback) {
156+
if (callback && typeof callback !== "function") {
157+
throw new TypeError("callback should be a function");
158+
}
159+
160+
let node = this.#linkedList.head;
161+
let index = 0;
162+
while (node) {
163+
let next = node.next;
164+
if (this.has(node.value.key)) {
165+
callback({ [node.value.key]: node.value.value }, index);
166+
}
167+
node = next;
168+
index++;
169+
}
170+
}
171+
172+
toArray() {
173+
let values = [];
174+
this.forEach(function (data) {
175+
values.push(data);
176+
});
177+
return values;
178+
}
179+
180+
#isStale(node) {
181+
if (!node.value.expiresAt) return false;
182+
return node.value.expiresAt - Date.now() <= 0;
183+
}
184+
185+
// Iterator to iterate over cache with a 'for...of' loop
186+
*[Symbol.iterator]() {
187+
let node = this.#linkedList.head;
188+
while (node) {
189+
let next = node.next;
190+
if (this.has(node.value.key)) {
191+
yield { [node.value.key]: node.value.value };
192+
}
193+
node = next;
194+
}
195+
}
196+
}

Diff for: ‎src/linkedlist/index.cjs

+252
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
const Node = require("./node.cjs");
2+
3+
module.exports = class LinkedList {
4+
#head = null;
5+
#tail = null;
6+
#length = 0;
7+
8+
get head() {
9+
return this.#head;
10+
}
11+
12+
get tail() {
13+
return this.#tail;
14+
}
15+
16+
get length() {
17+
return this.#length;
18+
}
19+
20+
clear() {
21+
this.#head = null;
22+
this.#tail = null;
23+
this.#length = 0;
24+
}
25+
26+
insertHead(value) {
27+
const node = new Node(value);
28+
if (this.#head === null) {
29+
this.#head = this.#tail = node;
30+
} else {
31+
this.#head.prev = node;
32+
node.next = this.#head;
33+
this.#head = node;
34+
}
35+
this.#length++;
36+
return node;
37+
}
38+
39+
insertTail(value) {
40+
if (this.#tail === null) {
41+
return this.insertHead(value);
42+
}
43+
44+
const node = new Node(value);
45+
this.#tail.next = node;
46+
node.prev = this.#tail;
47+
this.#tail = node;
48+
this.#length++;
49+
return node;
50+
}
51+
52+
insertAfter(node, value) {
53+
if (!(node instanceof Node)) {
54+
throw new TypeError("node should be a valid Node instance");
55+
}
56+
const newNode = new Node(value);
57+
if (node.next != null) {
58+
node.next.prev = newNode;
59+
newNode.next = node.next;
60+
}
61+
newNode.prev = node;
62+
node.next = newNode;
63+
this.#length++;
64+
return node;
65+
}
66+
67+
setHead(node) {
68+
if (!(node instanceof Node)) {
69+
throw new TypeError("node should be a valid Node instance");
70+
}
71+
72+
if (this.#head === node) return this.#head;
73+
74+
if (this.#head === null) {
75+
return this.insertHead(node.value);
76+
}
77+
78+
this.detach(node);
79+
node.prev = null;
80+
node.next = this.#head;
81+
this.#head.prev = node;
82+
this.#head = node;
83+
this.#length++;
84+
return this.#head;
85+
}
86+
87+
setTail(node) {
88+
if (!(node instanceof Node)) {
89+
throw new TypeError("node should be a valid Node instance");
90+
}
91+
92+
if (this.#tail === node) return this.#tail;
93+
94+
if (this.#tail === null) {
95+
return this.insertTail(node.value);
96+
}
97+
98+
this.detach(node);
99+
this.#tail.next = node;
100+
node.prev = this.#tail;
101+
node.next = null;
102+
this.#tail = node;
103+
this.#length++;
104+
return this.#tail;
105+
}
106+
107+
swap(leftNode, rightNode) {
108+
if (!(leftNode instanceof Node)) {
109+
throw new TypeError("leftNode should be a valid Node instance");
110+
}
111+
if (!(rightNode instanceof Node)) {
112+
throw new TypeError("rightNode should be a valid Node instance");
113+
}
114+
115+
if (leftNode === rightNode) return [leftNode, rightNode];
116+
117+
// Replace left node with right node
118+
let tmpRight = new Node(rightNode.value);
119+
if (leftNode.prev != null) {
120+
leftNode.prev.next = tmpRight;
121+
}
122+
if (leftNode.next != null) {
123+
leftNode.next.prev = tmpRight;
124+
}
125+
tmpRight.prev = leftNode.prev;
126+
tmpRight.next = leftNode.next;
127+
if (leftNode == this.#head) this.#head = tmpRight;
128+
if (leftNode == this.#tail) this.#tail = tmpRight;
129+
130+
// Replace right node with left node
131+
let tmpLeft = new Node(leftNode.value);
132+
if (rightNode.prev != null) {
133+
rightNode.prev.next = tmpLeft;
134+
}
135+
if (rightNode.next != null) {
136+
rightNode.next.prev = tmpLeft;
137+
}
138+
tmpLeft.prev = rightNode.prev;
139+
tmpLeft.next = rightNode.next;
140+
if (rightNode == this.#head) this.#head = tmpLeft;
141+
if (rightNode == this.#tail) this.#tail = tmpLeft;
142+
143+
delete leftNode.next;
144+
delete leftNode.prev;
145+
delete leftNode.value;
146+
delete rightNode.next;
147+
delete rightNode.prev;
148+
delete rightNode.value;
149+
return [tmpLeft, tmpRight];
150+
}
151+
152+
deleteHead() {
153+
if (this.#head == null) return;
154+
if (this.#head.next != null) {
155+
this.#head.next.prev = null;
156+
}
157+
delete this.#head.value;
158+
this.#head = this.#head.next;
159+
this.#length--;
160+
}
161+
162+
deleteTail() {
163+
if (this.#tail == null) return;
164+
if (this.#tail.prev != null) {
165+
this.#tail.prev.next = null;
166+
}
167+
delete this.#tail.value;
168+
this.#tail = this.#tail.prev;
169+
this.#length--;
170+
}
171+
172+
delete(node) {
173+
if (!(node instanceof Node)) {
174+
throw new TypeError("node should be a valid Node instance");
175+
}
176+
this.detach(node);
177+
delete node.prev;
178+
delete node.next;
179+
delete node.value;
180+
}
181+
182+
detach(node) {
183+
if (!(node instanceof Node)) {
184+
throw new TypeError("node should be a valid Node instance");
185+
}
186+
187+
if (node.prev != null) {
188+
node.prev.next = node.next;
189+
}
190+
if (node.next != null) {
191+
node.next.prev = node.prev;
192+
}
193+
if (this.#head === node) {
194+
this.#head = node.next;
195+
}
196+
if (this.#tail === node) {
197+
this.#tail = node.prev;
198+
}
199+
node.prev = null;
200+
node.next = null;
201+
this.#length--;
202+
}
203+
204+
search(value) {
205+
let nodes = [];
206+
this.forEach(function (node) {
207+
if (node.value === value) {
208+
nodes.push(node);
209+
}
210+
});
211+
return nodes;
212+
}
213+
214+
find(value) {
215+
for (let node of this) {
216+
if (node.value === value) {
217+
return node;
218+
}
219+
}
220+
return null;
221+
}
222+
223+
forEach(callback) {
224+
if (callback && typeof callback != "function") {
225+
throw new TypeError("callback should be a function");
226+
}
227+
228+
let node = this.#head;
229+
let index = 0;
230+
while (node) {
231+
callback(node, index);
232+
node = node.next;
233+
index++;
234+
}
235+
}
236+
237+
toArray() {
238+
let values = [];
239+
this.forEach(function (node) {
240+
values.push(node.value);
241+
});
242+
return values;
243+
}
244+
245+
*[Symbol.iterator]() {
246+
let node = this.#head;
247+
while (node) {
248+
yield node;
249+
node = node.next;
250+
}
251+
}
252+
};

Diff for: ‎src/linkedlist/index.mjs

+252
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
import Node from "./node.mjs";
2+
3+
export default class LinkedList {
4+
#head = null;
5+
#tail = null;
6+
#length = 0;
7+
8+
get head() {
9+
return this.#head;
10+
}
11+
12+
get tail() {
13+
return this.#tail;
14+
}
15+
16+
get length() {
17+
return this.#length;
18+
}
19+
20+
clear() {
21+
this.#head = null;
22+
this.#tail = null;
23+
this.#length = 0;
24+
}
25+
26+
insertHead(value) {
27+
const node = new Node(value);
28+
if (this.#head === null) {
29+
this.#head = this.#tail = node;
30+
} else {
31+
this.#head.prev = node;
32+
node.next = this.#head;
33+
this.#head = node;
34+
}
35+
this.#length++;
36+
return node;
37+
}
38+
39+
insertTail(value) {
40+
if (this.#tail === null) {
41+
return this.insertHead(value);
42+
}
43+
44+
const node = new Node(value);
45+
this.#tail.next = node;
46+
node.prev = this.#tail;
47+
this.#tail = node;
48+
this.#length++;
49+
return node;
50+
}
51+
52+
insertAfter(node, value) {
53+
if (!(node instanceof Node)) {
54+
throw new TypeError("node should be a valid Node instance");
55+
}
56+
const newNode = new Node(value);
57+
if (node.next != null) {
58+
node.next.prev = newNode;
59+
newNode.next = node.next;
60+
}
61+
newNode.prev = node;
62+
node.next = newNode;
63+
this.#length++;
64+
return node;
65+
}
66+
67+
setHead(node) {
68+
if (!(node instanceof Node)) {
69+
throw new TypeError("node should be a valid Node instance");
70+
}
71+
72+
if (this.#head === node) return this.#head;
73+
74+
if (this.#head === null) {
75+
return this.insertHead(node.value);
76+
}
77+
78+
this.detach(node);
79+
node.prev = null;
80+
node.next = this.#head;
81+
this.#head.prev = node;
82+
this.#head = node;
83+
this.#length++;
84+
return this.#head;
85+
}
86+
87+
setTail(node) {
88+
if (!(node instanceof Node)) {
89+
throw new TypeError("node should be a valid Node instance");
90+
}
91+
92+
if (this.#tail === node) return this.#tail;
93+
94+
if (this.#tail === null) {
95+
return this.insertTail(node.value);
96+
}
97+
98+
this.detach(node);
99+
this.#tail.next = node;
100+
node.prev = this.#tail;
101+
node.next = null;
102+
this.#tail = node;
103+
this.#length++;
104+
return this.#tail;
105+
}
106+
107+
swap(leftNode, rightNode) {
108+
if (!(leftNode instanceof Node)) {
109+
throw new TypeError("leftNode should be a valid Node instance");
110+
}
111+
if (!(rightNode instanceof Node)) {
112+
throw new TypeError("rightNode should be a valid Node instance");
113+
}
114+
115+
if (leftNode === rightNode) return [leftNode, rightNode];
116+
117+
// Replace left node with right node
118+
let tmpRight = new Node(rightNode.value);
119+
if (leftNode.prev != null) {
120+
leftNode.prev.next = tmpRight;
121+
}
122+
if (leftNode.next != null) {
123+
leftNode.next.prev = tmpRight;
124+
}
125+
tmpRight.prev = leftNode.prev;
126+
tmpRight.next = leftNode.next;
127+
if (leftNode == this.#head) this.#head = tmpRight;
128+
if (leftNode == this.#tail) this.#tail = tmpRight;
129+
130+
// Replace right node with left node
131+
let tmpLeft = new Node(leftNode.value);
132+
if (rightNode.prev != null) {
133+
rightNode.prev.next = tmpLeft;
134+
}
135+
if (rightNode.next != null) {
136+
rightNode.next.prev = tmpLeft;
137+
}
138+
tmpLeft.prev = rightNode.prev;
139+
tmpLeft.next = rightNode.next;
140+
if (rightNode == this.#head) this.#head = tmpLeft;
141+
if (rightNode == this.#tail) this.#tail = tmpLeft;
142+
143+
delete leftNode.next;
144+
delete leftNode.prev;
145+
delete leftNode.value;
146+
delete rightNode.next;
147+
delete rightNode.prev;
148+
delete rightNode.value;
149+
return [tmpLeft, tmpRight];
150+
}
151+
152+
deleteHead() {
153+
if (this.#head == null) return;
154+
if (this.#head.next != null) {
155+
this.#head.next.prev = null;
156+
}
157+
delete this.#head.value;
158+
this.#head = this.#head.next;
159+
this.#length--;
160+
}
161+
162+
deleteTail() {
163+
if (this.#tail == null) return;
164+
if (this.#tail.prev != null) {
165+
this.#tail.prev.next = null;
166+
}
167+
delete this.#tail.value;
168+
this.#tail = this.#tail.prev;
169+
this.#length--;
170+
}
171+
172+
delete(node) {
173+
if (!(node instanceof Node)) {
174+
throw new TypeError("node should be a valid Node instance");
175+
}
176+
this.detach(node);
177+
delete node.prev;
178+
delete node.next;
179+
delete node.value;
180+
}
181+
182+
detach(node) {
183+
if (!(node instanceof Node)) {
184+
throw new TypeError("node should be a valid Node instance");
185+
}
186+
187+
if (node.prev != null) {
188+
node.prev.next = node.next;
189+
}
190+
if (node.next != null) {
191+
node.next.prev = node.prev;
192+
}
193+
if (this.#head === node) {
194+
this.#head = node.next;
195+
}
196+
if (this.#tail === node) {
197+
this.#tail = node.prev;
198+
}
199+
node.prev = null;
200+
node.next = null;
201+
this.#length--;
202+
}
203+
204+
search(value) {
205+
let nodes = [];
206+
this.forEach(function (node) {
207+
if (node.value === value) {
208+
nodes.push(node);
209+
}
210+
});
211+
return nodes;
212+
}
213+
214+
find(value) {
215+
for (let node of this) {
216+
if (node.value === value) {
217+
return node;
218+
}
219+
}
220+
return null;
221+
}
222+
223+
forEach(callback) {
224+
if (callback && typeof callback != "function") {
225+
throw new TypeError("callback should be a function");
226+
}
227+
228+
let node = this.#head;
229+
let index = 0;
230+
while (node) {
231+
callback(node, index);
232+
node = node.next;
233+
index++;
234+
}
235+
}
236+
237+
toArray() {
238+
let values = [];
239+
this.forEach(function (node) {
240+
values.push(node.value);
241+
});
242+
return values;
243+
}
244+
245+
*[Symbol.iterator]() {
246+
let node = this.#head;
247+
while (node) {
248+
yield node;
249+
node = node.next;
250+
}
251+
}
252+
}

Diff for: ‎src/linkedlist/node.cjs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = class Node {
2+
constructor(value) {
3+
this.prev = null;
4+
this.next = null;
5+
this.value = value;
6+
}
7+
};

Diff for: ‎src/linkedlist/node.mjs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default class Node {
2+
constructor(value) {
3+
this.prev = null;
4+
this.next = null;
5+
this.value = value;
6+
}
7+
}

Diff for: ‎src/lru.cjs

+201
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
const LinkedList = require("./linkedlist/index.cjs");
2+
const Node = require("./linkedlist/node.cjs");
3+
4+
module.exports = class LRU {
5+
#linkedList = null;
6+
#cache = null;
7+
#ttl = 0;
8+
#maxLength = 0;
9+
10+
constructor(options = {}) {
11+
if (
12+
(typeof options.maxLength !== "undefined" &&
13+
typeof options.maxLength !== "number") ||
14+
options.maxLength < 0
15+
) {
16+
throw new TypeError("maxLength should be positive integer value");
17+
}
18+
19+
if (
20+
(typeof options.ttl !== "undefined" && typeof options.ttl !== "number") ||
21+
options.ttl < 0
22+
) {
23+
throw new TypeError("ttl should be positive integer value");
24+
}
25+
26+
if (
27+
(typeof options.interval !== "undefined" &&
28+
typeof options.interval !== "number") ||
29+
options.interval < 0
30+
) {
31+
throw new TypeError("interval should be positive integer value");
32+
}
33+
34+
options.maxLength =
35+
typeof options.maxLength === "number" ? options.maxLength : 1000;
36+
options.ttl = typeof options.ttl === "number" ? options.ttl : 0;
37+
38+
this.#linkedList = new LinkedList();
39+
this.#cache = new Map();
40+
this.#ttl = options.ttl;
41+
this.#maxLength = options.maxLength;
42+
}
43+
44+
get length() {
45+
return this.#cache.size;
46+
}
47+
48+
set(key, value, options = {}) {
49+
if (
50+
(typeof options.ttl !== "undefined" && typeof options.ttl !== "number") ||
51+
options.ttl < 0
52+
) {
53+
throw new TypeError("ttl should be positive integer value");
54+
}
55+
56+
options.ttl = typeof options.ttl === "number" ? options.ttl : this.#ttl;
57+
58+
const nodeValue = {
59+
key: key,
60+
value: value,
61+
createdAt: Date.now(),
62+
expiresAt: null,
63+
ttl: options.ttl,
64+
frequency: 0,
65+
};
66+
if (nodeValue.ttl > 0) {
67+
nodeValue.expiresAt = nodeValue.createdAt + nodeValue.ttl;
68+
}
69+
70+
// Insert a new node at head
71+
const existingNode = this.#cache.get(key);
72+
// Update node data if node is already exists
73+
if (existingNode instanceof Node) {
74+
existingNode.value = nodeValue;
75+
// Move current node to the head
76+
this.#linkedList.setHead(existingNode);
77+
} else {
78+
// Remove node if cache is full
79+
if (this.length === this.#maxLength) {
80+
this.#evict();
81+
}
82+
// Create new node and make attach it to the head
83+
const node = this.#linkedList.insertHead(nodeValue);
84+
this.#cache.set(key, node);
85+
}
86+
}
87+
88+
get(key, callback = null) {
89+
try {
90+
if (callback && typeof callback !== "function") {
91+
throw new TypeError("callback should be a function");
92+
}
93+
94+
const node = this.#cache.get(key);
95+
96+
if (node instanceof Node) {
97+
// Check node is live or not
98+
if (this.#isStale(node)) {
99+
this.delete(key);
100+
throw new Error(key + " Key not found");
101+
}
102+
103+
// Move current node to the head
104+
this.#linkedList.setHead(node);
105+
106+
if (callback) {
107+
return callback(null, node.value.value);
108+
} else {
109+
return node.value.value;
110+
}
111+
}
112+
113+
throw new Error(key + " Key not found");
114+
} catch (err) {
115+
if (callback) {
116+
return callback(err, undefined);
117+
} else {
118+
return;
119+
}
120+
}
121+
}
122+
123+
delete(key) {
124+
const node = this.#cache.get(key);
125+
126+
if (node instanceof Node) {
127+
this.#linkedList.delete(node);
128+
// Delete node
129+
this.#cache.delete(key);
130+
}
131+
}
132+
133+
#evict() {
134+
if (this.#linkedList.tail == null) return;
135+
if (this.length !== this.#maxLength) return;
136+
this.delete(this.#linkedList.tail.value.key);
137+
}
138+
139+
clear() {
140+
// Delete all data from cache
141+
this.#linkedList.clear();
142+
this.#cache.clear();
143+
}
144+
145+
has(key) {
146+
const node = this.#cache.get(key);
147+
148+
if (node instanceof Node) {
149+
// Check node is live or not
150+
if (this.#isStale(node)) {
151+
this.delete(key);
152+
} else {
153+
return true;
154+
}
155+
}
156+
return false;
157+
}
158+
159+
// Iterate over cache using forEach loop
160+
forEach(callback) {
161+
if (callback && typeof callback !== "function") {
162+
throw new TypeError("callback should be a function");
163+
}
164+
165+
let node = this.#linkedList.head;
166+
let index = 0;
167+
while (node) {
168+
let next = node.next;
169+
if (this.has(node.value.key)) {
170+
callback({ [node.value.key]: node.value.value }, index);
171+
}
172+
node = next;
173+
index++;
174+
}
175+
}
176+
177+
toArray() {
178+
let values = [];
179+
this.forEach(function (data) {
180+
values.push(data);
181+
});
182+
return values;
183+
}
184+
185+
#isStale(node) {
186+
if (!node.value.expiresAt) return false;
187+
return node.value.expiresAt - Date.now() <= 0;
188+
}
189+
190+
// Iterator to iterate over cache with a 'for...of' loop
191+
*[Symbol.iterator]() {
192+
let node = this.#linkedList.head;
193+
while (node) {
194+
let next = node.next;
195+
if (this.has(node.value.key)) {
196+
yield { [node.value.key]: node.value.value };
197+
}
198+
node = next;
199+
}
200+
}
201+
};

Diff for: ‎src/lru.mjs

+201
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import LinkedList from "./linkedlist/index.mjs";
2+
import Node from "./linkedlist/node.mjs";
3+
4+
export default class LRU {
5+
#linkedList = null;
6+
#cache = null;
7+
#ttl = 0;
8+
#maxLength = 0;
9+
10+
constructor(options = {}) {
11+
if (
12+
(typeof options.maxLength !== "undefined" &&
13+
typeof options.maxLength !== "number") ||
14+
options.maxLength < 0
15+
) {
16+
throw new TypeError("maxLength should be positive integer value");
17+
}
18+
19+
if (
20+
(typeof options.ttl !== "undefined" && typeof options.ttl !== "number") ||
21+
options.ttl < 0
22+
) {
23+
throw new TypeError("ttl should be positive integer value");
24+
}
25+
26+
if (
27+
(typeof options.interval !== "undefined" &&
28+
typeof options.interval !== "number") ||
29+
options.interval < 0
30+
) {
31+
throw new TypeError("interval should be positive integer value");
32+
}
33+
34+
options.maxLength =
35+
typeof options.maxLength === "number" ? options.maxLength : 1000;
36+
options.ttl = typeof options.ttl === "number" ? options.ttl : 0;
37+
38+
this.#linkedList = new LinkedList();
39+
this.#cache = new Map();
40+
this.#ttl = options.ttl;
41+
this.#maxLength = options.maxLength;
42+
}
43+
44+
get length() {
45+
return this.#cache.size;
46+
}
47+
48+
set(key, value, options = {}) {
49+
if (
50+
(typeof options.ttl !== "undefined" && typeof options.ttl !== "number") ||
51+
options.ttl < 0
52+
) {
53+
throw new TypeError("ttl should be positive integer value");
54+
}
55+
56+
options.ttl = typeof options.ttl === "number" ? options.ttl : this.#ttl;
57+
58+
const nodeValue = {
59+
key: key,
60+
value: value,
61+
createdAt: Date.now(),
62+
expiresAt: null,
63+
ttl: options.ttl,
64+
frequency: 0,
65+
};
66+
if (nodeValue.ttl > 0) {
67+
nodeValue.expiresAt = nodeValue.createdAt + nodeValue.ttl;
68+
}
69+
70+
// Insert a new node at head
71+
const existingNode = this.#cache.get(key);
72+
// Update node data if node is already exists
73+
if (existingNode instanceof Node) {
74+
existingNode.value = nodeValue;
75+
// Move current node to the head
76+
this.#linkedList.setHead(existingNode);
77+
} else {
78+
// Remove node if cache is full
79+
if (this.length === this.#maxLength) {
80+
this.#evict();
81+
}
82+
// Create new node and make attach it to the head
83+
const node = this.#linkedList.insertHead(nodeValue);
84+
this.#cache.set(key, node);
85+
}
86+
}
87+
88+
get(key, callback = null) {
89+
try {
90+
if (callback && typeof callback !== "function") {
91+
throw new TypeError("callback should be a function");
92+
}
93+
94+
const node = this.#cache.get(key);
95+
96+
if (node instanceof Node) {
97+
// Check node is live or not
98+
if (this.#isStale(node)) {
99+
this.delete(key);
100+
throw new Error(key + " Key not found");
101+
}
102+
103+
// Move current node to the head
104+
this.#linkedList.setHead(node);
105+
106+
if (callback) {
107+
return callback(null, node.value.value);
108+
} else {
109+
return node.value.value;
110+
}
111+
}
112+
113+
throw new Error(key + " Key not found");
114+
} catch (err) {
115+
if (callback) {
116+
return callback(err, undefined);
117+
} else {
118+
return;
119+
}
120+
}
121+
}
122+
123+
delete(key) {
124+
const node = this.#cache.get(key);
125+
126+
if (node instanceof Node) {
127+
this.#linkedList.delete(node);
128+
// Delete node
129+
this.#cache.delete(key);
130+
}
131+
}
132+
133+
#evict() {
134+
if (this.#linkedList.tail == null) return;
135+
if (this.length !== this.#maxLength) return;
136+
this.delete(this.#linkedList.tail.value.key);
137+
}
138+
139+
clear() {
140+
// Delete all data from cache
141+
this.#linkedList.clear();
142+
this.#cache.clear();
143+
}
144+
145+
has(key) {
146+
const node = this.#cache.get(key);
147+
148+
if (node instanceof Node) {
149+
// Check node is live or not
150+
if (this.#isStale(node)) {
151+
this.delete(key);
152+
} else {
153+
return true;
154+
}
155+
}
156+
return false;
157+
}
158+
159+
// Iterate over cache using forEach loop
160+
forEach(callback) {
161+
if (callback && typeof callback !== "function") {
162+
throw new TypeError("callback should be a function");
163+
}
164+
165+
let node = this.#linkedList.head;
166+
let index = 0;
167+
while (node) {
168+
let next = node.next;
169+
if (this.has(node.value.key)) {
170+
callback({ [node.value.key]: node.value.value }, index);
171+
}
172+
node = next;
173+
index++;
174+
}
175+
}
176+
177+
toArray() {
178+
let values = [];
179+
this.forEach(function (data) {
180+
values.push(data);
181+
});
182+
return values;
183+
}
184+
185+
#isStale(node) {
186+
if (!node.value.expiresAt) return false;
187+
return node.value.expiresAt - Date.now() <= 0;
188+
}
189+
190+
// Iterator to iterate over cache with a 'for...of' loop
191+
*[Symbol.iterator]() {
192+
let node = this.#linkedList.head;
193+
while (node) {
194+
let next = node.next;
195+
if (this.has(node.value.key)) {
196+
yield { [node.value.key]: node.value.value };
197+
}
198+
node = next;
199+
}
200+
}
201+
}

Diff for: ‎src/mru.cjs

+201
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
const LinkedList = require("./linkedlist/index.cjs");
2+
const Node = require("./linkedlist/node.cjs");
3+
4+
module.exports = class MRU {
5+
#linkedList = null;
6+
#cache = null;
7+
#ttl = 0;
8+
#maxLength = 0;
9+
10+
constructor(options = {}) {
11+
if (
12+
(typeof options.maxLength !== "undefined" &&
13+
typeof options.maxLength !== "number") ||
14+
options.maxLength < 0
15+
) {
16+
throw new TypeError("maxLength should be positive integer value");
17+
}
18+
19+
if (
20+
(typeof options.ttl !== "undefined" && typeof options.ttl !== "number") ||
21+
options.ttl < 0
22+
) {
23+
throw new TypeError("ttl should be positive integer value");
24+
}
25+
26+
if (
27+
(typeof options.interval !== "undefined" &&
28+
typeof options.interval !== "number") ||
29+
options.interval < 0
30+
) {
31+
throw new TypeError("interval should be positive integer value");
32+
}
33+
34+
options.maxLength =
35+
typeof options.maxLength === "number" ? options.maxLength : 1000;
36+
options.ttl = typeof options.ttl === "number" ? options.ttl : 0;
37+
38+
this.#linkedList = new LinkedList();
39+
this.#cache = new Map();
40+
this.#ttl = options.ttl;
41+
this.#maxLength = options.maxLength;
42+
}
43+
44+
get length() {
45+
return this.#cache.size;
46+
}
47+
48+
set(key, value, options = {}) {
49+
if (
50+
(typeof options.ttl !== "undefined" && typeof options.ttl !== "number") ||
51+
options.ttl < 0
52+
) {
53+
throw new TypeError("ttl should be positive integer value");
54+
}
55+
56+
options.ttl = typeof options.ttl === "number" ? options.ttl : this.#ttl;
57+
58+
const nodeValue = {
59+
key: key,
60+
value: value,
61+
createdAt: Date.now(),
62+
expiresAt: null,
63+
ttl: options.ttl,
64+
frequency: 0,
65+
};
66+
if (nodeValue.ttl > 0) {
67+
nodeValue.expiresAt = nodeValue.createdAt + nodeValue.ttl;
68+
}
69+
70+
// Insert a new node at head
71+
const existingNode = this.#cache.get(key);
72+
// Update node data if node is already exists
73+
if (existingNode instanceof Node) {
74+
existingNode.value = nodeValue;
75+
// Move current node to the head
76+
this.#linkedList.setHead(existingNode);
77+
} else {
78+
// Remove node if cache is full
79+
if (this.length === this.#maxLength) {
80+
this.#evict();
81+
}
82+
// Create new node and make attach it to the head
83+
const node = this.#linkedList.insertHead(nodeValue);
84+
this.#cache.set(key, node);
85+
}
86+
}
87+
88+
get(key, callback = null) {
89+
try {
90+
if (callback && typeof callback !== "function") {
91+
throw new TypeError("callback should be a function");
92+
}
93+
94+
const node = this.#cache.get(key);
95+
96+
if (node instanceof Node) {
97+
// Check node is live or not
98+
if (this.#isStale(node)) {
99+
this.delete(key);
100+
throw new Error(key + " Key not found");
101+
}
102+
103+
// Move current node to the head
104+
this.#linkedList.setHead(node);
105+
106+
if (callback) {
107+
return callback(null, node.value.value);
108+
} else {
109+
return node.value.value;
110+
}
111+
}
112+
113+
throw new Error(key + " Key not found");
114+
} catch (err) {
115+
if (callback) {
116+
return callback(err, undefined);
117+
} else {
118+
return;
119+
}
120+
}
121+
}
122+
123+
delete(key) {
124+
const node = this.#cache.get(key);
125+
126+
if (node instanceof Node) {
127+
this.#linkedList.delete(node);
128+
// Delete node
129+
this.#cache.delete(key);
130+
}
131+
}
132+
133+
#evict() {
134+
if (this.#linkedList.head == null) return;
135+
if (this.length !== this.#maxLength) return;
136+
this.delete(this.#linkedList.head.value.key);
137+
}
138+
139+
clear() {
140+
// Delete all data from cache
141+
this.#linkedList.clear();
142+
this.#cache.clear();
143+
}
144+
145+
has(key) {
146+
const node = this.#cache.get(key);
147+
148+
if (node instanceof Node) {
149+
// Check node is live or not
150+
if (this.#isStale(node)) {
151+
this.delete(key);
152+
} else {
153+
return true;
154+
}
155+
}
156+
return false;
157+
}
158+
159+
// Iterate over cache using forEach loop
160+
forEach(callback) {
161+
if (callback && typeof callback !== "function") {
162+
throw new TypeError("callback should be a function");
163+
}
164+
165+
let node = this.#linkedList.head;
166+
let index = 0;
167+
while (node) {
168+
let next = node.next;
169+
if (this.has(node.value.key)) {
170+
callback({ [node.value.key]: node.value.value }, index);
171+
}
172+
node = next;
173+
index++;
174+
}
175+
}
176+
177+
toArray() {
178+
let values = [];
179+
this.forEach(function (data) {
180+
values.push(data);
181+
});
182+
return values;
183+
}
184+
185+
#isStale(node) {
186+
if (!node.value.expiresAt) return false;
187+
return node.value.expiresAt - Date.now() <= 0;
188+
}
189+
190+
// Iterator to iterate over cache with a 'for...of' loop
191+
*[Symbol.iterator]() {
192+
let node = this.#linkedList.head;
193+
while (node) {
194+
let next = node.next;
195+
if (this.has(node.value.key)) {
196+
yield { [node.value.key]: node.value.value };
197+
}
198+
node = next;
199+
}
200+
}
201+
};

Diff for: ‎src/mru.mjs

+201
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import LinkedList from "./linkedlist/index.mjs";
2+
import Node from "./linkedlist/node.mjs";
3+
4+
export default class MRU {
5+
#linkedList = null;
6+
#cache = null;
7+
#ttl = 0;
8+
#maxLength = 0;
9+
10+
constructor(options = {}) {
11+
if (
12+
(typeof options.maxLength !== "undefined" &&
13+
typeof options.maxLength !== "number") ||
14+
options.maxLength < 0
15+
) {
16+
throw new TypeError("maxLength should be positive integer value");
17+
}
18+
19+
if (
20+
(typeof options.ttl !== "undefined" && typeof options.ttl !== "number") ||
21+
options.ttl < 0
22+
) {
23+
throw new TypeError("ttl should be positive integer value");
24+
}
25+
26+
if (
27+
(typeof options.interval !== "undefined" &&
28+
typeof options.interval !== "number") ||
29+
options.interval < 0
30+
) {
31+
throw new TypeError("interval should be positive integer value");
32+
}
33+
34+
options.maxLength =
35+
typeof options.maxLength === "number" ? options.maxLength : 1000;
36+
options.ttl = typeof options.ttl === "number" ? options.ttl : 0;
37+
38+
this.#linkedList = new LinkedList();
39+
this.#cache = new Map();
40+
this.#ttl = options.ttl;
41+
this.#maxLength = options.maxLength;
42+
}
43+
44+
get length() {
45+
return this.#cache.size;
46+
}
47+
48+
set(key, value, options = {}) {
49+
if (
50+
(typeof options.ttl !== "undefined" && typeof options.ttl !== "number") ||
51+
options.ttl < 0
52+
) {
53+
throw new TypeError("ttl should be positive integer value");
54+
}
55+
56+
options.ttl = typeof options.ttl === "number" ? options.ttl : this.#ttl;
57+
58+
const nodeValue = {
59+
key: key,
60+
value: value,
61+
createdAt: Date.now(),
62+
expiresAt: null,
63+
ttl: options.ttl,
64+
frequency: 0,
65+
};
66+
if (nodeValue.ttl > 0) {
67+
nodeValue.expiresAt = nodeValue.createdAt + nodeValue.ttl;
68+
}
69+
70+
// Insert a new node at head
71+
const existingNode = this.#cache.get(key);
72+
// Update node data if node is already exists
73+
if (existingNode instanceof Node) {
74+
existingNode.value = nodeValue;
75+
// Move current node to the head
76+
this.#linkedList.setHead(existingNode);
77+
} else {
78+
// Remove node if cache is full
79+
if (this.length === this.#maxLength) {
80+
this.#evict();
81+
}
82+
// Create new node and make attach it to the head
83+
const node = this.#linkedList.insertHead(nodeValue);
84+
this.#cache.set(key, node);
85+
}
86+
}
87+
88+
get(key, callback = null) {
89+
try {
90+
if (callback && typeof callback !== "function") {
91+
throw new TypeError("callback should be a function");
92+
}
93+
94+
const node = this.#cache.get(key);
95+
96+
if (node instanceof Node) {
97+
// Check node is live or not
98+
if (this.#isStale(node)) {
99+
this.delete(key);
100+
throw new Error(key + " Key not found");
101+
}
102+
103+
// Move current node to the head
104+
this.#linkedList.setHead(node);
105+
106+
if (callback) {
107+
return callback(null, node.value.value);
108+
} else {
109+
return node.value.value;
110+
}
111+
}
112+
113+
throw new Error(key + " Key not found");
114+
} catch (err) {
115+
if (callback) {
116+
return callback(err, undefined);
117+
} else {
118+
return;
119+
}
120+
}
121+
}
122+
123+
delete(key) {
124+
const node = this.#cache.get(key);
125+
126+
if (node instanceof Node) {
127+
this.#linkedList.delete(node);
128+
// Delete node
129+
this.#cache.delete(key);
130+
}
131+
}
132+
133+
#evict() {
134+
if (this.#linkedList.head == null) return;
135+
if (this.length !== this.#maxLength) return;
136+
this.delete(this.#linkedList.head.value.key);
137+
}
138+
139+
clear() {
140+
// Delete all data from cache
141+
this.#linkedList.clear();
142+
this.#cache.clear();
143+
}
144+
145+
has(key) {
146+
const node = this.#cache.get(key);
147+
148+
if (node instanceof Node) {
149+
// Check node is live or not
150+
if (this.#isStale(node)) {
151+
this.delete(key);
152+
} else {
153+
return true;
154+
}
155+
}
156+
return false;
157+
}
158+
159+
// Iterate over cache using forEach loop
160+
forEach(callback) {
161+
if (callback && typeof callback !== "function") {
162+
throw new TypeError("callback should be a function");
163+
}
164+
165+
let node = this.#linkedList.head;
166+
let index = 0;
167+
while (node) {
168+
let next = node.next;
169+
if (this.has(node.value.key)) {
170+
callback({ [node.value.key]: node.value.value }, index);
171+
}
172+
node = next;
173+
index++;
174+
}
175+
}
176+
177+
toArray() {
178+
let values = [];
179+
this.forEach(function (data) {
180+
values.push(data);
181+
});
182+
return values;
183+
}
184+
185+
#isStale(node) {
186+
if (!node.value.expiresAt) return false;
187+
return node.value.expiresAt - Date.now() <= 0;
188+
}
189+
190+
// Iterator to iterate over cache with a 'for...of' loop
191+
*[Symbol.iterator]() {
192+
let node = this.#linkedList.head;
193+
while (node) {
194+
let next = node.next;
195+
if (this.has(node.value.key)) {
196+
yield { [node.value.key]: node.value.value };
197+
}
198+
node = next;
199+
}
200+
}
201+
}

Diff for: ‎tests/fifo.test.js

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
const Cache = require("../index.cjs");
2+
3+
describe("FIFO Cache test", () => {
4+
let cache = null;
5+
beforeEach(() => {
6+
cache = new Cache({ maxLength: 4, ttl: 100, evictionPolicy: "FIFO" });
7+
});
8+
9+
test("Set data in cache", () => {
10+
cache.set("a", 10);
11+
expect(cache.has("a")).toEqual(true);
12+
});
13+
14+
test("Set max data in cache", () => {
15+
cache.set("a", 10);
16+
cache.set("b", 20);
17+
cache.set("c", 30);
18+
cache.set("d", 40);
19+
cache.set("e", 50);
20+
expect(cache.has("a")).toEqual(false);
21+
});
22+
23+
test("Get data from cache", () => {
24+
cache.set("a", 10);
25+
expect(cache.get("a")).toEqual(10);
26+
cache.set("b", { a: 10, b: 20 });
27+
expect(cache.get("b")).toEqual({ a: 10, b: 20 });
28+
expect(cache.get("c")).toEqual(undefined);
29+
});
30+
31+
test("Change get return value using callback function", () => {
32+
cache.set("a", undefined);
33+
expect(
34+
cache.get("a", (err, value) => {
35+
if (!err) return value;
36+
return null;
37+
})
38+
).toEqual(undefined);
39+
expect(
40+
cache.get("b", (err, value) => {
41+
if (!err) return value;
42+
return null;
43+
})
44+
).toEqual(null);
45+
});
46+
47+
test("Check data exists in cache", () => {
48+
cache.set("a", 10);
49+
expect(cache.has("a")).toEqual(true);
50+
expect(cache.has("b")).toEqual(false);
51+
});
52+
53+
test("Delete data from cache", () => {
54+
cache.set("a", 10);
55+
expect(cache.has("a")).toEqual(true);
56+
cache.delete("a");
57+
expect(cache.has("a")).toEqual(false);
58+
});
59+
60+
test("Delete all data from cache", () => {
61+
cache.set("a", 10);
62+
cache.set("b", 20);
63+
expect(cache.has("a")).toEqual(true);
64+
cache.clear();
65+
expect(cache.has("a")).toEqual(false);
66+
});
67+
68+
test("Evict data from cache", () => {
69+
cache.set("a", 10);
70+
cache.set("b", 20);
71+
cache.set("c", 30);
72+
cache.set("d", 40);
73+
cache.set("e", 50);
74+
expect(cache.toArray()).toEqual([
75+
{ e: 50 },
76+
{ d: 40 },
77+
{ c: 30 },
78+
{ b: 20 },
79+
]);
80+
});
81+
82+
test("Evict data from cache", () => {
83+
cache.set("a", 10);
84+
cache.set("b", 20);
85+
cache.set("c", 30);
86+
cache.set("d", 40);
87+
cache.get("a");
88+
cache.set("e", 50);
89+
expect(cache.toArray()).toEqual([
90+
{ e: 50 },
91+
{ d: 40 },
92+
{ c: 30 },
93+
{ b: 20 },
94+
]);
95+
});
96+
97+
test("Evict data from cache", () => {
98+
cache.set("a", 10);
99+
cache.set("b", 20);
100+
cache.set("c", 30);
101+
cache.set("d", 40);
102+
cache.get("d");
103+
cache.set("e", 50);
104+
expect(cache.toArray()).toEqual([
105+
{ e: 50 },
106+
{ d: 40 },
107+
{ c: 30 },
108+
{ b: 20 },
109+
]);
110+
});
111+
112+
test("Check ttl for cache", async () => {
113+
cache.set("a", 10);
114+
cache.set("b", 20, { ttl: 0 });
115+
expect(cache.has("a")).toEqual(true);
116+
expect(cache.has("b")).toEqual(true);
117+
await new Promise((resolve, reject) => {
118+
setTimeout(() => resolve(), 200);
119+
});
120+
expect(cache.has("a")).toEqual(false);
121+
expect(cache.has("b")).toEqual(true);
122+
});
123+
124+
test("Get all data as array", async () => {
125+
cache.set("a", 10);
126+
cache.set("b", 20, { ttl: 0 });
127+
expect(cache.toArray()).toEqual([{ b: 20 }, { a: 10 }]);
128+
await new Promise((resolve, reject) => {
129+
setTimeout(() => resolve(), 200);
130+
});
131+
expect(cache.toArray()).toEqual([{ b: 20 }]);
132+
});
133+
});

Diff for: ‎tests/lifo.test.js

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
const Cache = require("../index.cjs");
2+
3+
describe("LIFO Cache test", () => {
4+
let cache = null;
5+
beforeEach(() => {
6+
cache = new Cache({ maxLength: 4, ttl: 100, evictionPolicy: "LIFO" });
7+
});
8+
9+
test("Set data in cache", () => {
10+
cache.set("a", 10);
11+
expect(cache.has("a")).toEqual(true);
12+
});
13+
14+
test("Set max data in cache", () => {
15+
cache.set("a", 10);
16+
cache.set("b", 20);
17+
cache.set("c", 30);
18+
cache.set("d", 40);
19+
cache.set("e", 50);
20+
expect(cache.has("d")).toEqual(false);
21+
});
22+
23+
test("Get data from cache", () => {
24+
cache.set("a", 10);
25+
expect(cache.get("a")).toEqual(10);
26+
cache.set("b", { a: 10, b: 20 });
27+
expect(cache.get("b")).toEqual({ a: 10, b: 20 });
28+
expect(cache.get("c")).toEqual(undefined);
29+
});
30+
31+
test("Change get return value using callback function", () => {
32+
cache.set("a", undefined);
33+
expect(
34+
cache.get("a", (err, value) => {
35+
if (!err) return value;
36+
return null;
37+
})
38+
).toEqual(undefined);
39+
expect(
40+
cache.get("b", (err, value) => {
41+
if (!err) return value;
42+
return null;
43+
})
44+
).toEqual(null);
45+
});
46+
47+
test("Check data exists in cache", () => {
48+
cache.set("a", 10);
49+
expect(cache.has("a")).toEqual(true);
50+
expect(cache.has("b")).toEqual(false);
51+
});
52+
53+
test("Delete data from cache", () => {
54+
cache.set("a", 10);
55+
expect(cache.has("a")).toEqual(true);
56+
cache.delete("a");
57+
expect(cache.has("a")).toEqual(false);
58+
});
59+
60+
test("Delete all data from cache", () => {
61+
cache.set("a", 10);
62+
cache.set("b", 20);
63+
expect(cache.has("a")).toEqual(true);
64+
cache.clear();
65+
expect(cache.has("a")).toEqual(false);
66+
});
67+
68+
test("Evict data from cache", () => {
69+
cache.set("a", 10);
70+
cache.set("b", 20);
71+
cache.set("c", 30);
72+
cache.set("d", 40);
73+
cache.set("e", 50);
74+
expect(cache.toArray()).toEqual([
75+
{ e: 50 },
76+
{ c: 30 },
77+
{ b: 20 },
78+
{ a: 10 },
79+
]);
80+
});
81+
82+
test("Evict data from cache", () => {
83+
cache.set("a", 10);
84+
cache.set("b", 20);
85+
cache.set("c", 30);
86+
cache.set("d", 40);
87+
cache.get("a");
88+
cache.set("e", 50);
89+
expect(cache.toArray()).toEqual([
90+
{ e: 50 },
91+
{ c: 30 },
92+
{ b: 20 },
93+
{ a: 10 },
94+
]);
95+
});
96+
97+
test("Evict data from cache", () => {
98+
cache.set("a", 10);
99+
cache.set("b", 20);
100+
cache.set("c", 30);
101+
cache.set("d", 40);
102+
cache.get("d");
103+
cache.set("e", 50);
104+
expect(cache.toArray()).toEqual([
105+
{ e: 50 },
106+
{ c: 30 },
107+
{ b: 20 },
108+
{ a: 10 },
109+
]);
110+
});
111+
112+
test("Check ttl for cache", async () => {
113+
cache.set("a", 10);
114+
cache.set("b", 20, { ttl: 0 });
115+
expect(cache.has("a")).toEqual(true);
116+
expect(cache.has("b")).toEqual(true);
117+
await new Promise((resolve, reject) => {
118+
setTimeout(() => resolve(), 200);
119+
});
120+
expect(cache.has("a")).toEqual(false);
121+
expect(cache.has("b")).toEqual(true);
122+
});
123+
124+
test("Get all data as array", async () => {
125+
cache.set("a", 10);
126+
cache.set("b", 20, { ttl: 0 });
127+
expect(cache.toArray()).toEqual([{ b: 20 }, { a: 10 }]);
128+
await new Promise((resolve, reject) => {
129+
setTimeout(() => resolve(), 200);
130+
});
131+
expect(cache.toArray()).toEqual([{ b: 20 }]);
132+
});
133+
});

Diff for: ‎tests/linkedlist.test.js

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
const LinkedList = require("../src/linkedlist/index.cjs");
2+
3+
describe("Linkedlist test", () => {
4+
let linkedlist = null;
5+
beforeEach(() => {
6+
linkedlist = new LinkedList();
7+
});
8+
9+
test("Set data at head in linked list", () => {
10+
linkedlist.insertHead(10);
11+
linkedlist.insertHead(15);
12+
linkedlist.insertHead(20);
13+
expect(linkedlist.toArray()).toEqual([20, 15, 10]);
14+
});
15+
16+
test("Set data at the end of linked list", () => {
17+
linkedlist.insertTail(10);
18+
linkedlist.insertTail(15);
19+
linkedlist.insertTail(20);
20+
expect(linkedlist.toArray()).toEqual([10, 15, 20]);
21+
});
22+
23+
test("Set data after a node", () => {
24+
linkedlist.insertHead(10);
25+
linkedlist.insertTail(20);
26+
let node = linkedlist.find(10);
27+
linkedlist.insertAfter(node, 15);
28+
expect(linkedlist.toArray()).toEqual([10, 15, 20]);
29+
});
30+
31+
test("Set head", () => {
32+
linkedlist.insertHead(10);
33+
linkedlist.insertHead(15);
34+
linkedlist.insertHead(20);
35+
let node = linkedlist.find(15);
36+
linkedlist.setHead(node);
37+
expect(linkedlist.toArray()).toEqual([15, 20, 10]);
38+
});
39+
40+
test("Set tail", () => {
41+
linkedlist.insertHead(10);
42+
linkedlist.insertHead(15);
43+
linkedlist.insertHead(20);
44+
let node = linkedlist.find(15);
45+
linkedlist.setTail(node);
46+
expect(linkedlist.toArray()).toEqual([20, 10, 15]);
47+
});
48+
49+
test("Swap nodes", () => {
50+
linkedlist.insertTail(10);
51+
linkedlist.insertTail(15);
52+
linkedlist.insertTail(20);
53+
linkedlist.insertTail(25);
54+
linkedlist.insertTail(30);
55+
let left = linkedlist.find(10);
56+
let right = linkedlist.find(25);
57+
linkedlist.swap(left, right);
58+
expect(linkedlist.toArray()).toEqual([25, 15, 20, 10, 30]);
59+
});
60+
61+
test("Swap neighbor nodes", () => {
62+
linkedlist.insertTail(10);
63+
linkedlist.insertTail(15);
64+
let left = linkedlist.find(10);
65+
let right = linkedlist.find(15);
66+
linkedlist.swap(left, right);
67+
expect(linkedlist.toArray()).toEqual([15, 10]);
68+
});
69+
70+
test("Swap single node", () => {
71+
linkedlist.insertTail(10);
72+
let left = linkedlist.find(10);
73+
linkedlist.swap(left, left);
74+
expect(linkedlist.toArray()).toEqual([10]);
75+
});
76+
77+
test("Delete head", () => {
78+
linkedlist.insertHead(10);
79+
linkedlist.insertHead(15);
80+
linkedlist.insertHead(20);
81+
linkedlist.deleteHead();
82+
expect(linkedlist.toArray()).toEqual([15, 10]);
83+
});
84+
85+
test("Delete tail", () => {
86+
linkedlist.insertHead(10);
87+
linkedlist.insertHead(15);
88+
linkedlist.insertHead(20);
89+
linkedlist.deleteTail();
90+
expect(linkedlist.toArray()).toEqual([20, 15]);
91+
});
92+
93+
test("Delete node", () => {
94+
linkedlist.insertHead(10);
95+
linkedlist.insertHead(15);
96+
linkedlist.insertHead(20);
97+
let node = linkedlist.find(15);
98+
linkedlist.delete(node);
99+
expect(linkedlist.toArray()).toEqual([20, 10]);
100+
});
101+
102+
test("Detach node", () => {
103+
linkedlist.insertHead(10);
104+
linkedlist.insertHead(15);
105+
linkedlist.insertHead(20);
106+
let node = linkedlist.find(15);
107+
linkedlist.detach(node);
108+
expect(linkedlist.toArray()).toEqual([20, 10]);
109+
});
110+
111+
test("Search nodes by value", () => {
112+
linkedlist.insertHead(10);
113+
linkedlist.insertHead(15);
114+
linkedlist.insertHead(10);
115+
linkedlist.insertHead(20);
116+
let nodes = linkedlist.search(10);
117+
expect(nodes.map((e) => e.value)).toEqual([10, 10]);
118+
});
119+
120+
test("Find a first node by value", () => {
121+
linkedlist.insertHead(10);
122+
linkedlist.insertHead(15);
123+
linkedlist.insertHead(10);
124+
linkedlist.insertHead(20);
125+
let node = linkedlist.find(10);
126+
expect(node.value).toEqual(10);
127+
});
128+
129+
test("Get all values as array", () => {
130+
linkedlist.insertHead(10);
131+
linkedlist.insertHead(15);
132+
linkedlist.insertHead(20);
133+
expect(linkedlist.toArray()).toEqual([20, 15, 10]);
134+
});
135+
});

Diff for: ‎tests/lru.test.js

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
const Cache = require("../index.cjs");
2+
3+
describe("LRU Cache test", () => {
4+
let cache = null;
5+
beforeEach(() => {
6+
cache = new Cache({ maxLength: 4, ttl: 100, evictionPolicy: "LRU" });
7+
});
8+
9+
test("Set data in cache", () => {
10+
cache.set("a", 10);
11+
expect(cache.has("a")).toEqual(true);
12+
});
13+
14+
test("Set max data in cache", () => {
15+
cache.set("a", 10);
16+
cache.set("b", 20);
17+
cache.set("c", 30);
18+
cache.set("d", 40);
19+
cache.set("e", 50);
20+
expect(cache.has("a")).toEqual(false);
21+
});
22+
23+
test("Get data from cache", () => {
24+
cache.set("a", 10);
25+
expect(cache.get("a")).toEqual(10);
26+
cache.set("b", { a: 10, b: 20 });
27+
expect(cache.get("b")).toEqual({ a: 10, b: 20 });
28+
expect(cache.get("c")).toEqual(undefined);
29+
});
30+
31+
test("Change get return value using callback function", () => {
32+
cache.set("a", undefined);
33+
expect(
34+
cache.get("a", (err, value) => {
35+
if (!err) return value;
36+
return null;
37+
})
38+
).toEqual(undefined);
39+
expect(
40+
cache.get("b", (err, value) => {
41+
if (!err) return value;
42+
return null;
43+
})
44+
).toEqual(null);
45+
});
46+
47+
test("Check data exists in cache", () => {
48+
cache.set("a", 10);
49+
expect(cache.has("a")).toEqual(true);
50+
expect(cache.has("b")).toEqual(false);
51+
});
52+
53+
test("Delete data from cache", () => {
54+
cache.set("a", 10);
55+
expect(cache.has("a")).toEqual(true);
56+
cache.delete("a");
57+
expect(cache.has("a")).toEqual(false);
58+
});
59+
60+
test("Delete all data from cache", () => {
61+
cache.set("a", 10);
62+
cache.set("b", 20);
63+
expect(cache.has("a")).toEqual(true);
64+
cache.clear();
65+
expect(cache.has("a")).toEqual(false);
66+
});
67+
68+
test("Evict data from cache", () => {
69+
cache.set("a", 10);
70+
cache.set("b", 20);
71+
cache.set("c", 30);
72+
cache.set("d", 40);
73+
cache.set("e", 50);
74+
expect(cache.toArray()).toEqual([
75+
{ e: 50 },
76+
{ d: 40 },
77+
{ c: 30 },
78+
{ b: 20 },
79+
]);
80+
});
81+
82+
test("Evict data from cache", () => {
83+
cache.set("a", 10);
84+
cache.set("b", 20);
85+
cache.set("c", 30);
86+
cache.set("d", 40);
87+
cache.get("a");
88+
cache.set("e", 50);
89+
expect(cache.toArray()).toEqual([
90+
{ e: 50 },
91+
{ a: 10 },
92+
{ d: 40 },
93+
{ c: 30 },
94+
]);
95+
});
96+
97+
test("Evict data from cache", () => {
98+
cache.set("a", 10);
99+
cache.set("b", 20);
100+
cache.set("c", 30);
101+
cache.set("d", 40);
102+
cache.get("d");
103+
cache.set("e", 50);
104+
expect(cache.toArray()).toEqual([
105+
{ e: 50 },
106+
{ d: 40 },
107+
{ c: 30 },
108+
{ b: 20 },
109+
]);
110+
});
111+
112+
test("Check ttl for cache", async () => {
113+
cache.set("a", 10);
114+
cache.set("b", 20, { ttl: 0 });
115+
expect(cache.has("a")).toEqual(true);
116+
expect(cache.has("b")).toEqual(true);
117+
await new Promise((resolve, reject) => {
118+
setTimeout(() => resolve(), 200);
119+
});
120+
expect(cache.has("a")).toEqual(false);
121+
expect(cache.has("b")).toEqual(true);
122+
});
123+
124+
test("Get all data as array", async () => {
125+
cache.set("a", 10);
126+
cache.set("b", 20, { ttl: 0 });
127+
expect(cache.toArray()).toEqual([{ b: 20 }, { a: 10 }]);
128+
await new Promise((resolve, reject) => {
129+
setTimeout(() => resolve(), 200);
130+
});
131+
expect(cache.toArray()).toEqual([{ b: 20 }]);
132+
});
133+
});

0 commit comments

Comments
 (0)
Please sign in to comment.