-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcache.js
More file actions
164 lines (143 loc) · 3 KB
/
cache.js
File metadata and controls
164 lines (143 loc) · 3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
const DEFAULT_CACHE_SIZE = 25;
// cache node
function Node(key, value, expires) {
this.key = key;
this.value = value;
this.next = null;
this.prev = null;
this.timestamp = expires ? Date.now() : null;
}
Node.prototype.hasExpired = function (diff) {
if (diff && this.timestamp) {
return Date.now() - this.timestamp >= diff;
}
};
// in-memory LRU cache
function Cache(params = {}) {
let hash = {};
let head = null;
let tail = null;
let size = 0;
let options = {
cacheSize: DEFAULT_CACHE_SIZE,
expiresAt: null,
...params,
};
// add a new node to cache
function add(key, value) {
const node = new Node(key, value, options.expiresAt);
if (head) {
node.next = head;
head.prev = node;
}
// set the tail node
if (!tail) {
tail = node;
}
head = node;
size++;
// remove a node if we reach size limit
if (size > options.cacheSize) {
remove(tail);
}
hash[key] = node;
return node;
}
// remove the tail node
// the previous node becomes the tail
function _remove() {
if (tail) {
delete hash[tail.key];
const prev = tail.prev;
tail = prev;
// in case head/tail are the same
if (prev) {
prev.next = null;
}
size--;
}
}
function remove(node) {
if (node) {
delete hash[node.key];
// if the node is in the middle
if (node.prev) {
node.prev.next = node.next;
}
if (node.next) {
node.next.prev = node.prev;
}
// if it's the tail node
if (node === tail) {
tail = node.prev;
}
// if it's the head node
if (node === head) {
head = node.next;
}
size--;
}
}
// refresh a node, move it to top
function refresh(node) {
if (head === node) {
return;
}
// remove from current position
if (node.prev) {
node.prev.next = node.next;
}
if (node.next) {
node.next.prev = node.prev;
}
// add to top
node.next = head;
head.prev = node;
head = node;
// update tail if refreshed node is the tail node
if (tail === node) {
tail = node.prev;
}
node.prev = null;
}
// check for cached node
function find(key) {
if (key in hash) {
const node = hash[key];
if (node.hasExpired(options.expiresAt)) {
remove(node);
} else {
refresh(node);
return node;
}
}
return null;
}
// clear the cache
function clear() {
head = null;
tail = null;
size = 0;
hash = {};
// garabage collector will take care of the rest. right?
}
// pretty print cache
function print() {
let node = head;
let out = [];
while (node) {
out.push(`[${node.key}: ${node.value}]`);
node = node.next;
}
return out.join(" -> ");
}
return {
add,
find,
print,
clear,
};
}
// build cache key
Cache.generateKey = (args) => args.map((x) => JSON.stringify(x)).join("-");
module.exports = Cache;