@hazeljs/cache 0.2.0-alpha.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/LICENSE +192 -0
- package/README.md +524 -0
- package/dist/cache.module.d.ts +81 -0
- package/dist/cache.module.d.ts.map +1 -0
- package/dist/cache.module.js +111 -0
- package/dist/cache.service.d.ts +109 -0
- package/dist/cache.service.d.ts.map +1 -0
- package/dist/cache.service.js +263 -0
- 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/dist/cache.types.d.ts +180 -0
- package/dist/cache.types.d.ts.map +1 -0
- package/dist/cache.types.js +2 -0
- package/dist/decorators/cache.decorator.d.ts +93 -0
- package/dist/decorators/cache.decorator.d.ts.map +1 -0
- package/dist/decorators/cache.decorator.js +155 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/strategies/memory.strategy.d.ts +73 -0
- package/dist/strategies/memory.strategy.d.ts.map +1 -0
- package/dist/strategies/memory.strategy.js +236 -0
- package/dist/strategies/multi-tier.strategy.d.ts +69 -0
- package/dist/strategies/multi-tier.strategy.d.ts.map +1 -0
- package/dist/strategies/multi-tier.strategy.js +154 -0
- package/dist/strategies/redis.strategy.d.ts +61 -0
- package/dist/strategies/redis.strategy.d.ts.map +1 -0
- package/dist/strategies/redis.strategy.js +185 -0
- package/package.json +54 -0
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache strategy types
|
|
3
|
+
*/
|
|
4
|
+
export type CacheStrategy = 'memory' | 'redis' | 'multi-tier';
|
|
5
|
+
/**
|
|
6
|
+
* TTL strategy types
|
|
7
|
+
*/
|
|
8
|
+
export type TTLStrategy = 'absolute' | 'sliding';
|
|
9
|
+
/**
|
|
10
|
+
* Cache options
|
|
11
|
+
*/
|
|
12
|
+
export interface CacheOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Cache strategy to use
|
|
15
|
+
* @default 'memory'
|
|
16
|
+
*/
|
|
17
|
+
strategy?: CacheStrategy;
|
|
18
|
+
/**
|
|
19
|
+
* Time to live in seconds
|
|
20
|
+
* @default 3600
|
|
21
|
+
*/
|
|
22
|
+
ttl?: number;
|
|
23
|
+
/**
|
|
24
|
+
* TTL strategy
|
|
25
|
+
* @default 'absolute'
|
|
26
|
+
*/
|
|
27
|
+
ttlStrategy?: TTLStrategy;
|
|
28
|
+
/**
|
|
29
|
+
* Cache key pattern (supports placeholders like {id}, {userId})
|
|
30
|
+
*/
|
|
31
|
+
key?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Tags for group invalidation
|
|
34
|
+
*/
|
|
35
|
+
tags?: string[];
|
|
36
|
+
/**
|
|
37
|
+
* Events that should invalidate this cache
|
|
38
|
+
*/
|
|
39
|
+
invalidateOn?: string[];
|
|
40
|
+
/**
|
|
41
|
+
* Whether to cache null/undefined values
|
|
42
|
+
* @default false
|
|
43
|
+
*/
|
|
44
|
+
cacheNull?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Custom condition function to determine if value should be cached
|
|
47
|
+
*/
|
|
48
|
+
condition?: (value: unknown) => boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Cache entry metadata
|
|
52
|
+
*/
|
|
53
|
+
export interface CacheEntry<T = unknown> {
|
|
54
|
+
/**
|
|
55
|
+
* Cached value
|
|
56
|
+
*/
|
|
57
|
+
value: T;
|
|
58
|
+
/**
|
|
59
|
+
* Timestamp when cached
|
|
60
|
+
*/
|
|
61
|
+
cachedAt: number;
|
|
62
|
+
/**
|
|
63
|
+
* Expiration timestamp
|
|
64
|
+
*/
|
|
65
|
+
expiresAt: number;
|
|
66
|
+
/**
|
|
67
|
+
* Last accessed timestamp (for sliding TTL)
|
|
68
|
+
*/
|
|
69
|
+
lastAccessedAt?: number;
|
|
70
|
+
/**
|
|
71
|
+
* Cache tags
|
|
72
|
+
*/
|
|
73
|
+
tags?: string[];
|
|
74
|
+
/**
|
|
75
|
+
* Cache key
|
|
76
|
+
*/
|
|
77
|
+
key: string;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Cache statistics
|
|
81
|
+
*/
|
|
82
|
+
export interface CacheStats {
|
|
83
|
+
/**
|
|
84
|
+
* Total number of cache hits
|
|
85
|
+
*/
|
|
86
|
+
hits: number;
|
|
87
|
+
/**
|
|
88
|
+
* Total number of cache misses
|
|
89
|
+
*/
|
|
90
|
+
misses: number;
|
|
91
|
+
/**
|
|
92
|
+
* Hit rate percentage
|
|
93
|
+
*/
|
|
94
|
+
hitRate: number;
|
|
95
|
+
/**
|
|
96
|
+
* Total number of cached entries
|
|
97
|
+
*/
|
|
98
|
+
size: number;
|
|
99
|
+
/**
|
|
100
|
+
* Total memory used (in bytes, if applicable)
|
|
101
|
+
*/
|
|
102
|
+
memoryUsage?: number;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Cache store interface
|
|
106
|
+
*/
|
|
107
|
+
export interface ICacheStore {
|
|
108
|
+
/**
|
|
109
|
+
* Get a value from cache
|
|
110
|
+
*/
|
|
111
|
+
get<T>(key: string): Promise<T | null>;
|
|
112
|
+
/**
|
|
113
|
+
* Set a value in cache
|
|
114
|
+
*/
|
|
115
|
+
set<T>(key: string, value: T, ttl?: number): Promise<void>;
|
|
116
|
+
/**
|
|
117
|
+
* Delete a value from cache
|
|
118
|
+
*/
|
|
119
|
+
delete(key: string): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* Check if key exists in cache
|
|
122
|
+
*/
|
|
123
|
+
has(key: string): Promise<boolean>;
|
|
124
|
+
/**
|
|
125
|
+
* Clear all cache entries
|
|
126
|
+
*/
|
|
127
|
+
clear(): Promise<void>;
|
|
128
|
+
/**
|
|
129
|
+
* Get all keys matching a pattern
|
|
130
|
+
*/
|
|
131
|
+
keys(pattern?: string): Promise<string[]>;
|
|
132
|
+
/**
|
|
133
|
+
* Get cache statistics
|
|
134
|
+
*/
|
|
135
|
+
getStats(): Promise<CacheStats>;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Cache warming options
|
|
139
|
+
*/
|
|
140
|
+
export interface CacheWarmingOptions {
|
|
141
|
+
/**
|
|
142
|
+
* Keys to warm up
|
|
143
|
+
*/
|
|
144
|
+
keys: string[];
|
|
145
|
+
/**
|
|
146
|
+
* Function to fetch data for warming
|
|
147
|
+
*/
|
|
148
|
+
fetcher: (key: string) => Promise<unknown>;
|
|
149
|
+
/**
|
|
150
|
+
* TTL for warmed entries
|
|
151
|
+
*/
|
|
152
|
+
ttl?: number;
|
|
153
|
+
/**
|
|
154
|
+
* Whether to warm in parallel
|
|
155
|
+
* @default true
|
|
156
|
+
*/
|
|
157
|
+
parallel?: boolean;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Cache invalidation event
|
|
161
|
+
*/
|
|
162
|
+
export interface CacheInvalidationEvent {
|
|
163
|
+
/**
|
|
164
|
+
* Event name
|
|
165
|
+
*/
|
|
166
|
+
event: string;
|
|
167
|
+
/**
|
|
168
|
+
* Keys to invalidate
|
|
169
|
+
*/
|
|
170
|
+
keys?: string[];
|
|
171
|
+
/**
|
|
172
|
+
* Tags to invalidate
|
|
173
|
+
*/
|
|
174
|
+
tags?: string[];
|
|
175
|
+
/**
|
|
176
|
+
* Timestamp
|
|
177
|
+
*/
|
|
178
|
+
timestamp: number;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=cache.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.types.d.ts","sourceRoot":"","sources":["../src/cache.types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { CacheOptions } from '../cache.types';
|
|
3
|
+
/**
|
|
4
|
+
* Cache decorator for methods
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* @Cache({
|
|
9
|
+
* strategy: 'memory',
|
|
10
|
+
* ttl: 3600,
|
|
11
|
+
* key: 'user-{id}',
|
|
12
|
+
* tags: ['users']
|
|
13
|
+
* })
|
|
14
|
+
* @Get('/users/:id')
|
|
15
|
+
* async getUser(@Param('id') id: string) {
|
|
16
|
+
* return this.userService.findById(id);
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function Cache(options?: CacheOptions): MethodDecorator;
|
|
21
|
+
/**
|
|
22
|
+
* Get cache metadata from a method
|
|
23
|
+
*/
|
|
24
|
+
export declare function getCacheMetadata(target: object, propertyKey: string | symbol): CacheOptions | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Check if a method has cache metadata
|
|
27
|
+
*/
|
|
28
|
+
export declare function hasCacheMetadata(target: object, propertyKey: string | symbol): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* CacheKey decorator to specify custom cache key generation
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* @CacheKey('user-{id}-{role}')
|
|
35
|
+
* @Get('/users/:id')
|
|
36
|
+
* async getUser(@Param('id') id: string, @Query('role') role: string) {
|
|
37
|
+
* return this.userService.findById(id);
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare function CacheKey(keyPattern: string): MethodDecorator;
|
|
42
|
+
/**
|
|
43
|
+
* CacheTTL decorator to specify cache TTL
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* @CacheTTL(7200) // 2 hours
|
|
48
|
+
* @Get('/users')
|
|
49
|
+
* async getUsers() {
|
|
50
|
+
* return this.userService.findAll();
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function CacheTTL(ttl: number): MethodDecorator;
|
|
55
|
+
/**
|
|
56
|
+
* CacheTags decorator to specify cache tags
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* @CacheTags(['users', 'profiles'])
|
|
61
|
+
* @Get('/users/:id/profile')
|
|
62
|
+
* async getUserProfile(@Param('id') id: string) {
|
|
63
|
+
* return this.userService.getProfile(id);
|
|
64
|
+
* }
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function CacheTags(tags: string[]): MethodDecorator;
|
|
68
|
+
/**
|
|
69
|
+
* CacheEvict decorator to evict cache entries
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* @CacheEvict({ tags: ['users'] })
|
|
74
|
+
* @Post('/users')
|
|
75
|
+
* async createUser(@Body() user: CreateUserDto) {
|
|
76
|
+
* return this.userService.create(user);
|
|
77
|
+
* }
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export declare function CacheEvict(options: {
|
|
81
|
+
keys?: string[];
|
|
82
|
+
tags?: string[];
|
|
83
|
+
all?: boolean;
|
|
84
|
+
}): MethodDecorator;
|
|
85
|
+
/**
|
|
86
|
+
* Get cache evict metadata
|
|
87
|
+
*/
|
|
88
|
+
export declare function getCacheEvictMetadata(target: object, propertyKey: string | symbol): {
|
|
89
|
+
keys?: string[];
|
|
90
|
+
tags?: string[];
|
|
91
|
+
all?: boolean;
|
|
92
|
+
} | undefined;
|
|
93
|
+
//# sourceMappingURL=cache.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/cache.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAK9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,eAAe,CAoBjE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,MAAM,GAC3B,YAAY,GAAG,SAAS,CAE1B;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAEtF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,CAY5D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAYrD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,eAAe,CAYzD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE;IAClC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf,GAAG,eAAe,CAMlB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,MAAM,GAC3B;IAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAEjE"}
|