@isaacs/ttlcache 1.1.0 → 1.2.0
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 +28 -2
- package/index.d.ts +14 -4
- package/index.js +28 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,9 +54,14 @@ Default export is the `TTLCache` class.
|
|
|
54
54
|
|
|
55
55
|
Create a new `TTLCache` object.
|
|
56
56
|
|
|
57
|
-
* `max` The max number of items to keep in the cache.
|
|
57
|
+
* `max` The max number of items to keep in the cache. Must be
|
|
58
|
+
positive integer or `Infinity`, defaults to `Infinity` (ie,
|
|
59
|
+
limited only by TTL, not by item count).
|
|
58
60
|
* `ttl` The max time in ms to store items. Overridable on the `set()`
|
|
59
|
-
method.
|
|
61
|
+
method. Must be a positive integer or `Infinity` (see note
|
|
62
|
+
below about immortality hazards). If `undefined` in
|
|
63
|
+
constructor, then a TTL _must_ be provided in each `set()`
|
|
64
|
+
call.
|
|
60
65
|
* `updateAgeOnGet` Should the age of an item be updated when it is
|
|
61
66
|
retrieved? Defaults to `false`. Overridable on the `get()` method.
|
|
62
67
|
* `noUpdateTTL` Should setting a new value for an existing key leave the
|
|
@@ -104,6 +109,13 @@ If `updateAgeOnGet` is `true`, then re-add the item into the cache with the
|
|
|
104
109
|
updated `ttl` value. Both options default to the settings on the
|
|
105
110
|
constructor.
|
|
106
111
|
|
|
112
|
+
Note that using `updateAgeOnGet` _can_ effectively simulate a
|
|
113
|
+
"least-recently-used" type of algorithm, by repeatedly updating
|
|
114
|
+
the TTL of items as they are used. However, if you find yourself
|
|
115
|
+
doing this, consider using
|
|
116
|
+
[`lru-cache`](http://npm.im/lru-cache), as it is much more
|
|
117
|
+
optimized for an LRU use case.
|
|
118
|
+
|
|
107
119
|
### `cache.getRemainingTTL(key)`
|
|
108
120
|
|
|
109
121
|
Return the remaining time before an item expires. Returns `0` if the item
|
|
@@ -188,3 +200,17 @@ the current time.
|
|
|
188
200
|
Thus, the `start` time doesn't need to be tracked, only the expiration
|
|
189
201
|
time. When an item age is updated (either explicitly on `get()`, or by
|
|
190
202
|
setting to a new value), it is deleted and re-inserted.
|
|
203
|
+
|
|
204
|
+
## Immortality Hazards
|
|
205
|
+
|
|
206
|
+
It is possible to set a TTL of `Infinity`, in which case an item
|
|
207
|
+
will never expire. As it does not expire, its TTL is not
|
|
208
|
+
tracked, and `getRemainingTTL()` will return `Infinity` for that
|
|
209
|
+
key.
|
|
210
|
+
|
|
211
|
+
If you do this, then the item will never be purged. Create
|
|
212
|
+
enough immortal values, and the cache will grow to consume all
|
|
213
|
+
available memory. If find yourself doing this, it's _probably_
|
|
214
|
+
better to use a different data structure, such as a `Map` or
|
|
215
|
+
plain old object to store values, as it will have better
|
|
216
|
+
performance and the hazards will be more obvious.
|
package/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// https://github.com/isaacs/node-lru-cache/blob/v7.10.1/index.d.ts
|
|
5
5
|
|
|
6
6
|
declare class TTLCache<K, V> implements Iterable<[K, V]> {
|
|
7
|
-
constructor(options
|
|
7
|
+
constructor(options?: TTLCache.Options<K, V>)
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* The total number of items held in the cache at the current moment.
|
|
@@ -104,9 +104,10 @@ declare namespace TTLCache {
|
|
|
104
104
|
* by default, and MAY live in the cache, contributing to max,
|
|
105
105
|
* long after they have expired.
|
|
106
106
|
*
|
|
107
|
-
* Must be an integer number of ms,
|
|
107
|
+
* Must be an integer number of ms, or Infinity. Defaults to `undefined`,
|
|
108
|
+
* meaning that a TTL must be set explicitly for each set()
|
|
108
109
|
*/
|
|
109
|
-
ttl
|
|
110
|
+
ttl?: number
|
|
110
111
|
|
|
111
112
|
/**
|
|
112
113
|
* Boolean flag to tell the cache to not update the TTL when
|
|
@@ -154,13 +155,22 @@ declare namespace TTLCache {
|
|
|
154
155
|
* @default false
|
|
155
156
|
*/
|
|
156
157
|
noDisposeOnSet?: boolean
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Do not update the TTL when overwriting an existing item.
|
|
161
|
+
*/
|
|
157
162
|
noUpdateTTL?: boolean
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Override the default TTL for this one set() operation.
|
|
166
|
+
* Required if a TTL was not set in the constructor options.
|
|
167
|
+
*/
|
|
158
168
|
ttl?: number
|
|
159
169
|
}
|
|
160
170
|
|
|
161
171
|
type GetOptions = {
|
|
162
172
|
/**
|
|
163
|
-
* Update the age of
|
|
173
|
+
* Update the age of item being retrieved.
|
|
164
174
|
*/
|
|
165
175
|
updateAgeOnGet?: boolean
|
|
166
176
|
|
package/index.js
CHANGED
|
@@ -10,8 +10,10 @@ const maybeReqPerfHooks = fallback => {
|
|
|
10
10
|
return fallback
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
const
|
|
13
|
+
const timeProvider = maybeReqPerfHooks(Date)
|
|
14
|
+
const now = () => timeProvider.now()
|
|
14
15
|
const isPosInt = n => n && n === Math.floor(n) && n > 0 && isFinite(n)
|
|
16
|
+
const isPosIntOrInf = n => n === Infinity || isPosInt(n)
|
|
15
17
|
|
|
16
18
|
class TTLCache {
|
|
17
19
|
constructor({
|
|
@@ -20,17 +22,19 @@ class TTLCache {
|
|
|
20
22
|
updateAgeOnGet = false,
|
|
21
23
|
noUpdateTTL = false,
|
|
22
24
|
dispose,
|
|
23
|
-
}) {
|
|
25
|
+
} = {}) {
|
|
24
26
|
// {[expirationTime]: [keys]}
|
|
25
27
|
this.expirations = Object.create(null)
|
|
26
28
|
// {key=>val}
|
|
27
29
|
this.data = new Map()
|
|
28
30
|
// {key=>expiration}
|
|
29
31
|
this.expirationMap = new Map()
|
|
30
|
-
if (ttl !== undefined && !
|
|
31
|
-
throw new TypeError(
|
|
32
|
+
if (ttl !== undefined && !isPosIntOrInf(ttl)) {
|
|
33
|
+
throw new TypeError(
|
|
34
|
+
'ttl must be positive integer or Infinity if set'
|
|
35
|
+
)
|
|
32
36
|
}
|
|
33
|
-
if (!
|
|
37
|
+
if (!isPosIntOrInf(max)) {
|
|
34
38
|
throw new TypeError('max must be positive integer or Infinity')
|
|
35
39
|
}
|
|
36
40
|
this.ttl = ttl
|
|
@@ -56,7 +60,7 @@ class TTLCache {
|
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
62
|
|
|
59
|
-
setTTL
|
|
63
|
+
setTTL(key, ttl = this.ttl) {
|
|
60
64
|
const current = this.expirationMap.get(key)
|
|
61
65
|
if (current !== undefined) {
|
|
62
66
|
// remove from the expirations list, so it isn't purged
|
|
@@ -68,15 +72,19 @@ class TTLCache {
|
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
if (ttl !== Infinity) {
|
|
76
|
+
const expiration = Math.floor(now() + ttl)
|
|
77
|
+
this.expirationMap.set(key, expiration)
|
|
78
|
+
if (!this.expirations[expiration]) {
|
|
79
|
+
const t = setTimeout(() => this.purgeStale(), ttl)
|
|
80
|
+
/* istanbul ignore else - affordance for non-node envs */
|
|
81
|
+
if (t.unref) t.unref()
|
|
82
|
+
this.expirations[expiration] = []
|
|
83
|
+
}
|
|
84
|
+
this.expirations[expiration].push(key)
|
|
85
|
+
} else {
|
|
86
|
+
this.expirationMap.set(key, Infinity)
|
|
78
87
|
}
|
|
79
|
-
this.expirations[expiration].push(key)
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
set(
|
|
@@ -88,8 +96,8 @@ class TTLCache {
|
|
|
88
96
|
noDisposeOnSet = this.noDisposeOnSet,
|
|
89
97
|
} = {}
|
|
90
98
|
) {
|
|
91
|
-
if (!
|
|
92
|
-
throw new TypeError('ttl must be positive integer')
|
|
99
|
+
if (!isPosIntOrInf(ttl)) {
|
|
100
|
+
throw new TypeError('ttl must be positive integer or Infinity')
|
|
93
101
|
}
|
|
94
102
|
if (this.expirationMap.has(key)) {
|
|
95
103
|
if (!noUpdateTTL) {
|
|
@@ -121,7 +129,9 @@ class TTLCache {
|
|
|
121
129
|
|
|
122
130
|
getRemainingTTL(key) {
|
|
123
131
|
const expiration = this.expirationMap.get(key)
|
|
124
|
-
return expiration
|
|
132
|
+
return expiration === Infinity
|
|
133
|
+
? expiration
|
|
134
|
+
: expiration !== undefined
|
|
125
135
|
? Math.max(0, Math.ceil(expiration - now()))
|
|
126
136
|
: 0
|
|
127
137
|
}
|
|
@@ -188,7 +198,7 @@ class TTLCache {
|
|
|
188
198
|
purgeStale() {
|
|
189
199
|
const n = Math.ceil(now())
|
|
190
200
|
for (const exp in this.expirations) {
|
|
191
|
-
if (exp > n) {
|
|
201
|
+
if (exp === 'Infinity' || exp > n) {
|
|
192
202
|
return
|
|
193
203
|
}
|
|
194
204
|
for (const key of this.expirations[exp]) {
|