@jintianxiayu/cache-decorator 0.1.2 → 0.1.3
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 +13 -15
- package/jest.config.js +10 -13
- package/package.json +2 -2
- package/src/core/cache-provider-registry.ts +39 -39
- package/src/core/cache-provider.ts +28 -28
- package/src/core/key-builder.ts +25 -25
- package/src/core/native-cache.ts +33 -33
- package/src/core/pending-cache.ts +17 -17
- package/src/core/redis-cache.ts +40 -46
- package/src/decorators/cache-evict.ts +34 -38
- package/src/decorators/cache.ts +67 -71
- package/src/index.ts +0 -1
- package/test/cache-evict.test.ts +165 -165
- package/test/cache-provider-registry.test.ts +24 -24
- package/test/cache.test.ts +232 -232
- package/test/key-builder.test.ts +28 -28
- package/test/native-cache.test.ts +50 -50
- package/test/pending-cache.test.ts +44 -42
- package/tsconfig.json +7 -7
package/src/decorators/cache.ts
CHANGED
|
@@ -15,37 +15,37 @@ export type CacheKeyResolver = null | string | ((...args: unknown[]) => string);
|
|
|
15
15
|
* @Cache 装饰器配置项
|
|
16
16
|
*/
|
|
17
17
|
export interface CacheOptions {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
18
|
+
/**
|
|
19
|
+
* 过期时间(秒)
|
|
20
|
+
*/
|
|
21
|
+
ttl?: number;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 指定 CacheProvider 名称
|
|
25
|
+
*/
|
|
26
|
+
providerName?: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 自定义缓存 key 生成逻辑
|
|
30
|
+
* - undefined/null: 使用默认逻辑 KeyBuilder.build(cacheName, args)
|
|
31
|
+
* - string: 使用 KeyBuilder.build(cacheName, [key])
|
|
32
|
+
* - function: 调用函数后使用 KeyBuilder.build(cacheName, [result])
|
|
33
|
+
*/
|
|
34
|
+
key?: CacheKeyResolver;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* 成功缓存条目
|
|
39
39
|
*/
|
|
40
40
|
interface SuccessCacheEntry<T> {
|
|
41
|
-
|
|
41
|
+
value: T;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* 错误缓存条目
|
|
46
46
|
*/
|
|
47
47
|
interface ErrorCacheEntry {
|
|
48
|
-
|
|
48
|
+
error: unknown;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
@@ -63,17 +63,17 @@ const pendingCache = new PendingCache();
|
|
|
63
63
|
* @returns 解析后的缓存 key
|
|
64
64
|
*/
|
|
65
65
|
function resolveCacheKey(cacheName: string, keyResolver: CacheKeyResolver | undefined, args: unknown[]): string {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
if (keyResolver === undefined || keyResolver === null) {
|
|
67
|
+
return KeyBuilder.build(cacheName, args);
|
|
68
|
+
}
|
|
69
|
+
if (typeof keyResolver === 'string') {
|
|
70
|
+
return KeyBuilder.build(cacheName, [keyResolver]);
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
return KeyBuilder.build(cacheName, [keyResolver(...args)]);
|
|
74
|
+
} catch {
|
|
75
|
+
return KeyBuilder.build(cacheName, args);
|
|
76
|
+
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
@@ -83,47 +83,43 @@ function resolveCacheKey(cacheName: string, keyResolver: CacheKeyResolver | unde
|
|
|
83
83
|
* @param options 配置项
|
|
84
84
|
*/
|
|
85
85
|
export function Cache(cacheName: string, options?: CacheOptions) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
pendingCache.set(cacheKey, promise);
|
|
124
|
-
return promise;
|
|
86
|
+
return function <T>(_target: object, _propertyKey: string, descriptor: PropertyDescriptor) {
|
|
87
|
+
const originalMethod = descriptor.value;
|
|
88
|
+
|
|
89
|
+
descriptor.value = async function (...args: unknown[]) {
|
|
90
|
+
const cacheKey = resolveCacheKey(cacheName, options?.key, args);
|
|
91
|
+
const providerName = options?.providerName;
|
|
92
|
+
const provider = CacheProviderRegistry.get(providerName);
|
|
93
|
+
|
|
94
|
+
const cached = await provider.get(cacheKey);
|
|
95
|
+
if (cached !== undefined) {
|
|
96
|
+
const entry = cached as CacheEntry<T>;
|
|
97
|
+
if ('error' in entry) {
|
|
98
|
+
throw entry.error;
|
|
99
|
+
}
|
|
100
|
+
return entry.value;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const pending = pendingCache.get<T>(cacheKey);
|
|
104
|
+
if (pending) {
|
|
105
|
+
return pending;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const promise = (async () => {
|
|
109
|
+
try {
|
|
110
|
+
const result = await originalMethod.apply(this, args);
|
|
111
|
+
provider.set(cacheKey, { value: result } as CacheEntry<T>, options?.ttl);
|
|
112
|
+
return result;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
provider.set(cacheKey, { error } as CacheEntry<T>, options?.ttl);
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
})();
|
|
118
|
+
|
|
119
|
+
pendingCache.set(cacheKey, promise);
|
|
120
|
+
return promise;
|
|
121
|
+
};
|
|
125
122
|
};
|
|
126
|
-
};
|
|
127
123
|
}
|
|
128
124
|
|
|
129
|
-
export { CacheProviderRegistry } from '../core/cache-provider-registry';
|
|
125
|
+
export { CacheProviderRegistry } from '../core/cache-provider-registry';
|
package/src/index.ts
CHANGED
package/test/cache-evict.test.ts
CHANGED
|
@@ -4,175 +4,175 @@ import { CacheProviderRegistry } from '../src/core/cache-provider-registry';
|
|
|
4
4
|
import { MemoryCacheProvider } from '../src/core/native-cache';
|
|
5
5
|
|
|
6
6
|
describe('@CacheEvict 装饰器', () => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
memoryProvider.clear();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
describe('按入参清除', () => {
|
|
20
|
-
it('应清除对应缓存条目', async () => {
|
|
21
|
-
let getUserCallCount = 0;
|
|
22
|
-
let updateUserCallCount = 0;
|
|
23
|
-
|
|
24
|
-
class UserService {
|
|
25
|
-
@Cache('user-cache')
|
|
26
|
-
async getUser(id: number) {
|
|
27
|
-
getUserCallCount++;
|
|
28
|
-
return { id };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
@CacheEvict('user-cache')
|
|
32
|
-
async updateUser(id: number) {
|
|
33
|
-
updateUserCallCount++;
|
|
34
|
-
return { id, updated: true };
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const service = new UserService();
|
|
39
|
-
await service.getUser(1);
|
|
40
|
-
await service.getUser(1);
|
|
41
|
-
expect(getUserCallCount).toBe(1);
|
|
42
|
-
|
|
43
|
-
await service.updateUser(1);
|
|
44
|
-
await service.getUser(1);
|
|
45
|
-
expect(getUserCallCount).toBe(2);
|
|
46
|
-
expect(updateUserCallCount).toBe(1);
|
|
7
|
+
let memoryProvider: MemoryCacheProvider;
|
|
8
|
+
|
|
9
|
+
beforeAll(() => {
|
|
10
|
+
memoryProvider = new MemoryCacheProvider();
|
|
11
|
+
CacheProviderRegistry.register('memory', memoryProvider);
|
|
12
|
+
CacheProviderRegistry.setDefault('memory');
|
|
47
13
|
});
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
it('应清除该缓存名称下所有条目', async () => {
|
|
52
|
-
let getUserCallCount = 0;
|
|
53
|
-
let getOrderCallCount = 0;
|
|
54
|
-
let clearCallCount = 0;
|
|
55
|
-
|
|
56
|
-
class Service {
|
|
57
|
-
@Cache('user-cache')
|
|
58
|
-
async getUser(id: number) {
|
|
59
|
-
getUserCallCount++;
|
|
60
|
-
return { id };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
@Cache('order-cache')
|
|
64
|
-
async getOrder(id: number) {
|
|
65
|
-
getOrderCallCount++;
|
|
66
|
-
return { id };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
@CacheEvict('user-cache', { allEntries: true })
|
|
70
|
-
async clearAllUsers() {
|
|
71
|
-
clearCallCount++;
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const service = new Service();
|
|
77
|
-
await service.getUser(1);
|
|
78
|
-
await service.getUser(2);
|
|
79
|
-
await service.getOrder(1);
|
|
80
|
-
expect(getUserCallCount).toBe(2);
|
|
81
|
-
expect(getOrderCallCount).toBe(1);
|
|
82
|
-
|
|
83
|
-
await service.clearAllUsers();
|
|
84
|
-
expect(clearCallCount).toBe(1);
|
|
85
|
-
|
|
86
|
-
await service.getUser(1);
|
|
87
|
-
await service.getUser(2);
|
|
88
|
-
await service.getOrder(1);
|
|
89
|
-
expect(getUserCallCount).toBe(4);
|
|
90
|
-
expect(getOrderCallCount).toBe(1);
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
memoryProvider.clear();
|
|
91
17
|
});
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
18
|
+
|
|
19
|
+
describe('按入参清除', () => {
|
|
20
|
+
it('应清除对应缓存条目', async () => {
|
|
21
|
+
let getUserCallCount = 0;
|
|
22
|
+
let updateUserCallCount = 0;
|
|
23
|
+
|
|
24
|
+
class UserService {
|
|
25
|
+
@Cache('user-cache')
|
|
26
|
+
async getUser(id: number) {
|
|
27
|
+
getUserCallCount++;
|
|
28
|
+
return { id };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@CacheEvict('user-cache')
|
|
32
|
+
async updateUser(id: number) {
|
|
33
|
+
updateUserCallCount++;
|
|
34
|
+
return { id, updated: true };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const service = new UserService();
|
|
39
|
+
await service.getUser(1);
|
|
40
|
+
await service.getUser(1);
|
|
41
|
+
expect(getUserCallCount).toBe(1);
|
|
42
|
+
|
|
43
|
+
await service.updateUser(1);
|
|
44
|
+
await service.getUser(1);
|
|
45
|
+
expect(getUserCallCount).toBe(2);
|
|
46
|
+
expect(updateUserCallCount).toBe(1);
|
|
47
|
+
});
|
|
119
48
|
});
|
|
120
49
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
50
|
+
describe('清除所有条目', () => {
|
|
51
|
+
it('应清除该缓存名称下所有条目', async () => {
|
|
52
|
+
let getUserCallCount = 0;
|
|
53
|
+
let getOrderCallCount = 0;
|
|
54
|
+
let clearCallCount = 0;
|
|
55
|
+
|
|
56
|
+
class Service {
|
|
57
|
+
@Cache('user-cache')
|
|
58
|
+
async getUser(id: number) {
|
|
59
|
+
getUserCallCount++;
|
|
60
|
+
return { id };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Cache('order-cache')
|
|
64
|
+
async getOrder(id: number) {
|
|
65
|
+
getOrderCallCount++;
|
|
66
|
+
return { id };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@CacheEvict('user-cache', { allEntries: true })
|
|
70
|
+
async clearAllUsers() {
|
|
71
|
+
clearCallCount++;
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const service = new Service();
|
|
77
|
+
await service.getUser(1);
|
|
78
|
+
await service.getUser(2);
|
|
79
|
+
await service.getOrder(1);
|
|
80
|
+
expect(getUserCallCount).toBe(2);
|
|
81
|
+
expect(getOrderCallCount).toBe(1);
|
|
82
|
+
|
|
83
|
+
await service.clearAllUsers();
|
|
84
|
+
expect(clearCallCount).toBe(1);
|
|
85
|
+
|
|
86
|
+
await service.getUser(1);
|
|
87
|
+
await service.getUser(2);
|
|
88
|
+
await service.getOrder(1);
|
|
89
|
+
expect(getUserCallCount).toBe(4);
|
|
90
|
+
expect(getOrderCallCount).toBe(1);
|
|
91
|
+
});
|
|
146
92
|
});
|
|
147
93
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
94
|
+
describe('key 选项', () => {
|
|
95
|
+
it('key 为字符串时应删除对应缓存条目', async () => {
|
|
96
|
+
let getUserCallCount = 0;
|
|
97
|
+
|
|
98
|
+
class UserService {
|
|
99
|
+
@Cache('user-cache', { key: 'all-users' })
|
|
100
|
+
async getUsers() {
|
|
101
|
+
getUserCallCount++;
|
|
102
|
+
return [{ id: 1 }, { id: 2 }];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@CacheEvict('user-cache', { key: 'all-users' })
|
|
106
|
+
async clearUsers() {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const service = new UserService();
|
|
112
|
+
await service.getUsers();
|
|
113
|
+
await service.getUsers();
|
|
114
|
+
expect(getUserCallCount).toBe(1);
|
|
115
|
+
|
|
116
|
+
await service.clearUsers();
|
|
117
|
+
await service.getUsers();
|
|
118
|
+
expect(getUserCallCount).toBe(2);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('key 为函数时应使用函数返回值删除缓存条目', async () => {
|
|
122
|
+
let getUserCallCount = 0;
|
|
123
|
+
|
|
124
|
+
class UserService {
|
|
125
|
+
@Cache('user-cache', { key: (...args: unknown[]) => String(args[0]) })
|
|
126
|
+
async getUser(id: number) {
|
|
127
|
+
getUserCallCount++;
|
|
128
|
+
return { id };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@CacheEvict('user-cache', { key: (...args: unknown[]) => String(args[0]) })
|
|
132
|
+
async deleteUser(id: number) {
|
|
133
|
+
return { id, deleted: true };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const service = new UserService();
|
|
138
|
+
await service.getUser(1);
|
|
139
|
+
await service.getUser(2);
|
|
140
|
+
expect(getUserCallCount).toBe(2);
|
|
141
|
+
|
|
142
|
+
await service.deleteUser(1);
|
|
143
|
+
await service.getUser(1);
|
|
144
|
+
await service.getUser(2);
|
|
145
|
+
expect(getUserCallCount).toBe(3);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('allEntries 为 true 时忽略 key 选项', async () => {
|
|
149
|
+
let getUserCallCount = 0;
|
|
150
|
+
let clearCallCount = 0;
|
|
151
|
+
|
|
152
|
+
class UserService {
|
|
153
|
+
@Cache('user-cache', { key: 'specific-key' })
|
|
154
|
+
async getUsers() {
|
|
155
|
+
getUserCallCount++;
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@CacheEvict('user-cache', { allEntries: true, key: 'ignored-key' })
|
|
160
|
+
async clearAllUsers() {
|
|
161
|
+
clearCallCount++;
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const service = new UserService();
|
|
167
|
+
await service.getUsers();
|
|
168
|
+
await service.getUsers();
|
|
169
|
+
expect(getUserCallCount).toBe(1);
|
|
170
|
+
|
|
171
|
+
await service.clearAllUsers();
|
|
172
|
+
expect(clearCallCount).toBe(1);
|
|
173
|
+
|
|
174
|
+
await service.getUsers();
|
|
175
|
+
expect(getUserCallCount).toBe(2);
|
|
176
|
+
});
|
|
176
177
|
});
|
|
177
|
-
|
|
178
|
-
});
|
|
178
|
+
});
|
|
@@ -2,35 +2,35 @@ import { CacheProviderRegistry } from '../src/core/cache-provider-registry';
|
|
|
2
2
|
import { MemoryCacheProvider } from '../src/core/native-cache';
|
|
3
3
|
|
|
4
4
|
describe('CacheProviderRegistry', () => {
|
|
5
|
-
|
|
5
|
+
let memoryProvider: MemoryCacheProvider;
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
CacheProviderRegistry.clear();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
describe('register & get', () => {
|
|
17
|
-
it('应注册并获取 Provider', () => {
|
|
18
|
-
expect(CacheProviderRegistry.get('memory')).toBe(memoryProvider);
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
memoryProvider = new MemoryCacheProvider();
|
|
9
|
+
CacheProviderRegistry.register('memory', memoryProvider);
|
|
19
10
|
});
|
|
20
11
|
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
CacheProviderRegistry.clear();
|
|
23
14
|
});
|
|
24
|
-
});
|
|
25
15
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
16
|
+
describe('register & get', () => {
|
|
17
|
+
it('应注册并获取 Provider', () => {
|
|
18
|
+
expect(CacheProviderRegistry.get('memory')).toBe(memoryProvider);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('应在 Provider 不存在时抛出错误', () => {
|
|
22
|
+
expect(() => CacheProviderRegistry.get('nonexistent')).toThrow();
|
|
23
|
+
});
|
|
30
24
|
});
|
|
31
25
|
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
describe('setDefault & get', () => {
|
|
27
|
+
it('应设置并获取默认 Provider', () => {
|
|
28
|
+
CacheProviderRegistry.setDefault('memory');
|
|
29
|
+
expect(CacheProviderRegistry.get()).toBe(memoryProvider);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('应在未设置默认且未指定时抛出错误', () => {
|
|
33
|
+
expect(() => CacheProviderRegistry.get()).toThrow();
|
|
34
|
+
});
|
|
34
35
|
});
|
|
35
|
-
|
|
36
|
-
});
|
|
36
|
+
});
|