@isaacs/ttlcache 1.2.0 → 1.2.2

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.
Files changed (4) hide show
  1. package/README.md +15 -2
  2. package/index.d.ts +10 -5
  3. package/index.js +60 -21
  4. 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
@@ -89,7 +89,7 @@ declare class TTLCache<K, V> implements Iterable<[K, V]> {
89
89
  }
90
90
 
91
91
  declare namespace TTLCache {
92
- type DisposeReason = 'evict' | 'set' | 'delete'
92
+ type DisposeReason = 'evict' | 'set' | 'delete' | 'stale'
93
93
 
94
94
  type Disposer<K, V> = (
95
95
  value: V,
@@ -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
- * Set to true to suppress calling the dispose() function if the entry
153
- * key is still accessible within the cache.
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,15 @@
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 = fallback => {
7
- try {
8
- return require('perf_hooks').performance
9
- } catch (e) {
10
- return fallback
11
- }
12
- }
13
- const timeProvider = maybeReqPerfHooks(Date)
14
- const now = () => timeProvider.now()
6
+ /* istanbul ignore next */
7
+ const perf =
8
+ typeof performance === 'object' &&
9
+ performance &&
10
+ typeof performance.now === 'function'
11
+ ? performance
12
+ : Date
13
+
14
+ const now = () => perf.now()
15
15
  const isPosInt = n => n && n === Math.floor(n) && n > 0 && isFinite(n)
16
16
  const isPosIntOrInf = n => n === Infinity || isPosInt(n)
17
17
 
@@ -22,6 +22,7 @@ class TTLCache {
22
22
  updateAgeOnGet = false,
23
23
  noUpdateTTL = false,
24
24
  dispose,
25
+ noDisposeOnSet = false,
25
26
  } = {}) {
26
27
  // {[expirationTime]: [keys]}
27
28
  this.expirations = Object.create(null)
@@ -41,19 +42,35 @@ class TTLCache {
41
42
  this.max = max
42
43
  this.updateAgeOnGet = updateAgeOnGet
43
44
  this.noUpdateTTL = noUpdateTTL
45
+ this.noDisposeOnSet = noDisposeOnSet
44
46
  if (dispose !== undefined) {
45
47
  if (typeof dispose !== 'function') {
46
48
  throw new TypeError('dispose must be function if set')
47
49
  }
48
50
  this.dispose = dispose
49
51
  }
52
+
53
+ this.timers = new Set()
54
+ }
55
+
56
+ // hang onto the timer so we can clearTimeout if all items
57
+ // are deleted. Deno doesn't have Timer.unref(), so it
58
+ // hangs otherwise.
59
+ cancelTimers() {
60
+ for (const t of this.timers) {
61
+ clearTimeout(t)
62
+ this.timers.delete(t)
63
+ }
50
64
  }
51
65
 
66
+
52
67
  clear() {
53
68
  const entries =
54
69
  this.dispose !== TTLCache.prototype.dispose ? [...this] : []
55
70
  this.data.clear()
56
71
  this.expirationMap.clear()
72
+ // no need for any purging now
73
+ this.cancelTimers()
57
74
  this.expirations = Object.create(null)
58
75
  for (const [key, val] of entries) {
59
76
  this.dispose(val, key, 'delete')
@@ -76,9 +93,13 @@ class TTLCache {
76
93
  const expiration = Math.floor(now() + ttl)
77
94
  this.expirationMap.set(key, expiration)
78
95
  if (!this.expirations[expiration]) {
79
- const t = setTimeout(() => this.purgeStale(), ttl)
96
+ const t = setTimeout(() => {
97
+ this.timers.delete(t)
98
+ this.purgeStale()
99
+ }, ttl)
80
100
  /* istanbul ignore else - affordance for non-node envs */
81
101
  if (t.unref) t.unref()
102
+ this.timers.add(t)
82
103
  this.expirations[expiration] = []
83
104
  }
84
105
  this.expirations[expiration].push(key)
@@ -156,12 +177,17 @@ class TTLCache {
156
177
  this.data.delete(key)
157
178
  this.expirationMap.delete(key)
158
179
  const exp = this.expirations[current]
159
- if (exp && exp.length <= 1) {
160
- delete this.expirations[current]
161
- } else {
162
- this.expirations[current] = exp.filter(k => k !== key)
180
+ if (exp) {
181
+ if (exp.length <= 1) {
182
+ delete this.expirations[current]
183
+ } else {
184
+ this.expirations[current] = exp.filter(k => k !== key)
185
+ }
163
186
  }
164
187
  this.dispose(value, key, 'delete')
188
+ if (this.size === 0) {
189
+ this.cancelTimers()
190
+ }
165
191
  return true
166
192
  }
167
193
  return false
@@ -171,23 +197,29 @@ class TTLCache {
171
197
  for (const exp in this.expirations) {
172
198
  const keys = this.expirations[exp]
173
199
  if (this.size - keys.length >= this.max) {
200
+ delete this.expirations[exp]
201
+ const entries = []
174
202
  for (const key of keys) {
175
- const val = this.data.get(key)
203
+ entries.push([key, this.data.get(key)])
176
204
  this.data.delete(key)
177
205
  this.expirationMap.delete(key)
206
+ }
207
+ for (const [key, val] of entries) {
178
208
  this.dispose(val, key, 'evict')
179
209
  }
180
- delete this.expirations[exp]
181
210
  } else {
182
211
  const s = this.size - this.max
212
+ const entries = []
183
213
  for (const key of keys.splice(0, s)) {
184
- const val = this.data.get(key)
214
+ entries.push([key, this.data.get(key)])
185
215
  this.data.delete(key)
186
216
  this.expirationMap.delete(key)
217
+ }
218
+ for (const [key, val] of entries) {
187
219
  this.dispose(val, key, 'evict')
188
220
  }
221
+ return
189
222
  }
190
- return
191
223
  }
192
224
  }
193
225
 
@@ -201,13 +233,20 @@ class TTLCache {
201
233
  if (exp === 'Infinity' || exp > n) {
202
234
  return
203
235
  }
204
- for (const key of this.expirations[exp]) {
205
- const val = this.data.get(key)
236
+ const keys = [...this.expirations[exp]]
237
+ const entries = []
238
+ delete this.expirations[exp]
239
+ for (const key of keys) {
240
+ entries.push([key, this.data.get(key)])
206
241
  this.data.delete(key)
207
242
  this.expirationMap.delete(key)
243
+ }
244
+ for (const [key, val] of entries) {
208
245
  this.dispose(val, key, 'stale')
209
246
  }
210
- delete this.expirations[exp]
247
+ }
248
+ if (this.size === 0) {
249
+ this.cancelTimers()
211
250
  }
212
251
  }
213
252
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isaacs/ttlcache",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "files": [
5
5
  "index.js",
6
6
  "index.d.ts"