@aws-cdk/cloud-assembly-schema 2.23.0 → 2.24.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.
@@ -1,815 +1,334 @@
1
- const perf = typeof performance === 'object' && performance &&
2
- typeof performance.now === 'function' ? performance : Date
3
-
4
- const hasAbortController = typeof AbortController !== 'undefined'
5
-
6
- // minimal backwards-compatibility polyfill
7
- const AC = hasAbortController ? AbortController : Object.assign(
8
- class AbortController {
9
- constructor () { this.signal = new AC.AbortSignal }
10
- abort () { this.signal.aborted = true }
11
- },
12
- { AbortSignal: class AbortSignal { constructor () { this.aborted = false }}}
13
- )
14
-
15
- const warned = new Set()
16
- const deprecatedOption = (opt, instead) => {
17
- const code = `LRU_CACHE_OPTION_${opt}`
18
- if (shouldWarn(code)) {
19
- warn(code, `${opt} option`, `options.${instead}`, LRUCache)
20
- }
21
- }
22
- const deprecatedMethod = (method, instead) => {
23
- const code = `LRU_CACHE_METHOD_${method}`
24
- if (shouldWarn(code)) {
25
- const { prototype } = LRUCache
26
- const { get } = Object.getOwnPropertyDescriptor(prototype, method)
27
- warn(code, `${method} method`, `cache.${instead}()`, get)
28
- }
29
- }
30
- const deprecatedProperty = (field, instead) => {
31
- const code = `LRU_CACHE_PROPERTY_${field}`
32
- if (shouldWarn(code)) {
33
- const { prototype } = LRUCache
34
- const { get } = Object.getOwnPropertyDescriptor(prototype, field)
35
- warn(code, `${field} property`, `cache.${instead}`, get)
36
- }
37
- }
1
+ 'use strict'
2
+
3
+ // A linked list to keep track of recently-used-ness
4
+ const Yallist = require('yallist')
5
+
6
+ const MAX = Symbol('max')
7
+ const LENGTH = Symbol('length')
8
+ const LENGTH_CALCULATOR = Symbol('lengthCalculator')
9
+ const ALLOW_STALE = Symbol('allowStale')
10
+ const MAX_AGE = Symbol('maxAge')
11
+ const DISPOSE = Symbol('dispose')
12
+ const NO_DISPOSE_ON_SET = Symbol('noDisposeOnSet')
13
+ const LRU_LIST = Symbol('lruList')
14
+ const CACHE = Symbol('cache')
15
+ const UPDATE_AGE_ON_GET = Symbol('updateAgeOnGet')
16
+
17
+ const naiveLength = () => 1
18
+
19
+ // lruList is a yallist where the head is the youngest
20
+ // item, and the tail is the oldest. the list contains the Hit
21
+ // objects as the entries.
22
+ // Each Hit object has a reference to its Yallist.Node. This
23
+ // never changes.
24
+ //
25
+ // cache is a Map (or PseudoMap) that matches the keys to
26
+ // the Yallist.Node object.
27
+ class LRUCache {
28
+ constructor (options) {
29
+ if (typeof options === 'number')
30
+ options = { max: options }
38
31
 
39
- const emitWarning = (...a) => {
40
- typeof process === 'object' &&
41
- process &&
42
- typeof process.emitWarning === 'function'
43
- ? process.emitWarning(...a)
44
- : console.error(...a)
45
- }
32
+ if (!options)
33
+ options = {}
46
34
 
47
- const shouldWarn = code => !warned.has(code)
35
+ if (options.max && (typeof options.max !== 'number' || options.max < 0))
36
+ throw new TypeError('max must be a non-negative number')
37
+ // Kind of weird to have a default max of Infinity, but oh well.
38
+ const max = this[MAX] = options.max || Infinity
48
39
 
49
- const warn = (code, what, instead, fn) => {
50
- warned.add(code)
51
- const msg = `The ${what} is deprecated. Please use ${instead} instead.`
52
- emitWarning(msg, 'DeprecationWarning', code, fn)
53
- }
54
-
55
- const isPosInt = n => n && n === Math.floor(n) && n > 0 && isFinite(n)
56
-
57
- /* istanbul ignore next - This is a little bit ridiculous, tbh.
58
- * The maximum array length is 2^32-1 or thereabouts on most JS impls.
59
- * And well before that point, you're caching the entire world, I mean,
60
- * that's ~32GB of just integers for the next/prev links, plus whatever
61
- * else to hold that many keys and values. Just filling the memory with
62
- * zeroes at init time is brutal when you get that big.
63
- * But why not be complete?
64
- * Maybe in the future, these limits will have expanded. */
65
- const getUintArray = max => !isPosInt(max) ? null
66
- : max <= Math.pow(2, 8) ? Uint8Array
67
- : max <= Math.pow(2, 16) ? Uint16Array
68
- : max <= Math.pow(2, 32) ? Uint32Array
69
- : max <= Number.MAX_SAFE_INTEGER ? ZeroArray
70
- : null
71
-
72
- class ZeroArray extends Array {
73
- constructor (size) {
74
- super(size)
75
- this.fill(0)
40
+ const lc = options.length || naiveLength
41
+ this[LENGTH_CALCULATOR] = (typeof lc !== 'function') ? naiveLength : lc
42
+ this[ALLOW_STALE] = options.stale || false
43
+ if (options.maxAge && typeof options.maxAge !== 'number')
44
+ throw new TypeError('maxAge must be a number')
45
+ this[MAX_AGE] = options.maxAge || 0
46
+ this[DISPOSE] = options.dispose
47
+ this[NO_DISPOSE_ON_SET] = options.noDisposeOnSet || false
48
+ this[UPDATE_AGE_ON_GET] = options.updateAgeOnGet || false
49
+ this.reset()
76
50
  }
77
- }
78
51
 
79
- class Stack {
80
- constructor (max) {
81
- const UintArray = max ? getUintArray(max) : Array
82
- this.heap = new UintArray(max)
83
- this.length = 0
84
- }
85
- push (n) {
86
- this.heap[this.length++] = n
52
+ // resize the cache when the max changes.
53
+ set max (mL) {
54
+ if (typeof mL !== 'number' || mL < 0)
55
+ throw new TypeError('max must be a non-negative number')
56
+
57
+ this[MAX] = mL || Infinity
58
+ trim(this)
87
59
  }
88
- pop () {
89
- return this.heap[--this.length]
60
+ get max () {
61
+ return this[MAX]
90
62
  }
91
- }
92
63
 
93
- class LRUCache {
94
- constructor (options = {}) {
95
- const {
96
- max = 0,
97
- ttl,
98
- ttlResolution = 1,
99
- ttlAutopurge,
100
- updateAgeOnGet,
101
- updateAgeOnHas,
102
- allowStale,
103
- dispose,
104
- disposeAfter,
105
- noDisposeOnSet,
106
- noUpdateTTL,
107
- maxSize = 0,
108
- sizeCalculation,
109
- fetchMethod,
110
- } = options
111
-
112
- // deprecated options, don't trigger a warning for getting them if
113
- // the thing being passed in is another LRUCache we're copying.
114
- const {
115
- length,
116
- maxAge,
117
- stale,
118
- } = options instanceof LRUCache ? {} : options
119
-
120
- if (max !== 0 && !isPosInt(max)) {
121
- throw new TypeError('max option must be a nonnegative integer')
122
- }
123
-
124
- const UintArray = max ? getUintArray(max) : Array
125
- if (!UintArray) {
126
- throw new Error('invalid max value: ' + max)
127
- }
128
-
129
- this.max = max
130
- this.maxSize = maxSize
131
- this.sizeCalculation = sizeCalculation || length
132
- if (this.sizeCalculation) {
133
- if (!this.maxSize) {
134
- throw new TypeError('cannot set sizeCalculation without setting maxSize')
135
- }
136
- if (typeof this.sizeCalculation !== 'function') {
137
- throw new TypeError('sizeCalculation set to non-function')
138
- }
139
- }
140
-
141
- this.fetchMethod = fetchMethod || null
142
- if (this.fetchMethod && typeof this.fetchMethod !== 'function') {
143
- throw new TypeError('fetchMethod must be a function if specified')
144
- }
145
-
146
-
147
- this.keyMap = new Map()
148
- this.keyList = new Array(max).fill(null)
149
- this.valList = new Array(max).fill(null)
150
- this.next = new UintArray(max)
151
- this.prev = new UintArray(max)
152
- this.head = 0
153
- this.tail = 0
154
- this.free = new Stack(max)
155
- this.initialFill = 1
156
- this.size = 0
157
-
158
- if (typeof dispose === 'function') {
159
- this.dispose = dispose
160
- }
161
- if (typeof disposeAfter === 'function') {
162
- this.disposeAfter = disposeAfter
163
- this.disposed = []
164
- } else {
165
- this.disposeAfter = null
166
- this.disposed = null
167
- }
168
- this.noDisposeOnSet = !!noDisposeOnSet
169
- this.noUpdateTTL = !!noUpdateTTL
170
-
171
- if (this.maxSize !== 0) {
172
- if (!isPosInt(this.maxSize)) {
173
- throw new TypeError('maxSize must be a positive integer if specified')
174
- }
175
- this.initializeSizeTracking()
176
- }
177
-
178
- this.allowStale = !!allowStale || !!stale
179
- this.updateAgeOnGet = !!updateAgeOnGet
180
- this.updateAgeOnHas = !!updateAgeOnHas
181
- this.ttlResolution = isPosInt(ttlResolution) || ttlResolution === 0
182
- ? ttlResolution : 1
183
- this.ttlAutopurge = !!ttlAutopurge
184
- this.ttl = ttl || maxAge || 0
185
- if (this.ttl) {
186
- if (!isPosInt(this.ttl)) {
187
- throw new TypeError('ttl must be a positive integer if specified')
188
- }
189
- this.initializeTTLTracking()
190
- }
191
-
192
- // do not allow completely unbounded caches
193
- if (this.max === 0 && this.ttl === 0 && this.maxSize === 0) {
194
- throw new TypeError('At least one of max, maxSize, or ttl is required')
195
- }
196
- if (!this.ttlAutopurge && !this.max && !this.maxSize) {
197
- const code = 'LRU_CACHE_UNBOUNDED'
198
- if (shouldWarn(code)) {
199
- warned.add(code)
200
- const msg = 'TTL caching without ttlAutopurge, max, or maxSize can ' +
201
- 'result in unbounded memory consumption.'
202
- emitWarning(msg, 'UnboundedCacheWarning', code, LRUCache)
203
- }
204
- }
205
-
206
- if (stale) {
207
- deprecatedOption('stale', 'allowStale')
208
- }
209
- if (maxAge) {
210
- deprecatedOption('maxAge', 'ttl')
211
- }
212
- if (length) {
213
- deprecatedOption('length', 'sizeCalculation')
214
- }
64
+ set allowStale (allowStale) {
65
+ this[ALLOW_STALE] = !!allowStale
215
66
  }
216
-
217
- getRemainingTTL (key) {
218
- return this.has(key, { updateAgeOnHas: false }) ? Infinity : 0
67
+ get allowStale () {
68
+ return this[ALLOW_STALE]
219
69
  }
220
70
 
221
- initializeTTLTracking () {
222
- this.ttls = new ZeroArray(this.max)
223
- this.starts = new ZeroArray(this.max)
224
-
225
- this.setItemTTL = (index, ttl) => {
226
- this.starts[index] = ttl !== 0 ? perf.now() : 0
227
- this.ttls[index] = ttl
228
- if (ttl !== 0 && this.ttlAutopurge) {
229
- const t = setTimeout(() => {
230
- if (this.isStale(index)) {
231
- this.delete(this.keyList[index])
232
- }
233
- }, ttl + 1)
234
- /* istanbul ignore else - unref() not supported on all platforms */
235
- if (t.unref) {
236
- t.unref()
237
- }
238
- }
239
- }
71
+ set maxAge (mA) {
72
+ if (typeof mA !== 'number')
73
+ throw new TypeError('maxAge must be a non-negative number')
240
74
 
241
- this.updateItemAge = (index) => {
242
- this.starts[index] = this.ttls[index] !== 0 ? perf.now() : 0
243
- }
244
-
245
- // debounce calls to perf.now() to 1s so we're not hitting
246
- // that costly call repeatedly.
247
- let cachedNow = 0
248
- const getNow = () => {
249
- const n = perf.now()
250
- if (this.ttlResolution > 0) {
251
- cachedNow = n
252
- const t = setTimeout(() => cachedNow = 0, this.ttlResolution)
253
- /* istanbul ignore else - not available on all platforms */
254
- if (t.unref) {
255
- t.unref()
256
- }
257
- }
258
- return n
259
- }
260
-
261
- this.getRemainingTTL = (key) => {
262
- const index = this.keyMap.get(key)
263
- if (index === undefined) {
264
- return 0
265
- }
266
- return this.ttls[index] === 0 || this.starts[index] === 0 ? Infinity
267
- : ((this.starts[index] + this.ttls[index]) - (cachedNow || getNow()))
268
- }
269
-
270
- this.isStale = (index) => {
271
- return this.ttls[index] !== 0 && this.starts[index] !== 0 &&
272
- ((cachedNow || getNow()) - this.starts[index] > this.ttls[index])
273
- }
274
- }
275
- updateItemAge (index) {}
276
- setItemTTL (index, ttl) {}
277
- isStale (index) { return false }
278
-
279
- initializeSizeTracking () {
280
- this.calculatedSize = 0
281
- this.sizes = new ZeroArray(this.max)
282
- this.removeItemSize = index => this.calculatedSize -= this.sizes[index]
283
- this.requireSize = (k, v, size, sizeCalculation) => {
284
- if (!isPosInt(size)) {
285
- if (sizeCalculation) {
286
- if (typeof sizeCalculation !== 'function') {
287
- throw new TypeError('sizeCalculation must be a function')
288
- }
289
- size = sizeCalculation(v, k)
290
- if (!isPosInt(size)) {
291
- throw new TypeError('sizeCalculation return invalid (expect positive integer)')
292
- }
293
- } else {
294
- throw new TypeError('invalid size value (must be positive integer)')
295
- }
296
- }
297
- return size
298
- }
299
- this.addItemSize = (index, v, k, size) => {
300
- this.sizes[index] = size
301
- const maxSize = this.maxSize - this.sizes[index]
302
- while (this.calculatedSize > maxSize) {
303
- this.evict()
304
- }
305
- this.calculatedSize += this.sizes[index]
306
- }
307
- this.delete = k => {
308
- if (this.size !== 0) {
309
- const index = this.keyMap.get(k)
310
- if (index !== undefined) {
311
- this.calculatedSize -= this.sizes[index]
312
- }
313
- }
314
- return LRUCache.prototype.delete.call(this, k)
315
- }
75
+ this[MAX_AGE] = mA
76
+ trim(this)
316
77
  }
317
- removeItemSize (index) {}
318
- addItemSize (index, v, k, size) {}
319
- requireSize (k, v, size, sizeCalculation) {
320
- if (size || sizeCalculation) {
321
- throw new TypeError('cannot set size without setting maxSize on cache')
322
- }
78
+ get maxAge () {
79
+ return this[MAX_AGE]
323
80
  }
324
81
 
325
- *indexes ({ allowStale = this.allowStale } = {}) {
326
- if (this.size) {
327
- for (let i = this.tail; true; ) {
328
- if (!this.isValidIndex(i)) {
329
- break
330
- }
331
- if (allowStale || !this.isStale(i)) {
332
- yield i
333
- }
334
- if (i === this.head) {
335
- break
336
- } else {
337
- i = this.prev[i]
338
- }
339
- }
340
- }
341
- }
82
+ // resize the cache when the lengthCalculator changes.
83
+ set lengthCalculator (lC) {
84
+ if (typeof lC !== 'function')
85
+ lC = naiveLength
342
86
 
343
- *rindexes ({ allowStale = this.allowStale } = {}) {
344
- if (this.size) {
345
- for (let i = this.head; true; ) {
346
- if (!this.isValidIndex(i)) {
347
- break
348
- }
349
- if (allowStale || !this.isStale(i)) {
350
- yield i
351
- }
352
- if (i === this.tail) {
353
- break
354
- } else {
355
- i = this.next[i]
356
- }
357
- }
87
+ if (lC !== this[LENGTH_CALCULATOR]) {
88
+ this[LENGTH_CALCULATOR] = lC
89
+ this[LENGTH] = 0
90
+ this[LRU_LIST].forEach(hit => {
91
+ hit.length = this[LENGTH_CALCULATOR](hit.value, hit.key)
92
+ this[LENGTH] += hit.length
93
+ })
358
94
  }
95
+ trim(this)
359
96
  }
97
+ get lengthCalculator () { return this[LENGTH_CALCULATOR] }
360
98
 
361
- isValidIndex (index) {
362
- return this.keyMap.get(this.keyList[index]) === index
363
- }
99
+ get length () { return this[LENGTH] }
100
+ get itemCount () { return this[LRU_LIST].length }
364
101
 
365
- *entries () {
366
- for (const i of this.indexes()) {
367
- yield [this.keyList[i], this.valList[i]]
368
- }
369
- }
370
- *rentries () {
371
- for (const i of this.rindexes()) {
372
- yield [this.keyList[i], this.valList[i]]
102
+ rforEach (fn, thisp) {
103
+ thisp = thisp || this
104
+ for (let walker = this[LRU_LIST].tail; walker !== null;) {
105
+ const prev = walker.prev
106
+ forEachStep(this, fn, walker, thisp)
107
+ walker = prev
373
108
  }
374
109
  }
375
110
 
376
- *keys () {
377
- for (const i of this.indexes()) {
378
- yield this.keyList[i]
379
- }
380
- }
381
- *rkeys () {
382
- for (const i of this.rindexes()) {
383
- yield this.keyList[i]
111
+ forEach (fn, thisp) {
112
+ thisp = thisp || this
113
+ for (let walker = this[LRU_LIST].head; walker !== null;) {
114
+ const next = walker.next
115
+ forEachStep(this, fn, walker, thisp)
116
+ walker = next
384
117
  }
385
118
  }
386
119
 
387
- *values () {
388
- for (const i of this.indexes()) {
389
- yield this.valList[i]
390
- }
391
- }
392
- *rvalues () {
393
- for (const i of this.rindexes()) {
394
- yield this.valList[i]
395
- }
120
+ keys () {
121
+ return this[LRU_LIST].toArray().map(k => k.key)
396
122
  }
397
123
 
398
- [Symbol.iterator] () {
399
- return this.entries()
124
+ values () {
125
+ return this[LRU_LIST].toArray().map(k => k.value)
400
126
  }
401
127
 
402
- find (fn, getOptions = {}) {
403
- for (const i of this.indexes()) {
404
- if (fn(this.valList[i], this.keyList[i], this)) {
405
- return this.get(this.keyList[i], getOptions)
406
- }
128
+ reset () {
129
+ if (this[DISPOSE] &&
130
+ this[LRU_LIST] &&
131
+ this[LRU_LIST].length) {
132
+ this[LRU_LIST].forEach(hit => this[DISPOSE](hit.key, hit.value))
407
133
  }
408
- }
409
134
 
410
- forEach (fn, thisp = this) {
411
- for (const i of this.indexes()) {
412
- fn.call(thisp, this.valList[i], this.keyList[i], this)
413
- }
135
+ this[CACHE] = new Map() // hash of items by key
136
+ this[LRU_LIST] = new Yallist() // list of items in order of use recency
137
+ this[LENGTH] = 0 // length of items in the list
414
138
  }
415
139
 
416
- rforEach (fn, thisp = this) {
417
- for (const i of this.rindexes()) {
418
- fn.call(thisp, this.valList[i], this.keyList[i], this)
419
- }
140
+ dump () {
141
+ return this[LRU_LIST].map(hit =>
142
+ isStale(this, hit) ? false : {
143
+ k: hit.key,
144
+ v: hit.value,
145
+ e: hit.now + (hit.maxAge || 0)
146
+ }).toArray().filter(h => h)
420
147
  }
421
148
 
422
- get prune () {
423
- deprecatedMethod('prune', 'purgeStale')
424
- return this.purgeStale
149
+ dumpLru () {
150
+ return this[LRU_LIST]
425
151
  }
426
152
 
427
- purgeStale () {
428
- let deleted = false
429
- for (const i of this.rindexes({ allowStale: true })) {
430
- if (this.isStale(i)) {
431
- this.delete(this.keyList[i])
432
- deleted = true
433
- }
434
- }
435
- return deleted
436
- }
153
+ set (key, value, maxAge) {
154
+ maxAge = maxAge || this[MAX_AGE]
437
155
 
438
- dump () {
439
- const arr = []
440
- for (const i of this.indexes()) {
441
- const key = this.keyList[i]
442
- const value = this.valList[i]
443
- const entry = { value }
444
- if (this.ttls) {
445
- entry.ttl = this.ttls[i]
446
- }
447
- if (this.sizes) {
448
- entry.size = this.sizes[i]
449
- }
450
- arr.unshift([key, entry])
451
- }
452
- return arr
453
- }
156
+ if (maxAge && typeof maxAge !== 'number')
157
+ throw new TypeError('maxAge must be a number')
454
158
 
455
- load (arr) {
456
- this.clear()
457
- for (const [key, entry] of arr) {
458
- this.set(key, entry.value, entry)
459
- }
460
- }
159
+ const now = maxAge ? Date.now() : 0
160
+ const len = this[LENGTH_CALCULATOR](value, key)
461
161
 
462
- dispose (v, k, reason) {}
463
-
464
- set (k, v, {
465
- ttl = this.ttl,
466
- noDisposeOnSet = this.noDisposeOnSet,
467
- size = 0,
468
- sizeCalculation = this.sizeCalculation,
469
- noUpdateTTL = this.noUpdateTTL,
470
- } = {}) {
471
- size = this.requireSize(k, v, size, sizeCalculation)
472
- let index = this.size === 0 ? undefined : this.keyMap.get(k)
473
- if (index === undefined) {
474
- // addition
475
- index = this.newIndex()
476
- this.keyList[index] = k
477
- this.valList[index] = v
478
- this.keyMap.set(k, index)
479
- this.next[this.tail] = index
480
- this.prev[index] = this.tail
481
- this.tail = index
482
- this.size ++
483
- this.addItemSize(index, v, k, size)
484
- noUpdateTTL = false
485
- } else {
486
- // update
487
- const oldVal = this.valList[index]
488
- if (v !== oldVal) {
489
- if (this.isBackgroundFetch(oldVal)) {
490
- oldVal.__abortController.abort()
491
- } else {
492
- if (!noDisposeOnSet) {
493
- this.dispose(oldVal, k, 'set')
494
- if (this.disposeAfter) {
495
- this.disposed.push([oldVal, k, 'set'])
496
- }
497
- }
498
- }
499
- this.removeItemSize(index)
500
- this.valList[index] = v
501
- this.addItemSize(index, v, k, size)
162
+ if (this[CACHE].has(key)) {
163
+ if (len > this[MAX]) {
164
+ del(this, this[CACHE].get(key))
165
+ return false
502
166
  }
503
- this.moveToTail(index)
504
- }
505
- if (ttl !== 0 && this.ttl === 0 && !this.ttls) {
506
- this.initializeTTLTracking()
507
- }
508
- if (!noUpdateTTL) {
509
- this.setItemTTL(index, ttl)
510
- }
511
- if (this.disposeAfter) {
512
- while (this.disposed.length) {
513
- this.disposeAfter(...this.disposed.shift())
167
+
168
+ const node = this[CACHE].get(key)
169
+ const item = node.value
170
+
171
+ // dispose of the old one before overwriting
172
+ // split out into 2 ifs for better coverage tracking
173
+ if (this[DISPOSE]) {
174
+ if (!this[NO_DISPOSE_ON_SET])
175
+ this[DISPOSE](key, item.value)
514
176
  }
515
- }
516
- return this
517
- }
518
177
 
519
- newIndex () {
520
- if (this.size === 0) {
521
- return this.tail
178
+ item.now = now
179
+ item.maxAge = maxAge
180
+ item.value = value
181
+ this[LENGTH] += len - item.length
182
+ item.length = len
183
+ this.get(key)
184
+ trim(this)
185
+ return true
522
186
  }
523
- if (this.size === this.max) {
524
- return this.evict()
525
- }
526
- if (this.free.length !== 0) {
527
- return this.free.pop()
528
- }
529
- // initial fill, just keep writing down the list
530
- return this.initialFill++
531
- }
532
187
 
533
- pop () {
534
- if (this.size) {
535
- const val = this.valList[this.head]
536
- this.evict()
537
- return val
538
- }
539
- }
188
+ const hit = new Entry(key, value, len, now, maxAge)
540
189
 
541
- evict () {
542
- const head = this.head
543
- const k = this.keyList[head]
544
- const v = this.valList[head]
545
- if (this.isBackgroundFetch(v)) {
546
- v.__abortController.abort()
547
- } else {
548
- this.dispose(v, k, 'evict')
549
- if (this.disposeAfter) {
550
- this.disposed.push([v, k, 'evict'])
551
- }
552
- }
553
- this.removeItemSize(head)
554
- this.head = this.next[head]
555
- this.keyMap.delete(k)
556
- this.size --
557
- return head
558
- }
190
+ // oversized objects fall out of cache automatically.
191
+ if (hit.length > this[MAX]) {
192
+ if (this[DISPOSE])
193
+ this[DISPOSE](key, value)
559
194
 
560
- has (k, { updateAgeOnHas = this.updateAgeOnHas } = {}) {
561
- const index = this.keyMap.get(k)
562
- if (index !== undefined) {
563
- if (!this.isStale(index)) {
564
- if (updateAgeOnHas) {
565
- this.updateItemAge(index)
566
- }
567
- return true
568
- }
195
+ return false
569
196
  }
570
- return false
571
- }
572
197
 
573
- // like get(), but without any LRU updating or TTL expiration
574
- peek (k, { allowStale = this.allowStale } = {}) {
575
- const index = this.keyMap.get(k)
576
- if (index !== undefined && (allowStale || !this.isStale(index))) {
577
- return this.valList[index]
578
- }
198
+ this[LENGTH] += hit.length
199
+ this[LRU_LIST].unshift(hit)
200
+ this[CACHE].set(key, this[LRU_LIST].head)
201
+ trim(this)
202
+ return true
579
203
  }
580
204
 
581
- backgroundFetch (k, index, options) {
582
- const v = index === undefined ? undefined : this.valList[index]
583
- if (this.isBackgroundFetch(v)) {
584
- return v
585
- }
586
- const ac = new AC()
587
- const fetchOpts = {
588
- signal: ac.signal,
589
- options,
590
- }
591
- const p = Promise.resolve(this.fetchMethod(k, v, fetchOpts)).then(v => {
592
- if (!ac.signal.aborted) {
593
- this.set(k, v, fetchOpts.options)
594
- }
595
- return v
596
- })
597
- p.__abortController = ac
598
- p.__staleWhileFetching = v
599
- if (index === undefined) {
600
- this.set(k, p, fetchOpts.options)
601
- index = this.keyMap.get(k)
602
- } else {
603
- this.valList[index] = p
604
- }
605
- return p
205
+ has (key) {
206
+ if (!this[CACHE].has(key)) return false
207
+ const hit = this[CACHE].get(key).value
208
+ return !isStale(this, hit)
606
209
  }
607
210
 
608
- isBackgroundFetch (p) {
609
- return p && typeof p === 'object' && typeof p.then === 'function' &&
610
- Object.prototype.hasOwnProperty.call(p, '__staleWhileFetching')
211
+ get (key) {
212
+ return get(this, key, true)
611
213
  }
612
214
 
613
- // this takes the union of get() and set() opts, because it does both
614
- async fetch (k, {
615
- allowStale = this.allowStale,
616
- updateAgeOnGet = this.updateAgeOnGet,
617
- ttl = this.ttl,
618
- noDisposeOnSet = this.noDisposeOnSet,
619
- size = 0,
620
- sizeCalculation = this.sizeCalculation,
621
- noUpdateTTL = this.noUpdateTTL,
622
- } = {}) {
623
- if (!this.fetchMethod) {
624
- return this.get(k, {allowStale, updateAgeOnGet})
625
- }
626
-
627
- const options = {
628
- allowStale,
629
- updateAgeOnGet,
630
- ttl,
631
- noDisposeOnSet,
632
- size,
633
- sizeCalculation,
634
- noUpdateTTL,
635
- }
215
+ peek (key) {
216
+ return get(this, key, false)
217
+ }
636
218
 
637
- let index = this.keyMap.get(k)
638
- if (index === undefined) {
639
- return this.backgroundFetch(k, index, options)
640
- } else {
641
- // in cache, maybe already fetching
642
- const v = this.valList[index]
643
- if (this.isBackgroundFetch(v)) {
644
- return allowStale && v.__staleWhileFetching !== undefined
645
- ? v.__staleWhileFetching : v
646
- }
219
+ pop () {
220
+ const node = this[LRU_LIST].tail
221
+ if (!node)
222
+ return null
647
223
 
648
- if (!this.isStale(index)) {
649
- this.moveToTail(index)
650
- if (updateAgeOnGet) {
651
- this.updateItemAge(index)
652
- }
653
- return v
654
- }
224
+ del(this, node)
225
+ return node.value
226
+ }
655
227
 
656
- // ok, it is stale, and not already fetching
657
- // refresh the cache.
658
- const p = this.backgroundFetch(k, index, options)
659
- return allowStale && p.__staleWhileFetching !== undefined
660
- ? p.__staleWhileFetching : p
661
- }
228
+ del (key) {
229
+ del(this, this[CACHE].get(key))
662
230
  }
663
231
 
664
- get (k, {
665
- allowStale = this.allowStale,
666
- updateAgeOnGet = this.updateAgeOnGet,
667
- } = {}) {
668
- const index = this.keyMap.get(k)
669
- if (index !== undefined) {
670
- const value = this.valList[index]
671
- const fetching = this.isBackgroundFetch(value)
672
- if (this.isStale(index)) {
673
- // delete only if not an in-flight background fetch
674
- if (!fetching) {
675
- this.delete(k)
676
- return allowStale ? value : undefined
677
- } else {
678
- return allowStale ? value.__staleWhileFetching : undefined
679
- }
680
- } else {
681
- // if we're currently fetching it, we don't actually have it yet
682
- // it's not stale, which means this isn't a staleWhileRefetching,
683
- // so we just return undefined
684
- if (fetching) {
685
- return undefined
686
- }
687
- this.moveToTail(index)
688
- if (updateAgeOnGet) {
689
- this.updateItemAge(index)
232
+ load (arr) {
233
+ // reset the cache
234
+ this.reset()
235
+
236
+ const now = Date.now()
237
+ // A previous serialized cache has the most recent items first
238
+ for (let l = arr.length - 1; l >= 0; l--) {
239
+ const hit = arr[l]
240
+ const expiresAt = hit.e || 0
241
+ if (expiresAt === 0)
242
+ // the item was created without expiration in a non aged cache
243
+ this.set(hit.k, hit.v)
244
+ else {
245
+ const maxAge = expiresAt - now
246
+ // dont add already expired items
247
+ if (maxAge > 0) {
248
+ this.set(hit.k, hit.v, maxAge)
690
249
  }
691
- return value
692
250
  }
693
251
  }
694
252
  }
695
253
 
696
- connect (p, n) {
697
- this.prev[n] = p
698
- this.next[p] = n
254
+ prune () {
255
+ this[CACHE].forEach((value, key) => get(this, key, false))
699
256
  }
257
+ }
700
258
 
701
- moveToTail (index) {
702
- // if tail already, nothing to do
703
- // if head, move head to next[index]
704
- // else
705
- // move next[prev[index]] to next[index] (head has no prev)
706
- // move prev[next[index]] to prev[index]
707
- // prev[index] = tail
708
- // next[tail] = index
709
- // tail = index
710
- if (index !== this.tail) {
711
- if (index === this.head) {
712
- this.head = this.next[index]
713
- } else {
714
- this.connect(this.prev[index], this.next[index])
259
+ const get = (self, key, doUse) => {
260
+ const node = self[CACHE].get(key)
261
+ if (node) {
262
+ const hit = node.value
263
+ if (isStale(self, hit)) {
264
+ del(self, node)
265
+ if (!self[ALLOW_STALE])
266
+ return undefined
267
+ } else {
268
+ if (doUse) {
269
+ if (self[UPDATE_AGE_ON_GET])
270
+ node.value.now = Date.now()
271
+ self[LRU_LIST].unshiftNode(node)
715
272
  }
716
- this.connect(this.tail, index)
717
- this.tail = index
718
273
  }
274
+ return hit.value
719
275
  }
276
+ }
720
277
 
721
- get del () {
722
- deprecatedMethod('del', 'delete')
723
- return this.delete
724
- }
725
- delete (k) {
726
- let deleted = false
727
- if (this.size !== 0) {
728
- const index = this.keyMap.get(k)
729
- if (index !== undefined) {
730
- deleted = true
731
- if (this.size === 1) {
732
- this.clear()
733
- } else {
734
- this.removeItemSize(index)
735
- const v = this.valList[index]
736
- if (this.isBackgroundFetch(v)) {
737
- v.__abortController.abort()
738
- } else {
739
- this.dispose(v, k, 'delete')
740
- if (this.disposeAfter) {
741
- this.disposed.push([v, k, 'delete'])
742
- }
743
- }
744
- this.keyMap.delete(k)
745
- this.keyList[index] = null
746
- this.valList[index] = null
747
- if (index === this.tail) {
748
- this.tail = this.prev[index]
749
- } else if (index === this.head) {
750
- this.head = this.next[index]
751
- } else {
752
- this.next[this.prev[index]] = this.next[index]
753
- this.prev[this.next[index]] = this.prev[index]
754
- }
755
- this.size --
756
- this.free.push(index)
757
- }
758
- }
759
- }
760
- if (this.disposed) {
761
- while (this.disposed.length) {
762
- this.disposeAfter(...this.disposed.shift())
763
- }
278
+ const isStale = (self, hit) => {
279
+ if (!hit || (!hit.maxAge && !self[MAX_AGE]))
280
+ return false
281
+
282
+ const diff = Date.now() - hit.now
283
+ return hit.maxAge ? diff > hit.maxAge
284
+ : self[MAX_AGE] && (diff > self[MAX_AGE])
285
+ }
286
+
287
+ const trim = self => {
288
+ if (self[LENGTH] > self[MAX]) {
289
+ for (let walker = self[LRU_LIST].tail;
290
+ self[LENGTH] > self[MAX] && walker !== null;) {
291
+ // We know that we're about to delete this one, and also
292
+ // what the next least recently used key will be, so just
293
+ // go ahead and set it now.
294
+ const prev = walker.prev
295
+ del(self, walker)
296
+ walker = prev
764
297
  }
765
- return deleted
766
298
  }
299
+ }
767
300
 
768
- clear () {
769
- for (const index of this.rindexes({ allowStale: true })) {
770
- const v = this.valList[index]
771
- if (this.isBackgroundFetch(v)) {
772
- v.__abortController.abort()
773
- } else {
774
- const k = this.keyList[index]
775
- this.dispose(v, k, 'delete')
776
- if (this.disposeAfter) {
777
- this.disposed.push([v, k, 'delete'])
778
- }
779
- }
780
- }
301
+ const del = (self, node) => {
302
+ if (node) {
303
+ const hit = node.value
304
+ if (self[DISPOSE])
305
+ self[DISPOSE](hit.key, hit.value)
781
306
 
782
- this.keyMap.clear()
783
- this.valList.fill(null)
784
- this.keyList.fill(null)
785
- if (this.ttls) {
786
- this.ttls.fill(0)
787
- this.starts.fill(0)
788
- }
789
- if (this.sizes) {
790
- this.sizes.fill(0)
791
- }
792
- this.head = 0
793
- this.tail = 0
794
- this.initialFill = 1
795
- this.free.length = 0
796
- this.calculatedSize = 0
797
- this.size = 0
798
- if (this.disposed) {
799
- while (this.disposed.length) {
800
- this.disposeAfter(...this.disposed.shift())
801
- }
802
- }
307
+ self[LENGTH] -= hit.length
308
+ self[CACHE].delete(hit.key)
309
+ self[LRU_LIST].removeNode(node)
803
310
  }
804
- get reset () {
805
- deprecatedMethod('reset', 'clear')
806
- return this.clear
311
+ }
312
+
313
+ class Entry {
314
+ constructor (key, value, length, now, maxAge) {
315
+ this.key = key
316
+ this.value = value
317
+ this.length = length
318
+ this.now = now
319
+ this.maxAge = maxAge || 0
807
320
  }
321
+ }
808
322
 
809
- get length () {
810
- deprecatedProperty('length', 'size')
811
- return this.size
323
+ const forEachStep = (self, fn, node, thisp) => {
324
+ let hit = node.value
325
+ if (isStale(self, hit)) {
326
+ del(self, node)
327
+ if (!self[ALLOW_STALE])
328
+ hit = undefined
812
329
  }
330
+ if (hit)
331
+ fn.call(thisp, hit.value, hit.key, self)
813
332
  }
814
333
 
815
334
  module.exports = LRUCache