@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.
Files changed (4) hide show
  1. package/README.md +28 -2
  2. package/index.d.ts +14 -4
  3. package/index.js +28 -18
  4. 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: TTLCache.Options<K, V>)
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, defaults to 0, which means "no TTL"
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: number
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 items
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 { now } = maybeReqPerfHooks(Date)
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 && !isPosInt(ttl)) {
31
- throw new TypeError('ttl must be positive integer if set')
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 (!isPosInt(max) && max !== Infinity) {
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 (key, ttl = this.ttl) {
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
- const expiration = Math.floor(now() + ttl)
72
- this.expirationMap.set(key, expiration)
73
- if (!this.expirations[expiration]) {
74
- const t = setTimeout(() => this.purgeStale(), ttl)
75
- /* istanbul ignore else - affordance for non-node envs */
76
- if (t.unref) t.unref()
77
- this.expirations[expiration] = []
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 (!isPosInt(ttl)) {
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 !== undefined
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]) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isaacs/ttlcache",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "files": [
5
5
  "index.js",
6
6
  "index.d.ts"