@isaacs/ttlcache 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/index.d.ts +9 -4
- package/index.js +58 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ Custom size calculation is not supported. Max capacity is simply the count
|
|
|
29
29
|
of items in the cache.
|
|
30
30
|
|
|
31
31
|
```js
|
|
32
|
-
const TTLCache = require('ttlcache')
|
|
32
|
+
const TTLCache = require('@isaacs/ttlcache')
|
|
33
33
|
const cache = new TTLCache({ max: 10000, ttl: 1000 })
|
|
34
34
|
|
|
35
35
|
// set some value
|
|
@@ -44,13 +44,26 @@ cache.get(1) // returns undefined
|
|
|
44
44
|
cache.has(1) // returns false
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
## Caveat Regarding Timers and Graceful Exits
|
|
48
|
+
|
|
49
|
+
On Node.js, this module uses the `Timeout.unref()` method to
|
|
50
|
+
prevent its internal `setTimeout` calls from keeping the process
|
|
51
|
+
running indefinitely. However, on other systems such as Deno,
|
|
52
|
+
where the `setTimeout` method does not return an object with an
|
|
53
|
+
`unref()` method, the process will stay open as long as any
|
|
54
|
+
unexpired entry exists in the cache.
|
|
55
|
+
|
|
56
|
+
You may delete all entries (by using `cache.clear()` or
|
|
57
|
+
`cache.delete(key)` with every key) in order to clear the
|
|
58
|
+
timeouts and allow the process to exit normally.
|
|
59
|
+
|
|
47
60
|
## API
|
|
48
61
|
|
|
49
62
|
### `const TTLCache = require('@isaacs/ttlcache')` or `import TTLCache from '@isaacs/ttlcache'`
|
|
50
63
|
|
|
51
64
|
Default export is the `TTLCache` class.
|
|
52
65
|
|
|
53
|
-
### `new TTLCache({ ttl, max = Infinty, updateAgeOnGet = false, noUpdateTTL = false })`
|
|
66
|
+
### `new TTLCache({ ttl, max = Infinty, updateAgeOnGet = false, noUpdateTTL = false, noDisposeOnSet = false })`
|
|
54
67
|
|
|
55
68
|
Create a new `TTLCache` object.
|
|
56
69
|
|
package/index.d.ts
CHANGED
|
@@ -135,6 +135,13 @@ declare namespace TTLCache {
|
|
|
135
135
|
*/
|
|
136
136
|
updateAgeOnGet?: boolean
|
|
137
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Do not call dispose() function when overwriting a key with a new value
|
|
140
|
+
*
|
|
141
|
+
* @default false
|
|
142
|
+
*/
|
|
143
|
+
noDisposeOnSet?: boolean
|
|
144
|
+
|
|
138
145
|
/**
|
|
139
146
|
* Function that is called on items when they are dropped from the cache.
|
|
140
147
|
* This can be handy if you want to close file descriptors or do other
|
|
@@ -149,10 +156,8 @@ declare namespace TTLCache {
|
|
|
149
156
|
|
|
150
157
|
type SetOptions = {
|
|
151
158
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* @default false
|
|
159
|
+
* Do not call dispose() function when overwriting a key with a new value
|
|
160
|
+
* Overrides the value set in the constructor.
|
|
156
161
|
*/
|
|
157
162
|
noDisposeOnSet?: boolean
|
|
158
163
|
|
package/index.js
CHANGED
|
@@ -3,15 +3,13 @@
|
|
|
3
3
|
// Relies on the fact that integer Object keys are kept sorted,
|
|
4
4
|
// and managed very efficiently by V8.
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const timeProvider = maybeReqPerfHooks(Date)
|
|
14
|
-
const now = () => timeProvider.now()
|
|
6
|
+
const perf =
|
|
7
|
+
typeof performance === 'object' &&
|
|
8
|
+
performance &&
|
|
9
|
+
typeof performance.now === 'function'
|
|
10
|
+
? performance
|
|
11
|
+
: Date
|
|
12
|
+
const now = () => perf.now()
|
|
15
13
|
const isPosInt = n => n && n === Math.floor(n) && n > 0 && isFinite(n)
|
|
16
14
|
const isPosIntOrInf = n => n === Infinity || isPosInt(n)
|
|
17
15
|
|
|
@@ -22,6 +20,7 @@ class TTLCache {
|
|
|
22
20
|
updateAgeOnGet = false,
|
|
23
21
|
noUpdateTTL = false,
|
|
24
22
|
dispose,
|
|
23
|
+
noDisposeOnSet = false,
|
|
25
24
|
} = {}) {
|
|
26
25
|
// {[expirationTime]: [keys]}
|
|
27
26
|
this.expirations = Object.create(null)
|
|
@@ -41,19 +40,35 @@ class TTLCache {
|
|
|
41
40
|
this.max = max
|
|
42
41
|
this.updateAgeOnGet = updateAgeOnGet
|
|
43
42
|
this.noUpdateTTL = noUpdateTTL
|
|
43
|
+
this.noDisposeOnSet = noDisposeOnSet
|
|
44
44
|
if (dispose !== undefined) {
|
|
45
45
|
if (typeof dispose !== 'function') {
|
|
46
46
|
throw new TypeError('dispose must be function if set')
|
|
47
47
|
}
|
|
48
48
|
this.dispose = dispose
|
|
49
49
|
}
|
|
50
|
+
|
|
51
|
+
this.timers = new Set()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// hang onto the timer so we can clearTimeout if all items
|
|
55
|
+
// are deleted. Deno doesn't have Timer.unref(), so it
|
|
56
|
+
// hangs otherwise.
|
|
57
|
+
cancelTimers() {
|
|
58
|
+
for (const t of this.timers) {
|
|
59
|
+
clearTimeout(t)
|
|
60
|
+
this.timers.delete(t)
|
|
61
|
+
}
|
|
50
62
|
}
|
|
51
63
|
|
|
64
|
+
|
|
52
65
|
clear() {
|
|
53
66
|
const entries =
|
|
54
67
|
this.dispose !== TTLCache.prototype.dispose ? [...this] : []
|
|
55
68
|
this.data.clear()
|
|
56
69
|
this.expirationMap.clear()
|
|
70
|
+
// no need for any purging now
|
|
71
|
+
this.cancelTimers()
|
|
57
72
|
this.expirations = Object.create(null)
|
|
58
73
|
for (const [key, val] of entries) {
|
|
59
74
|
this.dispose(val, key, 'delete')
|
|
@@ -76,9 +91,13 @@ class TTLCache {
|
|
|
76
91
|
const expiration = Math.floor(now() + ttl)
|
|
77
92
|
this.expirationMap.set(key, expiration)
|
|
78
93
|
if (!this.expirations[expiration]) {
|
|
79
|
-
const t = setTimeout(() =>
|
|
94
|
+
const t = setTimeout(() => {
|
|
95
|
+
this.timers.delete(t)
|
|
96
|
+
this.purgeStale()
|
|
97
|
+
}, ttl)
|
|
80
98
|
/* istanbul ignore else - affordance for non-node envs */
|
|
81
99
|
if (t.unref) t.unref()
|
|
100
|
+
this.timers.add(t)
|
|
82
101
|
this.expirations[expiration] = []
|
|
83
102
|
}
|
|
84
103
|
this.expirations[expiration].push(key)
|
|
@@ -156,12 +175,17 @@ class TTLCache {
|
|
|
156
175
|
this.data.delete(key)
|
|
157
176
|
this.expirationMap.delete(key)
|
|
158
177
|
const exp = this.expirations[current]
|
|
159
|
-
if (exp
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
178
|
+
if (exp) {
|
|
179
|
+
if (exp.length <= 1) {
|
|
180
|
+
delete this.expirations[current]
|
|
181
|
+
} else {
|
|
182
|
+
this.expirations[current] = exp.filter(k => k !== key)
|
|
183
|
+
}
|
|
163
184
|
}
|
|
164
185
|
this.dispose(value, key, 'delete')
|
|
186
|
+
if (this.size === 0) {
|
|
187
|
+
this.cancelTimers()
|
|
188
|
+
}
|
|
165
189
|
return true
|
|
166
190
|
}
|
|
167
191
|
return false
|
|
@@ -171,23 +195,29 @@ class TTLCache {
|
|
|
171
195
|
for (const exp in this.expirations) {
|
|
172
196
|
const keys = this.expirations[exp]
|
|
173
197
|
if (this.size - keys.length >= this.max) {
|
|
198
|
+
delete this.expirations[exp]
|
|
199
|
+
const entries = []
|
|
174
200
|
for (const key of keys) {
|
|
175
|
-
|
|
201
|
+
entries.push([key, this.data.get(key)])
|
|
176
202
|
this.data.delete(key)
|
|
177
203
|
this.expirationMap.delete(key)
|
|
204
|
+
}
|
|
205
|
+
for (const [key, val] of entries) {
|
|
178
206
|
this.dispose(val, key, 'evict')
|
|
179
207
|
}
|
|
180
|
-
delete this.expirations[exp]
|
|
181
208
|
} else {
|
|
182
209
|
const s = this.size - this.max
|
|
210
|
+
const entries = []
|
|
183
211
|
for (const key of keys.splice(0, s)) {
|
|
184
|
-
|
|
212
|
+
entries.push([key, this.data.get(key)])
|
|
185
213
|
this.data.delete(key)
|
|
186
214
|
this.expirationMap.delete(key)
|
|
215
|
+
}
|
|
216
|
+
for (const [key, val] of entries) {
|
|
187
217
|
this.dispose(val, key, 'evict')
|
|
188
218
|
}
|
|
219
|
+
return
|
|
189
220
|
}
|
|
190
|
-
return
|
|
191
221
|
}
|
|
192
222
|
}
|
|
193
223
|
|
|
@@ -201,13 +231,20 @@ class TTLCache {
|
|
|
201
231
|
if (exp === 'Infinity' || exp > n) {
|
|
202
232
|
return
|
|
203
233
|
}
|
|
204
|
-
|
|
205
|
-
|
|
234
|
+
const keys = [...this.expirations[exp]]
|
|
235
|
+
const entries = []
|
|
236
|
+
delete this.expirations[exp]
|
|
237
|
+
for (const key of keys) {
|
|
238
|
+
entries.push([key, this.data.get(key)])
|
|
206
239
|
this.data.delete(key)
|
|
207
240
|
this.expirationMap.delete(key)
|
|
241
|
+
}
|
|
242
|
+
for (const [key, val] of entries) {
|
|
208
243
|
this.dispose(val, key, 'stale')
|
|
209
244
|
}
|
|
210
|
-
|
|
245
|
+
}
|
|
246
|
+
if (this.size === 0) {
|
|
247
|
+
this.cancelTimers()
|
|
211
248
|
}
|
|
212
249
|
}
|
|
213
250
|
|