@umituz/react-native-storage 2.5.0 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -3
- package/src/cache/__tests__/PerformanceAndMemory.test.ts +386 -0
- package/src/cache/__tests__/setup.ts +19 -0
- package/src/cache/domain/Cache.ts +146 -0
- package/src/cache/domain/CacheManager.ts +48 -0
- package/src/cache/domain/CacheStatsTracker.ts +49 -0
- package/src/cache/domain/ErrorHandler.ts +42 -0
- package/src/cache/domain/PatternMatcher.ts +30 -0
- package/src/cache/domain/__tests__/Cache.test.ts +292 -0
- package/src/cache/domain/__tests__/CacheManager.test.ts +276 -0
- package/src/cache/domain/__tests__/ErrorHandler.test.ts +303 -0
- package/src/cache/domain/__tests__/PatternMatcher.test.ts +261 -0
- package/src/cache/domain/strategies/EvictionStrategy.ts +9 -0
- package/src/cache/domain/strategies/FIFOStrategy.ts +12 -0
- package/src/cache/domain/strategies/LFUStrategy.ts +22 -0
- package/src/cache/domain/strategies/LRUStrategy.ts +22 -0
- package/src/cache/domain/strategies/TTLStrategy.ts +23 -0
- package/src/cache/domain/strategies/__tests__/EvictionStrategies.test.ts +293 -0
- package/src/cache/domain/types/Cache.ts +28 -0
- package/src/cache/index.ts +28 -0
- package/src/cache/infrastructure/TTLCache.ts +103 -0
- package/src/cache/infrastructure/__tests__/TTLCache.test.ts +303 -0
- package/src/cache/presentation/__tests__/ReactHooks.test.ts +512 -0
- package/src/cache/presentation/useCache.ts +76 -0
- package/src/cache/presentation/useCachedValue.ts +88 -0
- package/src/cache/types.d.ts +3 -0
- package/src/index.ts +28 -0
- package/src/types/global.d.ts +2 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TTLCache Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { TTLCache } from '../TTLCache';
|
|
6
|
+
import type { CacheConfig } from '../domain/types/Cache';
|
|
7
|
+
|
|
8
|
+
describe('TTLCache', () => {
|
|
9
|
+
let cache: TTLCache<string>;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.useFakeTimers();
|
|
13
|
+
cache = new TTLCache<string>({
|
|
14
|
+
maxSize: 5,
|
|
15
|
+
defaultTTL: 1000,
|
|
16
|
+
cleanupIntervalMs: 500
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
jest.useRealTimers();
|
|
22
|
+
cache.destroy();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('Basic Functionality', () => {
|
|
26
|
+
test('should inherit from Cache', () => {
|
|
27
|
+
expect(cache).toHaveProperty('set');
|
|
28
|
+
expect(cache).toHaveProperty('get');
|
|
29
|
+
expect(cache).toHaveProperty('has');
|
|
30
|
+
expect(cache).toHaveProperty('delete');
|
|
31
|
+
expect(cache).toHaveProperty('clear');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('should set and get values', () => {
|
|
35
|
+
cache.set('key1', 'value1');
|
|
36
|
+
expect(cache.get('key1')).toBe('value1');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('should use default TTL', () => {
|
|
40
|
+
cache.set('key1', 'value1');
|
|
41
|
+
expect(cache.get('key1')).toBe('value1');
|
|
42
|
+
|
|
43
|
+
jest.advanceTimersByTime(1001);
|
|
44
|
+
expect(cache.get('key1')).toBeUndefined();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('should use custom TTL', () => {
|
|
48
|
+
cache.set('key1', 'value1', 2000);
|
|
49
|
+
expect(cache.get('key1')).toBe('value1');
|
|
50
|
+
|
|
51
|
+
jest.advanceTimersByTime(1001);
|
|
52
|
+
expect(cache.get('key1')).toBe('value1'); // Should still exist
|
|
53
|
+
|
|
54
|
+
jest.advanceTimersByTime(1000);
|
|
55
|
+
expect(cache.get('key1')).toBeUndefined(); // Should be expired
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('Automatic Cleanup', () => {
|
|
60
|
+
test('should start cleanup interval on creation', () => {
|
|
61
|
+
const setIntervalSpy = jest.spyOn(global, 'setInterval');
|
|
62
|
+
new TTLCache<string>({ cleanupIntervalMs: 1000 });
|
|
63
|
+
|
|
64
|
+
expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), 1000);
|
|
65
|
+
setIntervalSpy.mockRestore();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('should cleanup expired entries automatically', () => {
|
|
69
|
+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
70
|
+
|
|
71
|
+
cache.set('key1', 'value1', 500);
|
|
72
|
+
cache.set('key2', 'value2', 1500);
|
|
73
|
+
|
|
74
|
+
// Fast forward to trigger first cleanup
|
|
75
|
+
jest.advanceTimersByTime(500);
|
|
76
|
+
|
|
77
|
+
// key1 should be cleaned up, key2 should remain
|
|
78
|
+
expect(cache.get('key1')).toBeUndefined();
|
|
79
|
+
expect(cache.get('key2')).toBe('value2');
|
|
80
|
+
|
|
81
|
+
expect(consoleSpy).toHaveBeenCalledWith('TTLCache: Cleaned up 1 expired entries');
|
|
82
|
+
|
|
83
|
+
consoleSpy.mockRestore();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('should not log when no entries are cleaned up', () => {
|
|
87
|
+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
88
|
+
|
|
89
|
+
cache.set('key1', 'value1', 2000);
|
|
90
|
+
|
|
91
|
+
// Fast forward, but no entries should be expired yet
|
|
92
|
+
jest.advanceTimersByTime(500);
|
|
93
|
+
|
|
94
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
95
|
+
|
|
96
|
+
consoleSpy.mockRestore();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('should handle multiple cleanup cycles', () => {
|
|
100
|
+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
101
|
+
|
|
102
|
+
cache.set('key1', 'value1', 300);
|
|
103
|
+
cache.set('key2', 'value2', 800);
|
|
104
|
+
cache.set('key3', 'value3', 1300);
|
|
105
|
+
|
|
106
|
+
// First cleanup - key1 expires
|
|
107
|
+
jest.advanceTimersByTime(500);
|
|
108
|
+
expect(cache.get('key1')).toBeUndefined();
|
|
109
|
+
expect(cache.get('key2')).toBe('value2');
|
|
110
|
+
expect(cache.get('key3')).toBe('value3');
|
|
111
|
+
|
|
112
|
+
// Second cleanup - key2 expires
|
|
113
|
+
jest.advanceTimersByTime(500);
|
|
114
|
+
expect(cache.get('key2')).toBeUndefined();
|
|
115
|
+
expect(cache.get('key3')).toBe('value3');
|
|
116
|
+
|
|
117
|
+
// Third cleanup - key3 expires
|
|
118
|
+
jest.advanceTimersByTime(500);
|
|
119
|
+
expect(cache.get('key3')).toBeUndefined();
|
|
120
|
+
|
|
121
|
+
expect(consoleSpy).toHaveBeenCalledTimes(3);
|
|
122
|
+
|
|
123
|
+
consoleSpy.mockRestore();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('Destroy Method', () => {
|
|
128
|
+
test('should clear cleanup interval on destroy', () => {
|
|
129
|
+
const clearIntervalSpy = jest.spyOn(global, 'clearInterval');
|
|
130
|
+
|
|
131
|
+
cache.destroy();
|
|
132
|
+
|
|
133
|
+
expect(clearIntervalSpy).toHaveBeenCalled();
|
|
134
|
+
clearIntervalSpy.mockRestore();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('should clear all data on destroy', () => {
|
|
138
|
+
cache.set('key1', 'value1');
|
|
139
|
+
cache.set('key2', 'value2');
|
|
140
|
+
|
|
141
|
+
expect(cache.getStats().size).toBe(2);
|
|
142
|
+
|
|
143
|
+
cache.destroy();
|
|
144
|
+
|
|
145
|
+
expect(cache.getStats().size).toBe(0);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('should handle multiple destroy calls', () => {
|
|
149
|
+
const clearIntervalSpy = jest.spyOn(global, 'clearInterval');
|
|
150
|
+
|
|
151
|
+
cache.destroy();
|
|
152
|
+
cache.destroy();
|
|
153
|
+
cache.destroy();
|
|
154
|
+
|
|
155
|
+
// Should only call clearInterval once
|
|
156
|
+
expect(clearIntervalSpy).toHaveBeenCalledTimes(1);
|
|
157
|
+
|
|
158
|
+
clearIntervalSpy.mockRestore();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('should not cleanup after destroy', () => {
|
|
162
|
+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
163
|
+
|
|
164
|
+
cache.set('key1', 'value1', 300);
|
|
165
|
+
|
|
166
|
+
cache.destroy();
|
|
167
|
+
|
|
168
|
+
// Advance time - should not trigger cleanup
|
|
169
|
+
jest.advanceTimersByTime(1000);
|
|
170
|
+
|
|
171
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
172
|
+
|
|
173
|
+
consoleSpy.mockRestore();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('Destroyed State Handling', () => {
|
|
178
|
+
beforeEach(() => {
|
|
179
|
+
cache.destroy();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('should warn on set after destroy', () => {
|
|
183
|
+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
184
|
+
|
|
185
|
+
cache.set('key1', 'value1');
|
|
186
|
+
|
|
187
|
+
expect(consoleSpy).toHaveBeenCalledWith('TTLCache: Attempted to set value on destroyed cache');
|
|
188
|
+
expect(cache.get('key1')).toBeUndefined();
|
|
189
|
+
|
|
190
|
+
consoleSpy.mockRestore();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('should warn on get after destroy', () => {
|
|
194
|
+
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
195
|
+
|
|
196
|
+
const result = cache.get('key1');
|
|
197
|
+
|
|
198
|
+
expect(consoleSpy).toHaveBeenCalledWith('TTLCache: Attempted to get value from destroyed cache');
|
|
199
|
+
expect(result).toBeUndefined();
|
|
200
|
+
|
|
201
|
+
consoleSpy.mockRestore();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('should return false for has after destroy', () => {
|
|
205
|
+
expect(cache.has('key1')).toBe(false);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test('should return false for delete after destroy', () => {
|
|
209
|
+
expect(cache.delete('key1')).toBe(false);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('should not throw on clear after destroy', () => {
|
|
213
|
+
expect(() => cache.clear()).not.toThrow();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('Configuration', () => {
|
|
218
|
+
test('should use custom cleanup interval', () => {
|
|
219
|
+
const setIntervalSpy = jest.spyOn(global, 'setInterval');
|
|
220
|
+
|
|
221
|
+
new TTLCache<string>({ cleanupIntervalMs: 2000 });
|
|
222
|
+
|
|
223
|
+
expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), 2000);
|
|
224
|
+
setIntervalSpy.mockRestore();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test('should use default cleanup interval when not specified', () => {
|
|
228
|
+
const setIntervalSpy = jest.spyOn(global, 'setInterval');
|
|
229
|
+
|
|
230
|
+
new TTLCache<string>();
|
|
231
|
+
|
|
232
|
+
expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), 60000);
|
|
233
|
+
setIntervalSpy.mockRestore();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('should pass configuration to parent Cache', () => {
|
|
237
|
+
const config: CacheConfig = {
|
|
238
|
+
maxSize: 10,
|
|
239
|
+
defaultTTL: 5000,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const customCache = new TTLCache<string>(config);
|
|
243
|
+
|
|
244
|
+
customCache.set('key1', 'value1');
|
|
245
|
+
expect(customCache.get('key1')).toBe('value1');
|
|
246
|
+
|
|
247
|
+
// Should use custom default TTL
|
|
248
|
+
jest.advanceTimersByTime(5001);
|
|
249
|
+
expect(customCache.get('key1')).toBeUndefined();
|
|
250
|
+
|
|
251
|
+
customCache.destroy();
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe('Memory Management', () => {
|
|
256
|
+
test('should not memory leak with many entries', () => {
|
|
257
|
+
const entryCount = 1000;
|
|
258
|
+
|
|
259
|
+
// Create many entries with short TTL
|
|
260
|
+
for (let i = 0; i < entryCount; i++) {
|
|
261
|
+
cache.set(`key${i}`, `value${i}`, 100);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
expect(cache.getStats().size).toBe(entryCount);
|
|
265
|
+
|
|
266
|
+
// Let all entries expire and cleanup
|
|
267
|
+
jest.advanceTimersByTime(500);
|
|
268
|
+
|
|
269
|
+
expect(cache.getStats().size).toBe(0);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('should handle rapid create/destroy cycles', () => {
|
|
273
|
+
for (let i = 0; i < 10; i++) {
|
|
274
|
+
const tempCache = new TTLCache<string>({ cleanupIntervalMs: 100 });
|
|
275
|
+
tempCache.set('key', 'value');
|
|
276
|
+
tempCache.destroy();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Should not throw or cause issues
|
|
280
|
+
expect(true).toBe(true);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe('Edge Cases', () => {
|
|
285
|
+
test('should handle zero cleanup interval', () => {
|
|
286
|
+
expect(() => {
|
|
287
|
+
new TTLCache<string>({ cleanupIntervalMs: 0 });
|
|
288
|
+
}).not.toThrow();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test('should handle negative cleanup interval', () => {
|
|
292
|
+
expect(() => {
|
|
293
|
+
new TTLCache<string>({ cleanupIntervalMs: -100 });
|
|
294
|
+
}).not.toThrow();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('should handle very large cleanup interval', () => {
|
|
298
|
+
expect(() => {
|
|
299
|
+
new TTLCache<string>({ cleanupIntervalMs: Number.MAX_SAFE_INTEGER });
|
|
300
|
+
}).not.toThrow();
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
});
|