@hazeljs/cache 0.2.0-beta.53 → 0.2.0-beta.55
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/README.md +1 -1
- package/dist/cache.service.js +1 -1
- package/dist/cache.test.d.ts +2 -0
- package/dist/cache.test.d.ts.map +1 -0
- package/dist/cache.test.js +374 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -521,4 +521,4 @@ Apache 2.0 © [HazelJS](https://hazeljs.com)
|
|
|
521
521
|
- [Documentation](https://hazeljs.com/docs/packages/cache)
|
|
522
522
|
- [GitHub](https://github.com/hazel-js/hazeljs)
|
|
523
523
|
- [Issues](https://github.com/hazel-js/hazeljs/issues)
|
|
524
|
-
- [Discord](https://discord.
|
|
524
|
+
- [Discord](https://discord.com/channels/1448263814238965833/1448263814859456575)
|
package/dist/cache.service.js
CHANGED
|
@@ -193,7 +193,7 @@ let CacheService = class CacheService {
|
|
|
193
193
|
};
|
|
194
194
|
exports.CacheService = CacheService;
|
|
195
195
|
exports.CacheService = CacheService = __decorate([
|
|
196
|
-
(0, core_1.
|
|
196
|
+
(0, core_1.Service)(),
|
|
197
197
|
__metadata("design:paramtypes", [String, Object])
|
|
198
198
|
], CacheService);
|
|
199
199
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.test.d.ts","sourceRoot":"","sources":["../src/cache.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const memory_strategy_1 = require("./strategies/memory.strategy");
|
|
13
|
+
const redis_strategy_1 = require("./strategies/redis.strategy");
|
|
14
|
+
const multi_tier_strategy_1 = require("./strategies/multi-tier.strategy");
|
|
15
|
+
const cache_service_1 = require("./cache.service");
|
|
16
|
+
const cache_decorator_1 = require("./decorators/cache.decorator");
|
|
17
|
+
describe('MemoryCacheStore', () => {
|
|
18
|
+
let store;
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
store = new memory_strategy_1.MemoryCacheStore(100); // Short cleanup interval for testing
|
|
21
|
+
});
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
store.destroy();
|
|
24
|
+
});
|
|
25
|
+
describe('get and set', () => {
|
|
26
|
+
it('should set and get a value', async () => {
|
|
27
|
+
await store.set('key1', 'value1', 60);
|
|
28
|
+
const value = await store.get('key1');
|
|
29
|
+
expect(value).toBe('value1');
|
|
30
|
+
});
|
|
31
|
+
it('should return null for non-existent key', async () => {
|
|
32
|
+
const value = await store.get('nonexistent');
|
|
33
|
+
expect(value).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
it('should handle complex objects', async () => {
|
|
36
|
+
const obj = { name: 'test', nested: { value: 123 } };
|
|
37
|
+
await store.set('obj', obj, 60);
|
|
38
|
+
const retrieved = await store.get('obj');
|
|
39
|
+
expect(retrieved).toEqual(obj);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('expiration', () => {
|
|
43
|
+
it('should expire entries after TTL', async () => {
|
|
44
|
+
await store.set('expire', 'value', 0.1); // 100ms
|
|
45
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
46
|
+
const value = await store.get('expire');
|
|
47
|
+
expect(value).toBeNull();
|
|
48
|
+
});
|
|
49
|
+
it('should not return expired entries', async () => {
|
|
50
|
+
await store.set('key', 'value', 0.05); // 50ms
|
|
51
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
52
|
+
const exists = await store.has('key');
|
|
53
|
+
expect(exists).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe('tags', () => {
|
|
57
|
+
it('should set value with tags', async () => {
|
|
58
|
+
await store.setWithTags('user1', { name: 'John' }, 60, ['users', 'profiles']);
|
|
59
|
+
const value = await store.get('user1');
|
|
60
|
+
expect(value).toEqual({ name: 'John' });
|
|
61
|
+
});
|
|
62
|
+
it('should delete entries by tag', async () => {
|
|
63
|
+
await store.setWithTags('user1', { name: 'John' }, 60, ['users']);
|
|
64
|
+
await store.setWithTags('user2', { name: 'Jane' }, 60, ['users']);
|
|
65
|
+
await store.setWithTags('post1', { title: 'Post' }, 60, ['posts']);
|
|
66
|
+
await store.deleteByTag('users');
|
|
67
|
+
expect(await store.get('user1')).toBeNull();
|
|
68
|
+
expect(await store.get('user2')).toBeNull();
|
|
69
|
+
expect(await store.get('post1')).toEqual({ title: 'Post' });
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe('operations', () => {
|
|
73
|
+
it('should delete a key', async () => {
|
|
74
|
+
await store.set('key', 'value', 60);
|
|
75
|
+
await store.delete('key');
|
|
76
|
+
const value = await store.get('key');
|
|
77
|
+
expect(value).toBeNull();
|
|
78
|
+
});
|
|
79
|
+
it('should check if key exists', async () => {
|
|
80
|
+
await store.set('key', 'value', 60);
|
|
81
|
+
expect(await store.has('key')).toBe(true);
|
|
82
|
+
expect(await store.has('nonexistent')).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
it('should clear all entries', async () => {
|
|
85
|
+
await store.set('key1', 'value1', 60);
|
|
86
|
+
await store.set('key2', 'value2', 60);
|
|
87
|
+
await store.clear();
|
|
88
|
+
expect(await store.get('key1')).toBeNull();
|
|
89
|
+
expect(await store.get('key2')).toBeNull();
|
|
90
|
+
});
|
|
91
|
+
it('should get keys by pattern', async () => {
|
|
92
|
+
await store.set('user:1', 'value1', 60);
|
|
93
|
+
await store.set('user:2', 'value2', 60);
|
|
94
|
+
await store.set('post:1', 'value3', 60);
|
|
95
|
+
const userKeys = await store.keys('user:*');
|
|
96
|
+
expect(userKeys).toHaveLength(2);
|
|
97
|
+
expect(userKeys).toContain('user:1');
|
|
98
|
+
expect(userKeys).toContain('user:2');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe('statistics', () => {
|
|
102
|
+
it('should track hits and misses', async () => {
|
|
103
|
+
await store.set('key', 'value', 60);
|
|
104
|
+
await store.get('key'); // hit
|
|
105
|
+
await store.get('nonexistent'); // miss
|
|
106
|
+
await store.get('key'); // hit
|
|
107
|
+
const stats = await store.getStats();
|
|
108
|
+
expect(stats.hits).toBe(2);
|
|
109
|
+
expect(stats.misses).toBe(1);
|
|
110
|
+
expect(stats.hitRate).toBeGreaterThan(0);
|
|
111
|
+
});
|
|
112
|
+
it('should report cache size', async () => {
|
|
113
|
+
await store.set('key1', 'value1', 60);
|
|
114
|
+
await store.set('key2', 'value2', 60);
|
|
115
|
+
const stats = await store.getStats();
|
|
116
|
+
expect(stats.size).toBe(2);
|
|
117
|
+
});
|
|
118
|
+
it('should reset statistics', async () => {
|
|
119
|
+
await store.set('key', 'value', 60);
|
|
120
|
+
await store.get('key');
|
|
121
|
+
store.resetStats();
|
|
122
|
+
const stats = await store.getStats();
|
|
123
|
+
expect(stats.hits).toBe(0);
|
|
124
|
+
expect(stats.misses).toBe(0);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
describe('RedisCacheStore', () => {
|
|
129
|
+
let store;
|
|
130
|
+
beforeEach(() => {
|
|
131
|
+
store = new redis_strategy_1.RedisCacheStore();
|
|
132
|
+
});
|
|
133
|
+
afterEach(async () => {
|
|
134
|
+
await store.disconnect();
|
|
135
|
+
});
|
|
136
|
+
it('should set and get a value', async () => {
|
|
137
|
+
await store.set('key1', 'value1', 60);
|
|
138
|
+
const value = await store.get('key1');
|
|
139
|
+
expect(value).toBe('value1');
|
|
140
|
+
});
|
|
141
|
+
it('should handle objects', async () => {
|
|
142
|
+
const obj = { name: 'test', value: 123 };
|
|
143
|
+
await store.set('obj', obj, 60);
|
|
144
|
+
const retrieved = await store.get('obj');
|
|
145
|
+
expect(retrieved).toEqual(obj);
|
|
146
|
+
});
|
|
147
|
+
it('should delete by tag', async () => {
|
|
148
|
+
await store.setWithTags('key1', 'value1', 60, ['tag1']);
|
|
149
|
+
await store.setWithTags('key2', 'value2', 60, ['tag1']);
|
|
150
|
+
await store.deleteByTag('tag1');
|
|
151
|
+
expect(await store.get('key1')).toBeNull();
|
|
152
|
+
expect(await store.get('key2')).toBeNull();
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
describe('MultiTierCacheStore', () => {
|
|
156
|
+
let store;
|
|
157
|
+
beforeEach(() => {
|
|
158
|
+
store = new multi_tier_strategy_1.MultiTierCacheStore();
|
|
159
|
+
});
|
|
160
|
+
afterEach(async () => {
|
|
161
|
+
await store.destroy();
|
|
162
|
+
});
|
|
163
|
+
it('should set and get from multi-tier cache', async () => {
|
|
164
|
+
await store.set('key', 'value', 60);
|
|
165
|
+
const value = await store.get('key');
|
|
166
|
+
expect(value).toBe('value');
|
|
167
|
+
});
|
|
168
|
+
it('should promote from L2 to L1', async () => {
|
|
169
|
+
await store.set('key', 'value', 60);
|
|
170
|
+
// Clear L1 to simulate L2-only scenario
|
|
171
|
+
const l1Stats = await store.getL1Stats();
|
|
172
|
+
// Get should promote to L1
|
|
173
|
+
await store.get('key');
|
|
174
|
+
const newL1Stats = await store.getL1Stats();
|
|
175
|
+
expect(newL1Stats.size).toBeGreaterThanOrEqual(l1Stats.size);
|
|
176
|
+
});
|
|
177
|
+
it('should delete from both tiers', async () => {
|
|
178
|
+
await store.set('key', 'value', 60);
|
|
179
|
+
await store.delete('key');
|
|
180
|
+
expect(await store.get('key')).toBeNull();
|
|
181
|
+
});
|
|
182
|
+
it('should get combined statistics', async () => {
|
|
183
|
+
await store.set('key1', 'value1', 60);
|
|
184
|
+
await store.set('key2', 'value2', 60);
|
|
185
|
+
await store.get('key1');
|
|
186
|
+
await store.get('nonexistent');
|
|
187
|
+
const stats = await store.getStats();
|
|
188
|
+
expect(stats.hits).toBeGreaterThan(0);
|
|
189
|
+
expect(stats.misses).toBeGreaterThan(0);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
describe('CacheService', () => {
|
|
193
|
+
let service;
|
|
194
|
+
beforeEach(() => {
|
|
195
|
+
service = new cache_service_1.CacheService('memory');
|
|
196
|
+
});
|
|
197
|
+
describe('basic operations', () => {
|
|
198
|
+
it('should get and set values', async () => {
|
|
199
|
+
await service.set('key', 'value', 60);
|
|
200
|
+
const value = await service.get('key');
|
|
201
|
+
expect(value).toBe('value');
|
|
202
|
+
});
|
|
203
|
+
it('should delete values', async () => {
|
|
204
|
+
await service.set('key', 'value', 60);
|
|
205
|
+
await service.delete('key');
|
|
206
|
+
expect(await service.get('key')).toBeNull();
|
|
207
|
+
});
|
|
208
|
+
it('should clear all cache', async () => {
|
|
209
|
+
await service.set('key1', 'value1', 60);
|
|
210
|
+
await service.set('key2', 'value2', 60);
|
|
211
|
+
await service.clear();
|
|
212
|
+
expect(await service.get('key1')).toBeNull();
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
describe('cache-aside pattern', () => {
|
|
216
|
+
it('should get or set value', async () => {
|
|
217
|
+
let fetchCount = 0;
|
|
218
|
+
const fetcher = async () => {
|
|
219
|
+
fetchCount++;
|
|
220
|
+
return { data: 'fetched' };
|
|
221
|
+
};
|
|
222
|
+
const result1 = await service.getOrSet('key', fetcher, 60);
|
|
223
|
+
const result2 = await service.getOrSet('key', fetcher, 60);
|
|
224
|
+
expect(result1).toEqual({ data: 'fetched' });
|
|
225
|
+
expect(result2).toEqual({ data: 'fetched' });
|
|
226
|
+
expect(fetchCount).toBe(1); // Should only fetch once
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
describe('cache warming', () => {
|
|
230
|
+
it('should warm up cache', async () => {
|
|
231
|
+
await service.warmUp({
|
|
232
|
+
keys: ['key1', 'key2', 'key3'],
|
|
233
|
+
fetcher: async (key) => ({ key, data: `data-${key}` }),
|
|
234
|
+
ttl: 60,
|
|
235
|
+
});
|
|
236
|
+
expect(await service.get('key1')).toEqual({ key: 'key1', data: 'data-key1' });
|
|
237
|
+
expect(await service.get('key2')).toEqual({ key: 'key2', data: 'data-key2' });
|
|
238
|
+
expect(await service.get('key3')).toEqual({ key: 'key3', data: 'data-key3' });
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
describe('tags', () => {
|
|
242
|
+
it('should invalidate by tags', async () => {
|
|
243
|
+
await service.setWithTags('user1', { name: 'John' }, 60, ['users']);
|
|
244
|
+
await service.setWithTags('user2', { name: 'Jane' }, 60, ['users']);
|
|
245
|
+
await service.invalidateTags(['users']);
|
|
246
|
+
expect(await service.get('user1')).toBeNull();
|
|
247
|
+
expect(await service.get('user2')).toBeNull();
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
describe('statistics', () => {
|
|
251
|
+
it('should get cache statistics', async () => {
|
|
252
|
+
await service.set('key', 'value', 60);
|
|
253
|
+
await service.get('key');
|
|
254
|
+
const stats = await service.getStats();
|
|
255
|
+
expect(stats).toBeDefined();
|
|
256
|
+
expect(stats.hits).toBeGreaterThanOrEqual(0);
|
|
257
|
+
expect(stats.misses).toBeGreaterThanOrEqual(0);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
describe('CacheManager', () => {
|
|
262
|
+
let manager;
|
|
263
|
+
beforeEach(() => {
|
|
264
|
+
manager = new cache_service_1.CacheManager();
|
|
265
|
+
});
|
|
266
|
+
it('should register and get cache', () => {
|
|
267
|
+
const cache = new cache_service_1.CacheService('memory');
|
|
268
|
+
manager.register('test', cache);
|
|
269
|
+
expect(manager.get('test')).toBe(cache);
|
|
270
|
+
});
|
|
271
|
+
it('should set default cache', () => {
|
|
272
|
+
const cache = new cache_service_1.CacheService('memory');
|
|
273
|
+
manager.register('default', cache, true);
|
|
274
|
+
expect(manager.get()).toBe(cache);
|
|
275
|
+
});
|
|
276
|
+
it('should get all caches', () => {
|
|
277
|
+
const cache1 = new cache_service_1.CacheService('memory');
|
|
278
|
+
const cache2 = new cache_service_1.CacheService('memory');
|
|
279
|
+
manager.register('cache1', cache1);
|
|
280
|
+
manager.register('cache2', cache2);
|
|
281
|
+
const all = manager.getAll();
|
|
282
|
+
expect(all.size).toBe(2);
|
|
283
|
+
});
|
|
284
|
+
it('should clear all caches', async () => {
|
|
285
|
+
const cache1 = new cache_service_1.CacheService('memory');
|
|
286
|
+
const cache2 = new cache_service_1.CacheService('memory');
|
|
287
|
+
manager.register('cache1', cache1);
|
|
288
|
+
manager.register('cache2', cache2);
|
|
289
|
+
await cache1.set('key', 'value', 60);
|
|
290
|
+
await cache2.set('key', 'value', 60);
|
|
291
|
+
await manager.clearAll();
|
|
292
|
+
expect(await cache1.get('key')).toBeNull();
|
|
293
|
+
expect(await cache2.get('key')).toBeNull();
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
describe('Cache Decorators', () => {
|
|
297
|
+
describe('@Cache', () => {
|
|
298
|
+
it('should store cache metadata', () => {
|
|
299
|
+
class TestClass {
|
|
300
|
+
testMethod() {
|
|
301
|
+
return 'test';
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
__decorate([
|
|
305
|
+
(0, cache_decorator_1.Cache)({ ttl: 120, strategy: 'memory' }),
|
|
306
|
+
__metadata("design:type", Function),
|
|
307
|
+
__metadata("design:paramtypes", []),
|
|
308
|
+
__metadata("design:returntype", void 0)
|
|
309
|
+
], TestClass.prototype, "testMethod", null);
|
|
310
|
+
const instance = new TestClass();
|
|
311
|
+
const metadata = (0, cache_decorator_1.getCacheMetadata)(instance, 'testMethod');
|
|
312
|
+
expect(metadata).toBeDefined();
|
|
313
|
+
expect(metadata?.ttl).toBe(120);
|
|
314
|
+
expect(metadata?.strategy).toBe('memory');
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
describe('@CacheKey', () => {
|
|
318
|
+
it('should set cache key pattern', () => {
|
|
319
|
+
class TestClass {
|
|
320
|
+
testMethod() {
|
|
321
|
+
return 'test';
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
__decorate([
|
|
325
|
+
(0, cache_decorator_1.CacheKey)('user-{id}'),
|
|
326
|
+
(0, cache_decorator_1.Cache)(),
|
|
327
|
+
__metadata("design:type", Function),
|
|
328
|
+
__metadata("design:paramtypes", []),
|
|
329
|
+
__metadata("design:returntype", void 0)
|
|
330
|
+
], TestClass.prototype, "testMethod", null);
|
|
331
|
+
const instance = new TestClass();
|
|
332
|
+
const metadata = (0, cache_decorator_1.getCacheMetadata)(instance, 'testMethod');
|
|
333
|
+
expect(metadata?.key).toBe('user-{id}');
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
describe('@CacheTTL', () => {
|
|
337
|
+
it('should set cache TTL', () => {
|
|
338
|
+
class TestClass {
|
|
339
|
+
testMethod() {
|
|
340
|
+
return 'test';
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
__decorate([
|
|
344
|
+
(0, cache_decorator_1.CacheTTL)(300),
|
|
345
|
+
(0, cache_decorator_1.Cache)(),
|
|
346
|
+
__metadata("design:type", Function),
|
|
347
|
+
__metadata("design:paramtypes", []),
|
|
348
|
+
__metadata("design:returntype", void 0)
|
|
349
|
+
], TestClass.prototype, "testMethod", null);
|
|
350
|
+
const instance = new TestClass();
|
|
351
|
+
const metadata = (0, cache_decorator_1.getCacheMetadata)(instance, 'testMethod');
|
|
352
|
+
expect(metadata?.ttl).toBe(300);
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
describe('@CacheTags', () => {
|
|
356
|
+
it('should set cache tags', () => {
|
|
357
|
+
class TestClass {
|
|
358
|
+
testMethod() {
|
|
359
|
+
return 'test';
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
__decorate([
|
|
363
|
+
(0, cache_decorator_1.CacheTags)(['users', 'profiles']),
|
|
364
|
+
(0, cache_decorator_1.Cache)(),
|
|
365
|
+
__metadata("design:type", Function),
|
|
366
|
+
__metadata("design:paramtypes", []),
|
|
367
|
+
__metadata("design:returntype", void 0)
|
|
368
|
+
], TestClass.prototype, "testMethod", null);
|
|
369
|
+
const instance = new TestClass();
|
|
370
|
+
const metadata = (0, cache_decorator_1.getCacheMetadata)(instance, 'testMethod');
|
|
371
|
+
expect(metadata?.tags).toEqual(['users', 'profiles']);
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hazeljs/cache",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.55",
|
|
4
4
|
"description": "Caching module for HazelJS framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -50,5 +50,5 @@
|
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"@hazeljs/core": ">=0.2.0-beta.0"
|
|
52
52
|
},
|
|
53
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "f2e54f346eea552595a44607999454a9e388cb9e"
|
|
54
54
|
}
|