@umituz/react-native-storage 2.6.0 → 2.6.2

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.
Files changed (28) hide show
  1. package/package.json +8 -4
  2. package/src/cache/__tests__/PerformanceAndMemory.test.ts +386 -0
  3. package/src/cache/__tests__/setup.ts +19 -0
  4. package/src/cache/domain/Cache.ts +146 -0
  5. package/src/cache/domain/CacheManager.ts +48 -0
  6. package/src/cache/domain/CacheStatsTracker.ts +49 -0
  7. package/src/cache/domain/ErrorHandler.ts +42 -0
  8. package/src/cache/domain/PatternMatcher.ts +30 -0
  9. package/src/cache/domain/__tests__/Cache.test.ts +292 -0
  10. package/src/cache/domain/__tests__/CacheManager.test.ts +276 -0
  11. package/src/cache/domain/__tests__/ErrorHandler.test.ts +303 -0
  12. package/src/cache/domain/__tests__/PatternMatcher.test.ts +261 -0
  13. package/src/cache/domain/strategies/EvictionStrategy.ts +9 -0
  14. package/src/cache/domain/strategies/FIFOStrategy.ts +12 -0
  15. package/src/cache/domain/strategies/LFUStrategy.ts +22 -0
  16. package/src/cache/domain/strategies/LRUStrategy.ts +22 -0
  17. package/src/cache/domain/strategies/TTLStrategy.ts +23 -0
  18. package/src/cache/domain/strategies/__tests__/EvictionStrategies.test.ts +293 -0
  19. package/src/cache/domain/types/Cache.ts +28 -0
  20. package/src/cache/index.ts +28 -0
  21. package/src/cache/infrastructure/TTLCache.ts +103 -0
  22. package/src/cache/infrastructure/__tests__/TTLCache.test.ts +303 -0
  23. package/src/cache/presentation/__tests__/ReactHooks.test.ts +512 -0
  24. package/src/cache/presentation/useCache.ts +76 -0
  25. package/src/cache/presentation/useCachedValue.ts +88 -0
  26. package/src/cache/types.d.ts +3 -0
  27. package/src/index.ts +28 -0
  28. package/src/types/global.d.ts +2 -0
