@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 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
- * 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,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 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
+ 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(() => this.purgeStale(), ttl)
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 && exp.length <= 1) {
160
- delete this.expirations[current]
161
- } else {
162
- this.expirations[current] = exp.filter(k => k !== key)
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
- const val = this.data.get(key)
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
- const val = this.data.get(key)
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
- for (const key of this.expirations[exp]) {
205
- const val = this.data.get(key)
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
- delete this.expirations[exp]
245
+ }
246
+ if (this.size === 0) {
247
+ this.cancelTimers()
211
248
  }
212
249
  }
213
250
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isaacs/ttlcache",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "files": [
5
5
  "index.js",
6
6
  "index.d.ts"