forked from mcollina/async-cache-dedupe
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
135 lines (110 loc) · 2.89 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
'use strict'
const kValues = require('./symbol')
const stringify = require('safe-stable-stringify')
const LRUCache = require('mnemonist/lru-cache')
const kCacheSize = Symbol('kCacheSize')
const kTTL = Symbol('kTTL')
class Cache {
constructor (opts) {
opts = opts || {}
this[kValues] = {}
this[kCacheSize] = opts.cacheSize || 1024
this[kTTL] = opts.ttl || 0
}
define (key, opts, func) {
if (typeof opts === 'function') {
func = opts
opts = {}
}
if (key && this[key]) {
throw new Error(`${key} is already defined in the cache or it is a forbidden name`)
}
opts = opts || {}
if (typeof func !== 'function') {
throw new TypeError(`Missing the function parameter for '${key}'`)
}
const serialize = opts.serialize
if (serialize && typeof serialize !== 'function') {
throw new TypeError('serialize must be a function')
}
const cacheSize = opts.cacheSize || this[kCacheSize]
const ttl = opts.ttl || this[kTTL]
const wrapper = new Wrapper(func, key, serialize, cacheSize, ttl)
this[kValues][key] = wrapper
this[key] = wrapper.add.bind(wrapper)
}
clear (key, value) {
if (key) {
this[kValues][key].clear(value)
return
}
for (const wrapper of Object.values(this[kValues])) {
wrapper.clear()
}
}
}
let _currentSecond
function currentSecond () {
if (_currentSecond !== undefined) {
return _currentSecond
}
_currentSecond = Math.floor(Date.now() / 1000)
setTimeout(_clearSecond, 1000).unref()
return _currentSecond
}
function _clearSecond () {
_currentSecond = undefined
}
class Wrapper {
constructor (func, key, serialize, cacheSize, ttl) {
this.ids = new LRUCache(cacheSize)
this.error = null
this.started = false
this.func = func
this.key = key
this.serialize = serialize
this.ttl = ttl
}
buildPromise (query, args, key) {
query.promise = this.func(args, key)
// we fork the promise chain on purpose
query.promise.catch(() => this.ids.set(key, undefined))
if (this.ttl > 0) {
query.cachedOn = currentSecond()
}
}
getKey (args) {
const id = this.serialize ? this.serialize(args) : args
return typeof id === 'string' ? id : stringify(id)
}
add (args) {
const key = this.getKey(args)
let query = this.ids.get(key)
if (!query) {
query = new Query()
this.buildPromise(query, args, key)
this.ids.set(key, query)
} else if (this.ttl > 0) {
if (currentSecond() - query.cachedOn > this.ttl) {
// restart
this.buildPromise(query, args, key)
}
}
return query.promise
}
clear (value) {
if (value) {
const key = this.getKey(value)
this.ids.set(key, undefined)
return
}
this.ids.clear()
}
}
class Query {
constructor () {
this.promise = null
this.cachedOn = null
}
}
module.exports.Cache = Cache