@isaacs/ttlcache 1.0.3 → 1.1.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/index.d.ts +174 -0
- package/index.js +72 -50
- package/package.json +31 -4
package/index.d.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// Type definitions for ttlcache 1.0.0
|
|
2
|
+
// Project: https://github.com/isaacs/ttlcache
|
|
3
|
+
// Loosely based on @isaacs/lru-cache
|
|
4
|
+
// https://github.com/isaacs/node-lru-cache/blob/v7.10.1/index.d.ts
|
|
5
|
+
|
|
6
|
+
declare class TTLCache<K, V> implements Iterable<[K, V]> {
|
|
7
|
+
constructor(options: TTLCache.Options<K, V>)
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The total number of items held in the cache at the current moment.
|
|
11
|
+
*/
|
|
12
|
+
public readonly size: number
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Add a value to the cache.
|
|
16
|
+
*/
|
|
17
|
+
public set(key: K, value: V, options?: TTLCache.SetOptions): this
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Return a value from the cache.
|
|
21
|
+
* If the key is not found, `get()` will return `undefined`.
|
|
22
|
+
* This can be confusing when setting values specifically to `undefined`,
|
|
23
|
+
* as in `cache.set(key, undefined)`. Use `cache.has()` to determine
|
|
24
|
+
* whether a key is present in the cache at all.
|
|
25
|
+
*/
|
|
26
|
+
public get<T = V>(
|
|
27
|
+
key: K,
|
|
28
|
+
options?: TTLCache.GetOptions
|
|
29
|
+
): T | undefined
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if a key is in the cache.
|
|
33
|
+
* Will return false if the item is stale, even though it is technically
|
|
34
|
+
* in the cache.
|
|
35
|
+
*/
|
|
36
|
+
public has(key: K): boolean
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Deletes a key out of the cache.
|
|
40
|
+
* Returns true if the key was deleted, false otherwise.
|
|
41
|
+
*/
|
|
42
|
+
public delete(key: K): boolean
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Clear the cache entirely, throwing away all values.
|
|
46
|
+
*/
|
|
47
|
+
public clear(): void
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Delete any stale entries. Returns true if anything was removed, false
|
|
51
|
+
* otherwise.
|
|
52
|
+
*/
|
|
53
|
+
public purgeStale(): boolean
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Return the remaining time before an item expires.
|
|
57
|
+
* Returns 0 if the item is not found in the cache or is already expired.
|
|
58
|
+
*/
|
|
59
|
+
public getRemainingTTL(key: K): number
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Set the ttl explicitly to a value, defaulting to the TTL set on the ctor
|
|
63
|
+
*/
|
|
64
|
+
public setTTL(key: K, ttl?: number): void
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Return a generator yielding `[key, value]` pairs, from soonest expiring
|
|
68
|
+
* to latest expiring. (Items expiring at the same time are walked in insertion order.)
|
|
69
|
+
*/
|
|
70
|
+
public entries(): Generator<[K, V]>
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Return a generator yielding the keys in the cache,
|
|
74
|
+
* from soonest expiring to latest expiring.
|
|
75
|
+
*/
|
|
76
|
+
public keys(): Generator<K>
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Return a generator yielding the values in the cache,
|
|
80
|
+
* from soonest expiring to latest expiring.
|
|
81
|
+
*/
|
|
82
|
+
public values(): Generator<V>
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Iterating over the cache itself yields the same results as
|
|
86
|
+
* `cache.entries()`
|
|
87
|
+
*/
|
|
88
|
+
public [Symbol.iterator](): Iterator<[K, V]>
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
declare namespace TTLCache {
|
|
92
|
+
type DisposeReason = 'evict' | 'set' | 'delete'
|
|
93
|
+
|
|
94
|
+
type Disposer<K, V> = (
|
|
95
|
+
value: V,
|
|
96
|
+
key: K,
|
|
97
|
+
reason: DisposeReason
|
|
98
|
+
) => void
|
|
99
|
+
|
|
100
|
+
type TTLOptions = {
|
|
101
|
+
/**
|
|
102
|
+
* Max time in milliseconds for items to live in cache before they are
|
|
103
|
+
* considered stale. Note that stale items are NOT preemptively removed
|
|
104
|
+
* by default, and MAY live in the cache, contributing to max,
|
|
105
|
+
* long after they have expired.
|
|
106
|
+
*
|
|
107
|
+
* Must be an integer number of ms, defaults to 0, which means "no TTL"
|
|
108
|
+
*/
|
|
109
|
+
ttl: number
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Boolean flag to tell the cache to not update the TTL when
|
|
113
|
+
* setting a new value for an existing key (ie, when updating a value
|
|
114
|
+
* rather than inserting a new value). Note that the TTL value is
|
|
115
|
+
* _always_ set when adding a new entry into the cache.
|
|
116
|
+
*
|
|
117
|
+
* @default false
|
|
118
|
+
*/
|
|
119
|
+
noUpdateTTL?: boolean
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
type Options<K, V> = {
|
|
123
|
+
/**
|
|
124
|
+
* The number of items to keep.
|
|
125
|
+
*
|
|
126
|
+
* @default Infinity
|
|
127
|
+
*/
|
|
128
|
+
max?: number
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Update the age of items on cache.get(), renewing their TTL
|
|
132
|
+
*
|
|
133
|
+
* @default false
|
|
134
|
+
*/
|
|
135
|
+
updateAgeOnGet?: boolean
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Function that is called on items when they are dropped from the cache.
|
|
139
|
+
* This can be handy if you want to close file descriptors or do other
|
|
140
|
+
* cleanup tasks when items are no longer accessible. Called with `key,
|
|
141
|
+
* value`. It's called before actually removing the item from the
|
|
142
|
+
* internal cache, so it is *NOT* safe to re-add them.
|
|
143
|
+
* Use `disposeAfter` if you wish to dispose items after they have been
|
|
144
|
+
* full removed, when it is safe to add them back to the cache.
|
|
145
|
+
*/
|
|
146
|
+
dispose?: Disposer<K, V>
|
|
147
|
+
} & TTLOptions
|
|
148
|
+
|
|
149
|
+
type SetOptions = {
|
|
150
|
+
/**
|
|
151
|
+
* Set to true to suppress calling the dispose() function if the entry
|
|
152
|
+
* key is still accessible within the cache.
|
|
153
|
+
*
|
|
154
|
+
* @default false
|
|
155
|
+
*/
|
|
156
|
+
noDisposeOnSet?: boolean
|
|
157
|
+
noUpdateTTL?: boolean
|
|
158
|
+
ttl?: number
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
type GetOptions = {
|
|
162
|
+
/**
|
|
163
|
+
* Update the age of items
|
|
164
|
+
*/
|
|
165
|
+
updateAgeOnGet?: boolean
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Set new TTL, applied only when `updateAgeOnGet` is true
|
|
169
|
+
*/
|
|
170
|
+
ttl?: number
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export = TTLCache
|
package/index.js
CHANGED
|
@@ -3,18 +3,24 @@
|
|
|
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 maybeReqPerfHooks =
|
|
6
|
+
const maybeReqPerfHooks = fallback => {
|
|
7
7
|
try {
|
|
8
8
|
return require('perf_hooks').performance
|
|
9
9
|
} catch (e) {
|
|
10
10
|
return fallback
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
const {now} = maybeReqPerfHooks(Date)
|
|
13
|
+
const { now } = maybeReqPerfHooks(Date)
|
|
14
14
|
const isPosInt = n => n && n === Math.floor(n) && n > 0 && isFinite(n)
|
|
15
15
|
|
|
16
16
|
class TTLCache {
|
|
17
|
-
constructor
|
|
17
|
+
constructor({
|
|
18
|
+
max = Infinity,
|
|
19
|
+
ttl,
|
|
20
|
+
updateAgeOnGet = false,
|
|
21
|
+
noUpdateTTL = false,
|
|
22
|
+
dispose,
|
|
23
|
+
}) {
|
|
18
24
|
// {[expirationTime]: [keys]}
|
|
19
25
|
this.expirations = Object.create(null)
|
|
20
26
|
// {key=>val}
|
|
@@ -29,8 +35,8 @@ class TTLCache {
|
|
|
29
35
|
}
|
|
30
36
|
this.ttl = ttl
|
|
31
37
|
this.max = max
|
|
32
|
-
this.updateAgeOnGet = updateAgeOnGet
|
|
33
|
-
this.noUpdateTTL = noUpdateTTL
|
|
38
|
+
this.updateAgeOnGet = updateAgeOnGet
|
|
39
|
+
this.noUpdateTTL = noUpdateTTL
|
|
34
40
|
if (dispose !== undefined) {
|
|
35
41
|
if (typeof dispose !== 'function') {
|
|
36
42
|
throw new TypeError('dispose must be function if set')
|
|
@@ -39,8 +45,9 @@ class TTLCache {
|
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
47
|
|
|
42
|
-
clear
|
|
43
|
-
const entries =
|
|
48
|
+
clear() {
|
|
49
|
+
const entries =
|
|
50
|
+
this.dispose !== TTLCache.prototype.dispose ? [...this] : []
|
|
44
51
|
this.data.clear()
|
|
45
52
|
this.expirationMap.clear()
|
|
46
53
|
this.expirations = Object.create(null)
|
|
@@ -49,37 +56,20 @@ class TTLCache {
|
|
|
49
56
|
}
|
|
50
57
|
}
|
|
51
58
|
|
|
52
|
-
|
|
53
|
-
if (!isPosInt(ttl)) {
|
|
54
|
-
throw new TypeError('ttl must be positive integer')
|
|
55
|
-
}
|
|
59
|
+
setTTL (key, ttl = this.ttl) {
|
|
56
60
|
const current = this.expirationMap.get(key)
|
|
57
|
-
const time = now()
|
|
58
|
-
const oldValue = current === undefined ? undefined : this.data.get(key)
|
|
59
61
|
if (current !== undefined) {
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (!noDisposeOnSet) {
|
|
65
|
-
this.dispose(oldValue, key, 'set')
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return this
|
|
62
|
+
// remove from the expirations list, so it isn't purged
|
|
63
|
+
const exp = this.expirations[current]
|
|
64
|
+
if (!exp || exp.length <= 1) {
|
|
65
|
+
delete this.expirations[current]
|
|
69
66
|
} else {
|
|
70
|
-
|
|
71
|
-
// add to data and expirationsMap anyway
|
|
72
|
-
const exp = this.expirations[current]
|
|
73
|
-
if (!exp || exp.length <= 1) {
|
|
74
|
-
delete this.expirations[current]
|
|
75
|
-
} else {
|
|
76
|
-
this.expirations[current] = exp.filter(k => k !== key)
|
|
77
|
-
}
|
|
67
|
+
this.expirations[current] = exp.filter(k => k !== key)
|
|
78
68
|
}
|
|
79
69
|
}
|
|
80
|
-
|
|
70
|
+
|
|
71
|
+
const expiration = Math.floor(now() + ttl)
|
|
81
72
|
this.expirationMap.set(key, expiration)
|
|
82
|
-
this.data.set(key, val)
|
|
83
73
|
if (!this.expirations[expiration]) {
|
|
84
74
|
const t = setTimeout(() => this.purgeStale(), ttl)
|
|
85
75
|
/* istanbul ignore else - affordance for non-node envs */
|
|
@@ -87,37 +77,69 @@ class TTLCache {
|
|
|
87
77
|
this.expirations[expiration] = []
|
|
88
78
|
}
|
|
89
79
|
this.expirations[expiration].push(key)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
set(
|
|
83
|
+
key,
|
|
84
|
+
val,
|
|
85
|
+
{
|
|
86
|
+
ttl = this.ttl,
|
|
87
|
+
noUpdateTTL = this.noUpdateTTL,
|
|
88
|
+
noDisposeOnSet = this.noDisposeOnSet,
|
|
89
|
+
} = {}
|
|
90
|
+
) {
|
|
91
|
+
if (!isPosInt(ttl)) {
|
|
92
|
+
throw new TypeError('ttl must be positive integer')
|
|
93
|
+
}
|
|
94
|
+
if (this.expirationMap.has(key)) {
|
|
95
|
+
if (!noUpdateTTL) {
|
|
96
|
+
this.setTTL(key, ttl)
|
|
97
|
+
}
|
|
98
|
+
// has old value
|
|
99
|
+
const oldValue = this.data.get(key)
|
|
100
|
+
if (oldValue !== val) {
|
|
101
|
+
this.data.set(key, val)
|
|
102
|
+
if (!noDisposeOnSet) {
|
|
103
|
+
this.dispose(oldValue, key, 'set')
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
this.setTTL(key, ttl)
|
|
108
|
+
this.data.set(key, val)
|
|
109
|
+
}
|
|
90
110
|
|
|
91
111
|
while (this.size > this.max) {
|
|
92
112
|
this.purgeToCapacity()
|
|
93
113
|
}
|
|
94
114
|
|
|
95
|
-
if (!noDisposeOnSet && current && oldValue !== val) {
|
|
96
|
-
this.dispose(oldValue, key, 'set')
|
|
97
|
-
}
|
|
98
115
|
return this
|
|
99
116
|
}
|
|
100
117
|
|
|
101
|
-
has
|
|
118
|
+
has(key) {
|
|
102
119
|
return this.data.has(key)
|
|
103
120
|
}
|
|
104
121
|
|
|
105
|
-
getRemainingTTL
|
|
122
|
+
getRemainingTTL(key) {
|
|
106
123
|
const expiration = this.expirationMap.get(key)
|
|
107
|
-
return expiration !== undefined
|
|
124
|
+
return expiration !== undefined
|
|
125
|
+
? Math.max(0, Math.ceil(expiration - now()))
|
|
126
|
+
: 0
|
|
108
127
|
}
|
|
109
128
|
|
|
110
|
-
get
|
|
129
|
+
get(
|
|
130
|
+
key,
|
|
131
|
+
{ updateAgeOnGet = this.updateAgeOnGet, ttl = this.ttl } = {}
|
|
132
|
+
) {
|
|
111
133
|
const val = this.data.get(key)
|
|
112
134
|
if (updateAgeOnGet) {
|
|
113
|
-
this.
|
|
135
|
+
this.setTTL(key, ttl)
|
|
114
136
|
}
|
|
115
137
|
return val
|
|
116
138
|
}
|
|
117
139
|
|
|
118
|
-
dispose
|
|
140
|
+
dispose(_, __) {}
|
|
119
141
|
|
|
120
|
-
delete
|
|
142
|
+
delete(key) {
|
|
121
143
|
const current = this.expirationMap.get(key)
|
|
122
144
|
if (current !== undefined) {
|
|
123
145
|
const value = this.data.get(key)
|
|
@@ -135,7 +157,7 @@ class TTLCache {
|
|
|
135
157
|
return false
|
|
136
158
|
}
|
|
137
159
|
|
|
138
|
-
purgeToCapacity
|
|
160
|
+
purgeToCapacity() {
|
|
139
161
|
for (const exp in this.expirations) {
|
|
140
162
|
const keys = this.expirations[exp]
|
|
141
163
|
if (this.size - keys.length >= this.max) {
|
|
@@ -159,12 +181,12 @@ class TTLCache {
|
|
|
159
181
|
}
|
|
160
182
|
}
|
|
161
183
|
|
|
162
|
-
get size
|
|
184
|
+
get size() {
|
|
163
185
|
return this.data.size
|
|
164
186
|
}
|
|
165
187
|
|
|
166
|
-
purgeStale
|
|
167
|
-
const n = now()
|
|
188
|
+
purgeStale() {
|
|
189
|
+
const n = Math.ceil(now())
|
|
168
190
|
for (const exp in this.expirations) {
|
|
169
191
|
if (exp > n) {
|
|
170
192
|
return
|
|
@@ -179,28 +201,28 @@ class TTLCache {
|
|
|
179
201
|
}
|
|
180
202
|
}
|
|
181
203
|
|
|
182
|
-
*entries
|
|
204
|
+
*entries() {
|
|
183
205
|
for (const exp in this.expirations) {
|
|
184
206
|
for (const key of this.expirations[exp]) {
|
|
185
207
|
yield [key, this.data.get(key)]
|
|
186
208
|
}
|
|
187
209
|
}
|
|
188
210
|
}
|
|
189
|
-
*keys
|
|
211
|
+
*keys() {
|
|
190
212
|
for (const exp in this.expirations) {
|
|
191
213
|
for (const key of this.expirations[exp]) {
|
|
192
214
|
yield key
|
|
193
215
|
}
|
|
194
216
|
}
|
|
195
217
|
}
|
|
196
|
-
*values
|
|
218
|
+
*values() {
|
|
197
219
|
for (const exp in this.expirations) {
|
|
198
220
|
for (const key of this.expirations[exp]) {
|
|
199
221
|
yield this.data.get(key)
|
|
200
222
|
}
|
|
201
223
|
}
|
|
202
224
|
}
|
|
203
|
-
[Symbol.iterator]
|
|
225
|
+
[Symbol.iterator]() {
|
|
204
226
|
return this.entries()
|
|
205
227
|
}
|
|
206
228
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@isaacs/ttlcache",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"files": [
|
|
5
|
-
"index.js"
|
|
5
|
+
"index.js",
|
|
6
|
+
"index.d.ts"
|
|
6
7
|
],
|
|
7
8
|
"main": "index.js",
|
|
8
9
|
"exports": {
|
|
@@ -23,10 +24,36 @@
|
|
|
23
24
|
"prepublishOnly": "git push origin --follow-tags"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
|
-
"
|
|
27
|
-
"tap": "^
|
|
27
|
+
"@types/node": "^17.0.42",
|
|
28
|
+
"@types/tap": "^15.0.7",
|
|
29
|
+
"clock-mock": "^1.0.6",
|
|
30
|
+
"prettier": "^2.7.0",
|
|
31
|
+
"tap": "^16.0.1",
|
|
32
|
+
"ts-node": "^10.8.1",
|
|
33
|
+
"typescript": "^4.7.3"
|
|
28
34
|
},
|
|
29
35
|
"engines": {
|
|
30
36
|
"node": ">=12"
|
|
37
|
+
},
|
|
38
|
+
"tap": {
|
|
39
|
+
"nyc-arg": [
|
|
40
|
+
"--include=index.js"
|
|
41
|
+
],
|
|
42
|
+
"node-arg": [
|
|
43
|
+
"--require",
|
|
44
|
+
"ts-node/register"
|
|
45
|
+
],
|
|
46
|
+
"ts": false
|
|
47
|
+
},
|
|
48
|
+
"prettier": {
|
|
49
|
+
"semi": false,
|
|
50
|
+
"printWidth": 70,
|
|
51
|
+
"tabWidth": 2,
|
|
52
|
+
"useTabs": false,
|
|
53
|
+
"singleQuote": true,
|
|
54
|
+
"jsxSingleQuote": false,
|
|
55
|
+
"bracketSameLine": true,
|
|
56
|
+
"arrowParens": "avoid",
|
|
57
|
+
"endOfLine": "lf"
|
|
31
58
|
}
|
|
32
59
|
}
|