UNPKG

10.7 kBPlain TextView Raw
1/*
2 * Copyright 2017-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
5 * the License. A copy of the License is located at
6 *
7 * http://aws.amazon.com/apache2.0/
8 *
9 * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
10 * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
11 * and limitations under the License.
12 */
13
14import { CacheList, defaultConfig, getCurrTime, CacheObject } from './Utils';
15
16import { StorageCache } from './StorageCache';
17import { ICache, CacheConfig, CacheItem, CacheItemOptions } from './types';
18import { ConsoleLogger as Logger } from '@aws-amplify/core';
19
20const logger = new Logger('InMemoryCache');
21
22/**
23 * Customized in-memory cache with LRU implemented
24 * @member cacheObj - object which store items
25 * @member cacheList - list of keys in the cache with LRU
26 * @member curSizeInBytes - current size of the cache
27 * @member maxPriority - max of the priority
28 * @member cacheSizeLimit - the limit of cache size
29 */
30export class InMemoryCacheClass extends StorageCache implements ICache {
31 private cacheList: CacheList[];
32 private curSizeInBytes: number;
33 private maxPriority: number;
34 private cacheSizeLimit: number;
35
36 /**
37 * initialize the cache
38 *
39 * @param config - the configuration of the cache
40 */
41 constructor(config?: CacheConfig) {
42 const cacheConfig = config
43 ? Object.assign({}, defaultConfig, config)
44 : defaultConfig;
45 super(cacheConfig);
46 logger.debug('now we start!');
47 this.cacheList = [];
48 this.curSizeInBytes = 0;
49 this.maxPriority = 5;
50
51 this.getItem = this.getItem.bind(this);
52 this.setItem = this.setItem.bind(this);
53 this.removeItem = this.removeItem.bind(this);
54
55 // initialize list for every priority
56 for (let i = 0; i < this.maxPriority; i += 1) {
57 this.cacheList[i] = new CacheList();
58 }
59 }
60
61 /**
62 * decrease current size of the cache
63 *
64 * @param amount - the amount of the cache size which needs to be decreased
65 */
66 private _decreaseCurSizeInBytes(amount: number): void {
67 this.curSizeInBytes -= amount;
68 }
69
70 /**
71 * increase current size of the cache
72 *
73 * @param amount - the amount of the cache szie which need to be increased
74 */
75 private _increaseCurSizeInBytes(amount: number): void {
76 this.curSizeInBytes += amount;
77 }
78
79 /**
80 * check whether item is expired
81 *
82 * @param key - the key of the item
83 *
84 * @return true if the item is expired.
85 */
86 private _isExpired(key: string): boolean {
87 const text: string | null = CacheObject.getItem(key);
88 const item: CacheItem = JSON.parse(text);
89 if (getCurrTime() >= item.expires) {
90 return true;
91 }
92 return false;
93 }
94
95 /**
96 * delete item from cache
97 *
98 * @param prefixedKey - the key of the item
99 * @param listIdx - indicates which cache list the key belongs to
100 */
101 private _removeItem(prefixedKey: string, listIdx: number): void {
102 // delete the key from the list
103 this.cacheList[listIdx].removeItem(prefixedKey);
104 // decrease the current size of the cache
105 this._decreaseCurSizeInBytes(
106 JSON.parse(CacheObject.getItem(prefixedKey)).byteSize
107 );
108 // finally remove the item from memory
109 CacheObject.removeItem(prefixedKey);
110 }
111
112 /**
113 * put item into cache
114 *
115 * @param prefixedKey - the key of the item
116 * @param itemData - the value of the item
117 * @param itemSizeInBytes - the byte size of the item
118 * @param listIdx - indicates which cache list the key belongs to
119 */
120 private _setItem(
121 prefixedKey: string,
122 item: CacheItem,
123 listIdx: number
124 ): void {
125 // insert the key into the list
126 this.cacheList[listIdx].insertItem(prefixedKey);
127 // increase the current size of the cache
128 this._increaseCurSizeInBytes(item.byteSize);
129 // finally add the item into memory
130 CacheObject.setItem(prefixedKey, JSON.stringify(item));
131 }
132
133 /**
134 * see whether cache is full
135 *
136 * @param itemSize
137 *
138 * @return true if cache is full
139 */
140 private _isCacheFull(itemSize: number): boolean {
141 return this.curSizeInBytes + itemSize > this.config.capacityInBytes;
142 }
143
144 /**
145 * check whether the cache contains the key
146 *
147 * @param key
148 */
149 private containsKey(key: string): number {
150 const prefixedKey: string = this.config.keyPrefix + key;
151 for (let i = 0; i < this.maxPriority; i += 1) {
152 if (this.cacheList[i].containsKey(prefixedKey)) {
153 return i + 1;
154 }
155 }
156 return -1;
157 }
158
159 /**
160 * * Set item into cache. You can put number, string, boolean or object.
161 * The cache will first check whether has the same key.
162 * If it has, it will delete the old item and then put the new item in
163 * The cache will pop out items if it is full
164 * You can specify the cache item options. The cache will abort and output a warning:
165 * If the key is invalid
166 * If the size of the item exceeds itemMaxSize.
167 * If the value is undefined
168 * If incorrect cache item configuration
169 * If error happened with browser storage
170 *
171 * @param key - the key of the item
172 * @param value - the value of the item
173 * @param options - optional, the specified meta-data
174 *
175 * @throws if the item is too big which exceeds the limit of single item size
176 * @throws if the key is invalid
177 */
178 public setItem(
179 key: string,
180 value: object | string | number | boolean,
181 options?: CacheItemOptions
182 ): void {
183 const prefixedKey: string = this.config.keyPrefix + key;
184 // invalid keys
185 if (
186 prefixedKey === this.config.keyPrefix ||
187 prefixedKey === this.cacheCurSizeKey
188 ) {
189 logger.warn(`Invalid key: should not be empty or 'CurSize'`);
190 return;
191 }
192
193 if (typeof value === 'undefined') {
194 logger.warn(`The value of item should not be undefined!`);
195 return;
196 }
197
198 const cacheItemOptions: CacheItemOptions = {
199 priority:
200 options && options.priority !== undefined
201 ? options.priority
202 : this.config.defaultPriority,
203 expires:
204 options && options.expires !== undefined
205 ? options.expires
206 : this.config.defaultTTL + getCurrTime(),
207 };
208
209 if (cacheItemOptions.priority < 1 || cacheItemOptions.priority > 5) {
210 logger.warn(
211 `Invalid parameter: priority due to out or range. It should be within 1 and 5.`
212 );
213 return;
214 }
215
216 const item: CacheItem = this.fillCacheItem(
217 prefixedKey,
218 value,
219 cacheItemOptions
220 );
221
222 // check wether this item is too big;
223 if (item.byteSize > this.config.itemMaxSize) {
224 logger.warn(
225 `Item with key: ${key} you are trying to put into is too big!`
226 );
227 return;
228 }
229
230 // if key already in the cache, then delete it.
231 const presentKeyPrio: number = this.containsKey(key);
232 if (presentKeyPrio !== -1) {
233 this._removeItem(prefixedKey, presentKeyPrio - 1);
234 }
235
236 // pop out items in the cache when cache is full based on LRU
237 // first start from lowest priority cache list
238 let cacheListIdx = this.maxPriority - 1;
239 while (this._isCacheFull(item.byteSize) && cacheListIdx >= 0) {
240 if (!this.cacheList[cacheListIdx].isEmpty()) {
241 const popedItemKey = this.cacheList[cacheListIdx].getLastItem();
242 this._removeItem(popedItemKey, cacheListIdx);
243 } else {
244 cacheListIdx -= 1;
245 }
246 }
247
248 this._setItem(prefixedKey, item, Number(item.priority) - 1);
249 }
250
251 /**
252 * Get item from cache. It will return null if item doesn’t exist or it has been expired.
253 * If you specified callback function in the options,
254 * then the function will be executed if no such item in the cache
255 * and finally put the return value into cache.
256 * Please make sure the callback function will return the value you want to put into the cache.
257 * The cache will abort output a warning:
258 * If the key is invalid
259 *
260 * @param key - the key of the item
261 * @param options - the options of callback function
262 */
263 public getItem(key: string, options?: CacheItemOptions): any {
264 let ret: string | null = null;
265 const prefixedKey: string = this.config.keyPrefix + key;
266
267 if (
268 prefixedKey === this.config.keyPrefix ||
269 prefixedKey === this.cacheCurSizeKey
270 ) {
271 logger.warn(`Invalid key: should not be empty or 'CurSize'`);
272 return null;
273 }
274
275 // check whether it's in the cachelist
276 const presentKeyPrio: number = this.containsKey(key);
277 if (presentKeyPrio !== -1) {
278 if (this._isExpired(prefixedKey)) {
279 // if expired, remove that item and return null
280 this._removeItem(prefixedKey, presentKeyPrio - 1);
281 } else {
282 // if not expired, great, return the value and refresh it
283 ret = CacheObject.getItem(prefixedKey);
284 const item: CacheItem = JSON.parse(ret);
285 this.cacheList[item.priority - 1].refresh(prefixedKey);
286 return item.data;
287 }
288 }
289
290 if (options && options.callback !== undefined) {
291 const val: object | string | number | boolean = options.callback();
292 if (val !== null) {
293 this.setItem(key, val, options);
294 }
295 return val;
296 }
297 return null;
298 }
299
300 /**
301 * remove item from the cache
302 *
303 * @param key - the key of the item
304 */
305 public removeItem(key: string): void {
306 const prefixedKey: string = this.config.keyPrefix + key;
307
308 // check if the key is in the cache
309 const presentKeyPrio: number = this.containsKey(key);
310 if (presentKeyPrio !== -1) {
311 this._removeItem(prefixedKey, presentKeyPrio - 1);
312 }
313 }
314
315 /**
316 * clear the entire cache
317 */
318 public clear(): void {
319 for (let i = 0; i < this.maxPriority; i += 1) {
320 for (const key of this.cacheList[i].getKeys()) {
321 this._removeItem(key, i);
322 }
323 }
324 }
325
326 /**
327 * Return all the keys in the cache.
328 */
329 public getAllKeys(): string[] {
330 const keys: string[] = [];
331 for (let i = 0; i < this.maxPriority; i += 1) {
332 for (const key of this.cacheList[i].getKeys()) {
333 keys.push(key.substring(this.config.keyPrefix.length));
334 }
335 }
336
337 return keys;
338 }
339
340 /**
341 * return the current size of the cache
342 *
343 * @return the current size of the cache
344 */
345 public getCacheCurSize(): number {
346 return this.curSizeInBytes;
347 }
348
349 /**
350 * Return a new instance of cache with customized configuration.
351 * @param config - the customized configuration
352 */
353 public createInstance(config: CacheConfig): ICache {
354 return new InMemoryCacheClass(config);
355 }
356}
357
358export const InMemoryCache: ICache = new InMemoryCacheClass();
359/**
360 * @deprecated use named import
361 */
362export default InMemoryCache;