Skip to content

Commit 0861538

Browse files
Replace lru-cache package with simple local implementation (FF-1876) (#47)
1 parent 40cbd2b commit 0861538

File tree

5 files changed

+128
-11
lines changed

5 files changed

+128
-11
lines changed

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@eppo/js-client-sdk-common",
3-
"version": "2.2.2",
3+
"version": "2.2.3",
44
"description": "Eppo SDK for client-side JavaScript applications (base for both web and react native)",
55
"main": "dist/index.js",
66
"files": [
@@ -65,7 +65,6 @@
6565
},
6666
"dependencies": {
6767
"axios": "^1.6.0",
68-
"lru-cache": "^10.0.1",
6968
"md5": "^2.3.0",
7069
"pino": "^8.19.0",
7170
"semver": "^7.5.4",

src/assignment-cache.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { LRUCache } from 'lru-cache';
2-
31
import { EppoValue } from './eppo_value';
2+
import { LRUCache } from './lru-cache';
43

54
export interface AssignmentCacheKey {
65
subjectKey: string;
@@ -69,8 +68,8 @@ export class NonExpiringInMemoryAssignmentCache extends AssignmentCache<Map<stri
6968
* multiple users. In this case, the cache size should be set to the maximum number
7069
* of users that can be active at the same time.
7170
*/
72-
export class LRUInMemoryAssignmentCache extends AssignmentCache<LRUCache<string, string>> {
71+
export class LRUInMemoryAssignmentCache extends AssignmentCache<LRUCache> {
7372
constructor(maxSize: number) {
74-
super(new LRUCache<string, string>({ max: maxSize }));
73+
super(new LRUCache(maxSize));
7574
}
7675
}

src/lru-cache.spec.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { LRUCache } from './lru-cache';
2+
3+
describe('LRUCache', () => {
4+
let cache: LRUCache;
5+
6+
beforeEach(() => {
7+
cache = new LRUCache(2);
8+
});
9+
10+
it('should insert and retrieve a value', () => {
11+
cache.set('a', 'apple');
12+
expect(cache.get('a')).toBe('apple');
13+
});
14+
15+
it('should return undefined for missing values', () => {
16+
expect(cache.get('missing')).toBeUndefined();
17+
});
18+
19+
it('should overwrite existing values', () => {
20+
cache.set('a', 'apple');
21+
cache.set('a', 'avocado');
22+
expect(cache.get('a')).toBe('avocado');
23+
});
24+
25+
it('should evict least recently used item', () => {
26+
cache.set('a', 'apple');
27+
cache.set('b', 'banana');
28+
cache.set('c', 'cherry');
29+
expect(cache.get('a')).toBeUndefined();
30+
expect(cache.get('b')).toBe('banana');
31+
expect(cache.get('c')).toBe('cherry');
32+
});
33+
34+
it('should move recently used item to the end of the cache', () => {
35+
cache.set('a', 'apple');
36+
cache.set('b', 'banana');
37+
cache.get('a'); // Access 'a' to make it recently used
38+
cache.set('c', 'cherry');
39+
expect(cache.get('a')).toBe('apple');
40+
expect(cache.get('b')).toBeUndefined();
41+
expect(cache.get('c')).toBe('cherry');
42+
});
43+
44+
it('should check if a key exists', () => {
45+
cache.set('a', 'apple');
46+
expect(cache.has('a')).toBeTruthy();
47+
expect(cache.has('b')).toBeFalsy();
48+
});
49+
50+
it('should handle the cache capacity of zero', () => {
51+
const zeroCache = new LRUCache(0);
52+
zeroCache.set('a', 'apple');
53+
expect(zeroCache.get('a')).toBeUndefined();
54+
});
55+
56+
it('should handle the cache capacity of one', () => {
57+
const oneCache = new LRUCache(1);
58+
oneCache.set('a', 'apple');
59+
expect(oneCache.get('a')).toBe('apple');
60+
oneCache.set('b', 'banana');
61+
expect(oneCache.get('a')).toBeUndefined();
62+
expect(oneCache.get('b')).toBe('banana');
63+
});
64+
});

src/lru-cache.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* LRUCache is a cache that stores a maximum number of items.
3+
*
4+
* Items are removed from the cache when the cache is full.
5+
*
6+
* The cache is implemented as a Map, which is a built-in JavaScript object.
7+
* The Map object holds key-value pairs and remembers the order of key-value pairs as they were inserted.
8+
*/
9+
export class LRUCache {
10+
private capacity: number;
11+
private cache: Map<string, string>;
12+
13+
constructor(capacity: number) {
14+
this.capacity = capacity;
15+
this.cache = new Map<string, string>();
16+
}
17+
18+
has(key: string): boolean {
19+
return this.cache.has(key);
20+
}
21+
22+
get(key: string): string | undefined {
23+
if (!this.cache.has(key)) {
24+
return undefined;
25+
}
26+
27+
const value = this.cache.get(key);
28+
29+
if (value !== undefined) {
30+
// the delete and set operations are used together to ensure that the most recently accessed
31+
// or added item is always considered the "newest" in terms of access order.
32+
// This is crucial for maintaining the correct order of elements in the cache,
33+
// which directly impacts which item is considered the least recently used (LRU) and
34+
// thus eligible for eviction when the cache reaches its capacity.
35+
this.cache.delete(key);
36+
this.cache.set(key, value);
37+
}
38+
39+
return value;
40+
}
41+
42+
set(key: string, value: string): void {
43+
if (this.capacity === 0) {
44+
return;
45+
}
46+
47+
if (this.cache.has(key)) {
48+
this.cache.delete(key);
49+
} else if (this.cache.size >= this.capacity) {
50+
// To evict the least recently used (LRU) item, we retrieve the first key in the Map.
51+
// This is possible because the Map object in JavaScript maintains the insertion order of the keys.
52+
// Therefore, the first key represents the oldest entry, which is the least recently used item in our cache.
53+
// We use Map.prototype.keys().next().value to obtain this oldest key and then delete it from the cache.
54+
const oldestKey = this.cache.keys().next().value;
55+
this.cache.delete(oldestKey);
56+
}
57+
58+
this.cache.set(key, value);
59+
}
60+
}

yarn.lock

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3116,11 +3116,6 @@ lodash@^4.17.21:
31163116
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
31173117
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
31183118

3119-
lru-cache@^10.0.1:
3120-
version "10.0.1"
3121-
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz"
3122-
integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==
3123-
31243119
lru-cache@^5.1.1:
31253120
version "5.1.1"
31263121
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz"

0 commit comments

Comments
 (0)