diff --git a/superset-frontend/packages/superset-ui-core/src/utils/index.ts b/superset-frontend/packages/superset-ui-core/src/utils/index.ts index 1c43663e28ef..19c5ed586145 100644 --- a/superset-frontend/packages/superset-ui-core/src/utils/index.ts +++ b/superset-frontend/packages/superset-ui-core/src/utils/index.ts @@ -26,6 +26,7 @@ export { default as makeSingleton } from './makeSingleton'; export { default as promiseTimeout } from './promiseTimeout'; export { default as logging } from './logging'; export { default as removeDuplicates } from './removeDuplicates'; +export { lruCache } from './lruCache'; export * from './featureFlags'; export * from './random'; export * from './typedMemo'; diff --git a/superset-frontend/packages/superset-ui-core/src/utils/lruCache.ts b/superset-frontend/packages/superset-ui-core/src/utils/lruCache.ts new file mode 100644 index 000000000000..f6785850c22a --- /dev/null +++ b/superset-frontend/packages/superset-ui-core/src/utils/lruCache.ts @@ -0,0 +1,74 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +class LRUCache { + private cache: Map; + + readonly capacity: number; + + constructor(capacity: number) { + if (capacity < 1) { + throw new Error('The capacity in LRU must be greater than 0.'); + } + this.capacity = capacity; + this.cache = new Map(); + } + + public has(key: string): boolean { + return this.cache.has(key); + } + + public get(key: string): T | undefined { + // Prevent runtime errors + if (typeof key !== 'string') { + throw new TypeError('The LRUCache key must be string.'); + } + + if (this.cache.has(key)) { + const tmp = this.cache.get(key) as T; + this.cache.delete(key); + this.cache.set(key, tmp); + return tmp; + } + return undefined; + } + + public set(key: string, value: T) { + // Prevent runtime errors + if (typeof key !== 'string') { + throw new TypeError('The LRUCache key must be string.'); + } + if (this.cache.size >= this.capacity) { + this.cache.delete(this.cache.keys().next().value); + } + this.cache.set(key, value); + } + + public clear() { + this.cache.clear(); + } + + public get size() { + return this.cache.size; + } +} + +export function lruCache(capacity = 100) { + return new LRUCache(capacity); +} diff --git a/superset-frontend/packages/superset-ui-core/test/utils/lruCache.test.ts b/superset-frontend/packages/superset-ui-core/test/utils/lruCache.test.ts new file mode 100644 index 000000000000..f8a077eba031 --- /dev/null +++ b/superset-frontend/packages/superset-ui-core/test/utils/lruCache.test.ts @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { lruCache } from '@superset-ui/core'; + +test('initial LRU', () => { + expect(lruCache().capacity).toBe(100); + expect(lruCache(10).capacity).toBe(10); + expect(lruCache(10).size).toBe(0); + expect(() => lruCache(0)).toThrow(Error); +}); + +test('LRU operations', () => { + const cache = lruCache(3); + cache.set('1', 'a'); + cache.set('2', 'b'); + cache.set('3', 'c'); + cache.set('4', 'd'); + expect(cache.size).toBe(3); + expect(cache.has('1')).toBeFalsy(); + expect(cache.get('1')).toBeUndefined(); + cache.get('2'); + cache.set('5', 'e'); + expect(cache.has('2')).toBeTruthy(); + expect(cache.has('3')).toBeFalsy(); + // @ts-expect-error + expect(() => cache.set(0)).toThrow(TypeError); + // @ts-expect-error + expect(() => cache.get(0)).toThrow(TypeError); + expect(cache.size).toBe(3); + cache.clear(); + expect(cache.size).toBe(0); + expect(cache.capacity).toBe(3); +}); + +test('LRU handle null and undefined', () => { + const cache = lruCache(); + cache.set('a', null); + cache.set('b', undefined); + expect(cache.has('a')).toBeTruthy(); + expect(cache.has('b')).toBeTruthy(); + expect(cache.get('a')).toBeNull(); + expect(cache.get('b')).toBeUndefined(); +});