@@ -0,0 +1,303 @@
1
+ /**
2
+ * ErrorHandler Tests
3
+ */
4
+
5
+ import { ErrorHandler, CacheError } from '../ErrorHandler';
6
+
7
+ describe('ErrorHandler', () => {
8
+ describe('CacheError', () => {
9
+ test('should create CacheError with message and code', () => {
10
+ const error = new CacheError('Test message', 'TEST_CODE');
11
+
12
+ expect(error).toBeInstanceOf(Error);
13
+ expect(error).toBeInstanceOf(CacheError);
14
+ expect(error.message).toBe('Test message');
15
+ expect(error.code).toBe('TEST_CODE');
16
+ expect(error.name).toBe('CacheError');
17
+ });
18
+
19
+ test('should have stack trace', () => {
20
+ const error = new CacheError('Test message', 'TEST_CODE');
21
+
22
+ expect(error.stack).toBeDefined();
23
+ expect(typeof error.stack).toBe('string');
24
+ });
25
+
26
+ test('should be serializable', () => {
27
+ const error = new CacheError('Test message', 'TEST_CODE');
28
+ const serialized = JSON.stringify(error);
29
+ const parsed = JSON.parse(serialized);
30
+
31
+ expect(parsed.message).toBe('Test message');
32
+ expect(parsed.code).toBe('TEST_CODE');
33
+ expect(parsed.name).toBe('CacheError');
34
+ });
35
+ });
36
+
37
+ describe('handle', () => {
38
+ test('should throw CacheError as-is', () => {
39
+ const originalError = new CacheError('Original error', 'ORIGINAL');
40
+
41
+ expect(() => {
42
+ ErrorHandler.handle(originalError, 'test context');
43
+ }).toThrow('Original error');
44
+ });
45
+
46
+ test('should wrap regular Error in CacheError', () => {
47
+ const originalError = new Error('Regular error');
48
+
49
+ expect(() => {
50
+ ErrorHandler.handle(originalError, 'test context');
51
+ }).toThrow(CacheError);
52
+ });
53
+
54
+ test('should include context in wrapped error message', () => {
55
+ const originalError = new Error('Regular error');
56
+
57
+ try {
58
+ ErrorHandler.handle(originalError, 'test context');
59
+ } catch (error) {
60
+ expect((error as CacheError).message).toBe('test context: Regular error');
61
+ expect((error as CacheError).code).toBe('CACHE_ERROR');
62
+ }
63
+ });
64
+
65
+ test('should handle unknown error type', () => {
66
+ const unknownError = 'string error';
67
+
68
+ try {
69
+ ErrorHandler.handle(unknownError, 'test context');
70
+ } catch (error) {
71
+ expect((error as CacheError).message).toBe('test context: Unknown error');
72
+ expect((error as CacheError).code).toBe('UNKNOWN_ERROR');
73
+ }
74
+ });
75
+
76
+ test('should handle null error', () => {
77
+ try {
78
+ ErrorHandler.handle(null, 'test context');
79
+ } catch (error) {
80
+ expect((error as CacheError).message).toBe('test context: Unknown error');
81
+ expect((error as CacheError).code).toBe('UNKNOWN_ERROR');
82
+ }
83
+ });
84
+
85
+ test('should handle undefined error', () => {
86
+ try {
87
+ ErrorHandler.handle(undefined, 'test context');
88
+ } catch (error) {
89
+ expect((error as CacheError).message).toBe('test context: Unknown error');
90
+ expect((error as CacheError).code).toBe('UNKNOWN_ERROR');
91
+ }
92
+ });
93
+
94
+ test('should preserve original error properties when possible', () => {
95
+ const originalError = new Error('Original error');
96
+ (originalError as any).customProperty = 'custom value';
97
+
98
+ try {
99
+ ErrorHandler.handle(originalError, 'test context');
100
+ } catch (error) {
101
+ const cacheError = error as CacheError;
102
+ expect(cacheError.message).toBe('test context: Original error');
103
+ expect(cacheError.code).toBe('CACHE_ERROR');
104
+ // Note: original properties are not copied to maintain clean error structure
105
+ }
106
+ });
107
+ });
108
+
109
+ describe('withTimeout', () => {
110
+ beforeEach(() => {
111
+ jest.useFakeTimers();
112
+ });
113
+
114
+ afterEach(() => {
115
+ jest.useRealTimers();
116
+ });
117
+
118
+ test('should resolve promise before timeout', async () => {
119
+ const promise = Promise.resolve('success');
120
+ const result = await ErrorHandler.withTimeout(promise, 1000, 'test context');
121
+
122
+ expect(result).toBe('success');
123
+ });
124
+
125
+ test('should timeout when promise takes too long', async () => {
126
+ const promise = new Promise(resolve => setTimeout(() => resolve('late'), 2000));
127
+
128
+ await expect(
129
+ ErrorHandler.withTimeout(promise, 1000, 'test context')
130
+ ).rejects.toThrow(CacheError);
131
+ });
132
+
133
+ test('should include timeout in error message', async () => {
134
+ const promise = new Promise(resolve => setTimeout(() => resolve('late'), 2000));
135
+
136
+ try {
137
+ await ErrorHandler.withTimeout(promise, 1000, 'test context');
138
+ } catch (error) {
139
+ expect((error as CacheError).message).toBe('test context: Operation timed out after 1000ms');
140
+ expect((error as CacheError).code).toBe('TIMEOUT');
141
+ }
142
+ });
143
+
144
+ test('should handle promise rejection before timeout', async () => {
145
+ const promise = Promise.reject(new Error('Promise rejected'));
146
+
147
+ await expect(
148
+ ErrorHandler.withTimeout(promise, 1000, 'test context')
149
+ ).rejects.toThrow(CacheError);
150
+ });
151
+
152
+ test('should include context in promise rejection error', async () => {
153
+ const promise = Promise.reject(new Error('Promise rejected'));
154
+
155
+ try {
156
+ await ErrorHandler.withTimeout(promise, 1000, 'test context');
157
+ } catch (error) {
158
+ expect((error as CacheError).message).toBe('test context: Promise rejected');
159
+ expect((error as CacheError).code).toBe('CACHE_ERROR');
160
+ }
161
+ });
162
+
163
+ test('should handle zero timeout', async () => {
164
+ const promise = new Promise(resolve => setTimeout(() => resolve('success'), 100));
165
+
166
+ await expect(
167
+ ErrorHandler.withTimeout(promise, 0, 'test context')
168
+ ).rejects.toThrow(CacheError);
169
+ });
170
+
171
+ test('should handle negative timeout', async () => {
172
+ const promise = Promise.resolve('success');
173
+
174
+ // Negative timeout should trigger immediate timeout
175
+ await expect(
176
+ ErrorHandler.withTimeout(promise, -1000, 'test context')
177
+ ).rejects.toThrow(CacheError);
178
+ });
179
+
180
+ test('should cleanup timeout when promise resolves', async () => {
181
+ const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout');
182
+ const promise = Promise.resolve('success');
183
+
184
+ await ErrorHandler.withTimeout(promise, 1000, 'test context');
185
+
186
+ // Should clear timeout after promise resolves
187
+ expect(clearTimeoutSpy).toHaveBeenCalled();
188
+
189
+ clearTimeoutSpy.mockRestore();
190
+ });
191
+
192
+ test('should handle multiple concurrent timeouts', async () => {
193
+ const promise1 = Promise.resolve('success1');
194
+ const promise2 = Promise.resolve('success2');
195
+ const promise3 = new Promise(resolve => setTimeout(() => resolve('success3'), 2000));
196
+
197
+ const results = await Promise.allSettled([
198
+ ErrorHandler.withTimeout(promise1, 1000, 'context1'),
199
+ ErrorHandler.withTimeout(promise2, 1000, 'context2'),
200
+ ErrorHandler.withTimeout(promise3, 1000, 'context3'),
201
+ ]);
202
+
203
+ expect(results[0].status).toBe('fulfilled');
204
+ expect(results[1].status).toBe('fulfilled');
205
+ expect(results[2].status).toBe('rejected');
206
+ });
207
+ });
208
+
209
+ describe('Integration with Cache Operations', () => {
210
+ test('should handle cache operation errors', () => {
211
+ const mockOperation = () => {
212
+ throw new Error('Cache operation failed');
213
+ };
214
+
215
+ expect(() => {
216
+ ErrorHandler.handle(mockOperation(), 'cache.set');
217
+ }).toThrow('cache.set: Cache operation failed');
218
+ });
219
+
220
+ test('should handle async cache operation errors', async () => {
221
+ const mockAsyncOperation = async () => {
222
+ throw new Error('Async cache operation failed');
223
+ };
224
+
225
+ await expect(
226
+ ErrorHandler.withTimeout(mockAsyncOperation(), 1000, 'cache.get')
227
+ ).rejects.toThrow('cache.get: Async cache operation failed');
228
+ });
229
+
230
+ test('should handle network timeout scenarios', async () => {
231
+ const mockNetworkCall = new Promise(resolve =>
232
+ setTimeout(() => resolve({ data: 'response' }), 5000)
233
+ );
234
+
235
+ await expect(
236
+ ErrorHandler.withTimeout(mockNetworkCall, 1000, 'api.fetch')
237
+ ).rejects.toThrow('api.fetch: Operation timed out after 1000ms');
238
+ });
239
+ });
240
+
241
+ describe('Error Code Constants', () => {
242
+ test('should use consistent error codes', () => {
243
+ const scenarios = [
244
+ { error: new Error('test'), expectedCode: 'CACHE_ERROR' },
245
+ { error: 'string', expectedCode: 'UNKNOWN_ERROR' },
246
+ { error: null, expectedCode: 'UNKNOWN_ERROR' },
247
+ { error: undefined, expectedCode: 'UNKNOWN_ERROR' },
248
+ ];
249
+
250
+ scenarios.forEach(({ error, expectedCode }) => {
251
+ try {
252
+ ErrorHandler.handle(error, 'test');
253
+ } catch (caught) {
254
+ expect((caught as CacheError).code).toBe(expectedCode);
255
+ }
256
+ });
257
+ });
258
+
259
+ test('should use TIMEOUT code for timeout errors', async () => {
260
+ const promise = new Promise(resolve => setTimeout(() => resolve('late'), 2000));
261
+
262
+ try {
263
+ await ErrorHandler.withTimeout(promise, 1000, 'test');
264
+ } catch (error) {
265
+ expect((error as CacheError).code).toBe('TIMEOUT');
266
+ }
267
+ });
268
+ });
269
+
270
+ describe('Edge Cases', () => {
271
+ test('should handle empty context string', () => {
272
+ const error = new Error('test error');
273
+
274
+ try {
275
+ ErrorHandler.handle(error, '');
276
+ } catch (caught) {
277
+ expect((caught as CacheError).message).toBe(': test error');
278
+ }
279
+ });
280
+
281
+ test('should handle very long context string', () => {
282
+ const longContext = 'context'.repeat(1000);
283
+ const error = new Error('test error');
284
+
285
+ try {
286
+ ErrorHandler.handle(error, longContext);
287
+ } catch (caught) {
288
+ expect((caught as CacheError).message).toBe(`${longContext}: test error`);
289
+ }
290
+ });
291
+
292
+ test('should handle special characters in context', () => {
293
+ const context = 'context-with-special-chars-!@#$%^&*()';
294
+ const error = new Error('test error');
295
+
296
+ try {
297
+ ErrorHandler.handle(error, context);
298
+ } catch (caught) {
299
+ expect((caught as CacheError).message).toBe(`${context}: test error`);
300
+ }
301
+ });
302
+ });
303
+ });
@@ -0,0 +1,261 @@
1
+ /**
2
+ * PatternMatcher Tests
3
+ */
4
+
5
+ import { PatternMatcher } from '../PatternMatcher';
6
+
7
+ describe('PatternMatcher', () => {
8
+ beforeEach(() => {
9
+ PatternMatcher.clearCache();
10
+ });
11
+
12
+ describe('convertPatternToRegex', () => {
13
+ test('should handle simple patterns', () => {
14
+ const regex = PatternMatcher.convertPatternToRegex('user:*');
15
+ expect(regex.test('user:1')).toBe(true);
16
+ expect(regex.test('user:123')).toBe(true);
17
+ expect(regex.test('user:abc')).toBe(true);
18
+ expect(regex.test('admin:1')).toBe(false);
19
+ });
20
+
21
+ test('should handle multiple wildcards', () => {
22
+ const regex = PatternMatcher.convertPatternToRegex('*:*:*');
23
+ expect(regex.test('user:1:profile')).toBe(true);
24
+ expect(regex.test('post:2:comments')).toBe(true);
25
+ expect(regex.test('user:1')).toBe(false);
26
+ expect(regex.test('user')).toBe(false);
27
+ });
28
+
29
+ test('should handle exact matches', () => {
30
+ const regex = PatternMatcher.convertPatternToRegex('exact-key');
31
+ expect(regex.test('exact-key')).toBe(true);
32
+ expect(regex.test('exact-key-123')).toBe(false);
33
+ expect(regex.test('exact')).toBe(false);
34
+ });
35
+
36
+ test('should handle patterns with special characters', () => {
37
+ const regex = PatternMatcher.convertPatternToRegex('user.*profile');
38
+ expect(regex.test('user.123.profile')).toBe(true);
39
+ expect(regex.test('user.abc.profile')).toBe(true);
40
+ expect(regex.test('user.profile')).toBe(false);
41
+ });
42
+
43
+ test('should escape regex special characters', () => {
44
+ const regex = PatternMatcher.convertPatternToRegex('user.+?^${}()|[]\\');
45
+ expect(regex.test('user.+?^${}()|[]\\')).toBe(true);
46
+ expect(regex.test('user-something')).toBe(false);
47
+ });
48
+
49
+ test('should handle empty pattern', () => {
50
+ const regex = PatternMatcher.convertPatternToRegex('');
51
+ expect(regex.test('')).toBe(true);
52
+ expect(regex.test('anything')).toBe(false);
53
+ });
54
+
55
+ test('should handle pattern with only wildcards', () => {
56
+ const regex = PatternMatcher.convertPatternToRegex('*');
57
+ expect(regex.test('anything')).toBe(true);
58
+ expect(regex.test('')).toBe(true);
59
+ });
60
+
61
+ test('should handle complex patterns', () => {
62
+ const regex = PatternMatcher.convertPatternToRegex('cache:*:data:*');
63
+ expect(regex.test('cache:user:data:123')).toBe(true);
64
+ expect(regex.test('cache:post:data:456')).toBe(true);
65
+ expect(regex.test('cache:user:meta:123')).toBe(false);
66
+ expect(regex.test('user:data:123')).toBe(false);
67
+ });
68
+
69
+ test('should handle patterns with dots and dashes', () => {
70
+ const regex = PatternMatcher.convertPatternToRegex('module.*-service.*');
71
+ expect(regex.test('module.auth-service.v1')).toBe(true);
72
+ expect(regex.test('module.user-service.v2')).toBe(true);
73
+ expect(regex.test('module.auth')).toBe(false);
74
+ });
75
+ });
76
+
77
+ describe('matchesPattern', () => {
78
+ test('should return true for matching patterns', () => {
79
+ expect(PatternMatcher.matchesPattern('user:1', 'user:*')).toBe(true);
80
+ expect(PatternMatcher.matchesPattern('post:123:comments', '*:*:*')).toBe(true);
81
+ expect(PatternMatcher.matchesPattern('exact', 'exact')).toBe(true);
82
+ });
83
+
84
+ test('should return false for non-matching patterns', () => {
85
+ expect(PatternMatcher.matchesPattern('admin:1', 'user:*')).toBe(false);
86
+ expect(PatternMatcher.matchesPattern('user:1', '*:*:*')).toBe(false);
87
+ expect(PatternMatcher.matchesPattern('exact', 'different')).toBe(false);
88
+ });
89
+
90
+ test('should handle case-sensitive matching', () => {
91
+ expect(PatternMatcher.matchesPattern('User:1', 'user:*')).toBe(false);
92
+ expect(PatternMatcher.matchesPattern('user:1', 'User:*')).toBe(false);
93
+ expect(PatternMatcher.matchesPattern('USER:1', 'USER:*')).toBe(true);
94
+ });
95
+
96
+ test('should handle empty strings', () => {
97
+ expect(PatternMatcher.matchesPattern('', '')).toBe(true);
98
+ expect(PatternMatcher.matchesPattern('', '*')).toBe(true);
99
+ expect(PatternMatcher.matchesPattern('test', '')).toBe(false);
100
+ });
101
+ });
102
+
103
+ describe('Regex Caching', () => {
104
+ test('should cache converted regex patterns', () => {
105
+ const pattern = 'user:*';
106
+
107
+ const regex1 = PatternMatcher.convertPatternToRegex(pattern);
108
+ const regex2 = PatternMatcher.convertPatternToRegex(pattern);
109
+
110
+ expect(regex1).toBe(regex2); // Same reference
111
+ });
112
+
113
+ test('should create different regex for different patterns', () => {
114
+ const regex1 = PatternMatcher.convertPatternToRegex('user:*');
115
+ const regex2 = PatternMatcher.convertPatternToRegex('post:*');
116
+
117
+ expect(regex1).not.toBe(regex2); // Different references
118
+ expect(regex1.test('user:1')).toBe(true);
119
+ expect(regex1.test('post:1')).toBe(false);
120
+ expect(regex2.test('post:1')).toBe(true);
121
+ expect(regex2.test('user:1')).toBe(false);
122
+ });
123
+
124
+ test('should clear cache', () => {
125
+ const pattern = 'user:*';
126
+
127
+ const regex1 = PatternMatcher.convertPatternToRegex(pattern);
128
+ PatternMatcher.clearCache();
129
+ const regex2 = PatternMatcher.convertPatternToRegex(pattern);
130
+
131
+ expect(regex1).not.toBe(regex2); // Different references after clear
132
+ });
133
+
134
+ test('should maintain cache performance', () => {
135
+ const pattern = 'very:complex:pattern:*:with:many:parts:*';
136
+
137
+ // First call - should create new regex
138
+ const start1 = performance.now();
139
+ const regex1 = PatternMatcher.convertPatternToRegex(pattern);
140
+ const end1 = performance.now();
141
+
142
+ // Second call - should use cached regex
143
+ const start2 = performance.now();
144
+ const regex2 = PatternMatcher.convertPatternToRegex(pattern);
145
+ const end2 = performance.now();
146
+
147
+ expect(regex1).toBe(regex2);
148
+ // Second call should be faster (though this might not always be true in tests)
149
+ expect(end2 - start2).toBeLessThanOrEqual(end1 - start1);
150
+ });
151
+ });
152
+
153
+ describe('Edge Cases', () => {
154
+ test('should handle very long patterns', () => {
155
+ const longPattern = 'a'.repeat(1000) + '*';
156
+ const longKey = 'a'.repeat(1000) + 'suffix';
157
+
158
+ expect(PatternMatcher.matchesPattern(longKey, longPattern)).toBe(true);
159
+ expect(PatternMatcher.matchesPattern('a'.repeat(999), longPattern)).toBe(false);
160
+ });
161
+
162
+ test('should handle patterns with only special characters', () => {
163
+ const regex = PatternMatcher.convertPatternToRegex('.+?^${}()|[]\\');
164
+ expect(regex.test('.+?^${}()|[]\\')).toBe(true);
165
+ });
166
+
167
+ test('should handle Unicode characters', () => {
168
+ expect(PatternMatcher.matchesPattern('üser:1', 'üser:*')).toBe(true);
169
+ expect(PatternMatcher.matchesPattern('用户:1', '用户:*')).toBe(true);
170
+ expect(PatternMatcher.matchesPattern('🚀:launch', '🚀:*')).toBe(true);
171
+ });
172
+
173
+ test('should handle null and undefined inputs gracefully', () => {
174
+ expect(() => {
175
+ PatternMatcher.convertPatternToRegex(null as any);
176
+ }).toThrow();
177
+
178
+ expect(() => {
179
+ PatternMatcher.convertPatternToRegex(undefined as any);
180
+ }).toThrow();
181
+ });
182
+
183
+ test('should handle non-string inputs', () => {
184
+ expect(() => {
185
+ PatternMatcher.convertPatternToRegex(123 as any);
186
+ }).toThrow();
187
+
188
+ expect(() => {
189
+ PatternMatcher.convertPatternToRegex({} as any);
190
+ }).toThrow();
191
+ });
192
+ });
193
+
194
+ describe('Performance Considerations', () => {
195
+ test('should handle large number of pattern matches efficiently', () => {
196
+ const pattern = 'cache:*:data:*';
197
+ const keys = Array.from({ length: 1000 }, (_, i) => `cache:${i}:data:${i * 2}`);
198
+
199
+ const start = performance.now();
200
+
201
+ keys.forEach(key => {
202
+ PatternMatcher.matchesPattern(key, pattern);
203
+ });
204
+
205
+ const end = performance.now();
206
+
207
+ // Should complete within reasonable time (adjust threshold as needed)
208
+ expect(end - start).toBeLessThan(100); // 100ms
209
+ });
210
+
211
+ test('should reuse cached regex for many matches', () => {
212
+ const pattern = 'user:*';
213
+ const keys = Array.from({ length: 1000 }, (_, i) => `user:${i}`);
214
+
215
+ // Pre-cache the regex
216
+ PatternMatcher.convertPatternToRegex(pattern);
217
+
218
+ const start = performance.now();
219
+
220
+ keys.forEach(key => {
221
+ PatternMatcher.matchesPattern(key, pattern);
222
+ });
223
+
224
+ const end = performance.now();
225
+
226
+ // Should be faster with cached regex
227
+ expect(end - start).toBeLessThan(50); // 50ms
228
+ });
229
+ });
230
+
231
+ describe('Real-world Scenarios', () => {
232
+ test('should handle common cache key patterns', () => {
233
+ const scenarios = [
234
+ { key: 'user:123:profile', pattern: 'user:*:profile', expected: true },
235
+ { key: 'user:123:settings', pattern: 'user:*:profile', expected: false },
236
+ { key: 'post:456:comments:789', pattern: 'post:*:comments:*', expected: true },
237
+ { key: 'post:456:likes', pattern: 'post:*:comments:*', expected: false },
238
+ { key: 'session:abc123', pattern: 'session:*', expected: true },
239
+ { key: 'cache:api:user:123', pattern: 'cache:api:*', expected: true },
240
+ { key: 'cache:db:user:123', pattern: 'cache:api:*', expected: false },
241
+ ];
242
+
243
+ scenarios.forEach(({ key, pattern, expected }) => {
244
+ expect(PatternMatcher.matchesPattern(key, pattern)).toBe(expected);
245
+ });
246
+ });
247
+
248
+ test('should handle API endpoint patterns', () => {
249
+ const apiPatterns = [
250
+ { endpoint: '/api/v1/users/123', pattern: '/api/v1/users/*', expected: true },
251
+ { endpoint: '/api/v1/posts/456/comments', pattern: '/api/v1/posts/*/comments', expected: true },
252
+ { endpoint: '/api/v2/users/123', pattern: '/api/v1/users/*', expected: false },
253
+ { endpoint: '/api/v1/users', pattern: '/api/v1/users/*', expected: false },
254
+ ];
255
+
256
+ apiPatterns.forEach(({ endpoint, pattern, expected }) => {
257
+ expect(PatternMatcher.matchesPattern(endpoint, pattern)).toBe(expected);
258
+ });
259
+ });
260
+ });
261
+ });
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Eviction Strategy Interface
3
+ */
4
+
5
+ import type { CacheEntry } from '../types/Cache';
6
+
7
+ export interface EvictionStrategy<T> {
8
+ findKeyToEvict(entries: Map<string, CacheEntry<T>>): string | undefined;
9
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * FIFO (First In First Out) Eviction Strategy
3
+ */
4
+
5
+ import type { EvictionStrategy } from './EvictionStrategy';
6
+ import type { CacheEntry } from '../types/Cache';
7
+
8
+ export class FIFOStrategy<T> implements EvictionStrategy<T> {
9
+ findKeyToEvict(entries: Map<string, CacheEntry<T>>): string | undefined {
10
+ return entries.keys().next().value;
11
+ }
12
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * LFU (Least Frequently Used) Eviction Strategy
3
+ */
4
+
5
+ import type { EvictionStrategy } from './EvictionStrategy';
6
+ import type { CacheEntry } from '../types/Cache';
7
+
8
+ export class LFUStrategy<T> implements EvictionStrategy<T> {
9
+ findKeyToEvict(entries: Map<string, CacheEntry<T>>): string | undefined {
10
+ let least: string | undefined;
11
+ let leastCount = Infinity;
12
+
13
+ for (const [key, entry] of entries.entries()) {
14
+ if (entry.accessCount < leastCount) {
15
+ leastCount = entry.accessCount;
16
+ least = key;
17
+ }
18
+ }
19
+
20
+ return least;
21
+ }
22
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * LRU (Least Recently Used) Eviction Strategy
3
+ */
4
+
5
+ import type { EvictionStrategy } from './EvictionStrategy';
6
+ import type { CacheEntry } from '../types/Cache';
7
+
8
+ export class LRUStrategy<T> implements EvictionStrategy<T> {
9
+ findKeyToEvict(entries: Map<string, CacheEntry<T>>): string | undefined {
10
+ let oldest: string | undefined;
11
+ let oldestTime = Infinity;
12
+
13
+ for (const [key, entry] of entries.entries()) {
14
+ if (entry.lastAccess < oldestTime) {
15
+ oldestTime = entry.lastAccess;
16
+ oldest = key;
17
+ }
18
+ }
19
+
20
+ return oldest;
21
+ }
22
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * TTL (Time To Live) Eviction Strategy
3
+ */
4
+
5
+ import type { EvictionStrategy } from './EvictionStrategy';
6
+ import type { CacheEntry } from '../types/Cache';
7
+
8
+ export class TTLStrategy<T> implements EvictionStrategy<T> {
9
+ findKeyToEvict(entries: Map<string, CacheEntry<T>>): string | undefined {
10
+ let nearest: string | undefined;
11
+ let nearestExpiry = Infinity;
12
+
13
+ for (const [key, entry] of entries.entries()) {
14
+ const expiry = entry.timestamp + entry.ttl;
15
+ if (expiry < nearestExpiry) {
16
+ nearestExpiry = expiry;
17
+ nearest = key;
18
+ }
19
+ }
20
+
21
+ return nearest;
22
+ }
23
+ }