@isaacs/ttlcache 1.0.1 → 1.0.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 (3) hide show
  1. package/README.md +39 -1
  2. package/index.js +21 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -61,11 +61,22 @@ Create a new `TTLCache` object.
61
61
  retrieved? Defaults to `false`. Overridable on the `get()` method.
62
62
  * `noUpdateTTL` Should setting a new value for an existing key leave the
63
63
  TTL unchanged? Defaults to `false`. Overridable on the `set()` method.
64
+ (Note that TTL is _always_ updated if the item is expired, since that is
65
+ treated as a new `set()` and the old item is no longer relevant.)
64
66
  * `dispose` Method called with `(value, key, reason)` when an item is
65
67
  removed from the cache. Called once item is fully removed from cache.
66
68
  It is safe to re-add at this point, but note that adding when `reason` is
67
69
  `'set'` can result in infinite recursion if `noDisponseOnSet` is not
68
70
  specified.
71
+
72
+ Disposal reasons:
73
+
74
+ * `'stale'` TTL expired.
75
+ * `'set'` Overwritten with a new different value.
76
+ * `'evict'` Removed from the cache to stay within capacity limit.
77
+ * `'delete'` Explicitly deleted with `cache.delete()` or
78
+ `cache.clear()`
79
+
69
80
  * `noDisposeOnSet` Do not call `dispose()` method when overwriting a key
70
81
  with a new value. Defaults to `false`. Overridable on `set()` method.
71
82
 
@@ -113,7 +124,8 @@ Delete all items from the cache.
113
124
  ### `cache.entries()`
114
125
 
115
126
  Return an iterator that walks through each `[key, value]` from soonest
116
- expiring to latest expiring.
127
+ expiring to latest expiring. (Items expiring at the same time are walked
128
+ in insertion order.)
117
129
 
118
130
  Default iteration method for the cache object.
119
131
 
@@ -150,3 +162,29 @@ automatically.
150
162
 
151
163
  Called when an item is removed from the cache and should be disposed. Set
152
164
  this on the constructor options.
165
+
166
+ ## Algorithm
167
+
168
+ The cache uses two `Map` objects. The first maps item keys to their
169
+ expiration time, and the second maps item keys to their values. Then, a
170
+ null-prototype object uses the expiration time as keys, with the value
171
+ being an array of all the keys expiring at that time.
172
+
173
+ This leverages a few important features of modern JavaScript engines for
174
+ fairly good performance:
175
+
176
+ - `Map` objects are highly optimized for referring to arbitrary values by
177
+ arbitrary keys.
178
+ - Objects with solely integer-numeric keys are iterated in sorted numeric
179
+ order rather than insertion order, and insertions in the middle of the
180
+ key ordering are still very fast. This is true of all modern JS engines
181
+ tested at the time of this module's creation, but most particularly V8
182
+ (the engine in Node.js).
183
+
184
+ When it is time to prune, we can always walk the null-prototype object in
185
+ iteration order, deleting items until we come to the first key greater than
186
+ the current time.
187
+
188
+ Thus, the `start` time doesn't need to be tracked, only the expiration
189
+ time. When an item age is updated (either explicitly on `get()`, or by
190
+ setting to a new value), it is deleted and re-inserted.
package/index.js CHANGED
@@ -52,9 +52,11 @@ class TTLCache {
52
52
  throw new TypeError('ttl must be positive integer')
53
53
  }
54
54
  const current = this.expirationMap.get(key)
55
+ const time = now()
56
+ const oldValue = current === undefined ? undefined : this.data.get(key)
55
57
  if (current !== undefined) {
56
- const oldValue = this.data.get(key)
57
- if (noUpdateTTL) {
58
+ // we aren't updating the ttl, so just set the data
59
+ if (noUpdateTTL && current > time) {
58
60
  if (oldValue !== val) {
59
61
  this.data.set(key, val)
60
62
  if (!noDisposeOnSet) {
@@ -63,13 +65,17 @@ class TTLCache {
63
65
  }
64
66
  return this
65
67
  } else {
66
- this.delete(key, {
67
- reason: 'set',
68
- noDispose: noDisposeOnSet || oldValue === val,
69
- })
68
+ // just delete from expirations list, since we're about to
69
+ // add to data and expirationsMap anyway
70
+ const exp = this.expirations[current]
71
+ if (!exp || exp.length <= 1) {
72
+ delete this.expirations[current]
73
+ } else {
74
+ this.expirations[current] = exp.filter(k => k !== key)
75
+ }
70
76
  }
71
77
  }
72
- const expiration = Math.ceil(now() + ttl)
78
+ const expiration = Math.ceil(time + ttl)
73
79
  this.expirationMap.set(key, expiration)
74
80
  this.data.set(key, val)
75
81
  if (!this.expirations[expiration]) {
@@ -79,9 +85,14 @@ class TTLCache {
79
85
  this.expirations[expiration] = []
80
86
  }
81
87
  this.expirations[expiration].push(key)
88
+
82
89
  while (this.size > this.max) {
83
90
  this.purgeToCapacity()
84
91
  }
92
+
93
+ if (!noDisposeOnSet && current && oldValue !== val) {
94
+ this.dispose(oldValue, key, 'set')
95
+ }
85
96
  return this
86
97
  }
87
98
 
@@ -104,21 +115,19 @@ class TTLCache {
104
115
 
105
116
  dispose (value, key) {}
106
117
 
107
- delete (key, { reason = 'delete', noDispose = false } = {}) {
118
+ delete (key) {
108
119
  const current = this.expirationMap.get(key)
109
120
  if (current !== undefined) {
110
121
  const value = this.data.get(key)
111
122
  this.data.delete(key)
112
123
  this.expirationMap.delete(key)
113
124
  const exp = this.expirations[current]
114
- if (exp.length === 1) {
125
+ if (exp && exp.length <= 1) {
115
126
  delete this.expirations[current]
116
127
  } else {
117
128
  this.expirations[current] = exp.filter(k => k !== key)
118
129
  }
119
- if (!noDispose) {
120
- this.dispose(value, key, reason)
121
- }
130
+ this.dispose(value, key, 'delete')
122
131
  return true
123
132
  }
124
133
  return false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isaacs/ttlcache",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "files": [
5
5
  "index.js"
6
6
  ],