UNPKG

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