UNPKG

3.97 kBJavaScriptView Raw
1/* eslint-env browser */
2
3/**
4 * An implementation of a map which has keys that expire.
5 *
6 * @module cache
7 */
8
9import * as list from './list.js'
10import * as map from './map.js'
11import * as time from './time.js'
12
13/**
14 * @template K, V
15 *
16 * @implements {list.ListNode}
17 */
18class Entry {
19 /**
20 * @param {K} key
21 * @param {V | Promise<V>} val
22 */
23 constructor (key, val) {
24 /**
25 * @type {this | null}
26 */
27 this.prev = null
28 /**
29 * @type {this | null}
30 */
31 this.next = null
32 this.created = time.getUnixTime()
33 this.val = val
34 this.key = key
35 }
36}
37
38/**
39 * @template K, V
40 */
41export class Cache {
42 /**
43 * @param {number} timeout
44 */
45 constructor (timeout) {
46 this.timeout = timeout
47 /**
48 * @type list.List<Entry<K, V>>
49 */
50 this._q = list.create()
51 /**
52 * @type {Map<K, Entry<K, V>>}
53 */
54 this._map = map.create()
55 }
56}
57
58/**
59 * @template K, V
60 *
61 * @param {Cache<K, V>} cache
62 * @return {number} Returns the current timestamp
63 */
64export const removeStale = cache => {
65 const now = time.getUnixTime()
66 const q = cache._q
67 while (q.start && now - q.start.created > cache.timeout) {
68 cache._map.delete(q.start.key)
69 list.popFront(q)
70 }
71 return now
72}
73
74/**
75 * @template K, V
76 *
77 * @param {Cache<K, V>} cache
78 * @param {K} key
79 * @param {V} value
80 */
81export const set = (cache, key, value) => {
82 const now = removeStale(cache)
83 const q = cache._q
84 const n = cache._map.get(key)
85 if (n) {
86 list.removeNode(q, n)
87 list.pushEnd(q, n)
88 n.created = now
89 n.val = value
90 } else {
91 const node = new Entry(key, value)
92 list.pushEnd(q, node)
93 cache._map.set(key, node)
94 }
95}
96
97/**
98 * @template K, V
99 *
100 * @param {Cache<K, V>} cache
101 * @param {K} key
102 * @return {Entry<K, V> | undefined}
103 */
104const getNode = (cache, key) => {
105 removeStale(cache)
106 const n = cache._map.get(key)
107 if (n) {
108 return n
109 }
110}
111
112/**
113 * @template K, V
114 *
115 * @param {Cache<K, V>} cache
116 * @param {K} key
117 * @return {V | undefined}
118 */
119export const get = (cache, key) => {
120 const n = getNode(cache, key)
121 return n && !(n.val instanceof Promise) ? n.val : undefined
122}
123
124/**
125 * @template K, V
126 *
127 * @param {Cache<K, V>} cache
128 * @param {K} key
129 */
130export const refreshTimeout = (cache, key) => {
131 const now = time.getUnixTime()
132 const q = cache._q
133 const n = cache._map.get(key)
134 if (n) {
135 list.removeNode(q, n)
136 list.pushEnd(q, n)
137 n.created = now
138 }
139}
140
141/**
142 * Works well in conjunktion with setIfUndefined which has an async init function.
143 * Using getAsync & setIfUndefined ensures that the init function is only called once.
144 *
145 * @template K, V
146 *
147 * @param {Cache<K, V>} cache
148 * @param {K} key
149 * @return {V | Promise<V> | undefined}
150 */
151export const getAsync = (cache, key) => {
152 const n = getNode(cache, key)
153 return n ? n.val : undefined
154}
155
156/**
157 * @template K, V
158 *
159 * @param {Cache<K, V>} cache
160 * @param {K} key
161 */
162export const remove = (cache, key) => {
163 const n = cache._map.get(key)
164 if (n) {
165 list.removeNode(cache._q, n)
166 cache._map.delete(key)
167 return n.val && !(n.val instanceof Promise) ? n.val : undefined
168 }
169}
170
171/**
172 * @template K, V
173 *
174 * @param {Cache<K, V>} cache
175 * @param {K} key
176 * @param {function():Promise<V>} init
177 * @param {boolean} removeNull Optional argument that automatically removes values that resolve to null/undefined from the cache.
178 * @return {Promise<V> | V}
179 */
180export const setIfUndefined = (cache, key, init, removeNull = false) => {
181 removeStale(cache)
182 const q = cache._q
183 const n = cache._map.get(key)
184 if (n) {
185 return n.val
186 } else {
187 const p = init()
188 const node = new Entry(key, p)
189 list.pushEnd(q, node)
190 cache._map.set(key, node)
191 p.then(v => {
192 if (p === node.val) {
193 node.val = v
194 }
195 if (removeNull && v == null) {
196 remove(cache, key)
197 }
198 })
199 return p
200 }
201}
202
203/**
204 * @param {number} timeout
205 */
206export const create = timeout => new Cache(timeout)