@jintianxiayu/cache-decorator 0.1.2 → 1.0.0
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/CHANGELOG.md +18 -0
- package/README.md +270 -133
- package/dist/adapters/ioredis-cache-client.d.ts +9 -0
- package/dist/adapters/ioredis-cache-client.d.ts.map +1 -0
- package/dist/adapters/ioredis-cache-client.js +73 -0
- package/dist/adapters/ioredis-cache-client.js.map +1 -0
- package/dist/adapters/node-redis-cache-client.d.ts +10 -0
- package/dist/adapters/node-redis-cache-client.d.ts.map +1 -0
- package/dist/adapters/node-redis-cache-client.js +81 -0
- package/dist/adapters/node-redis-cache-client.js.map +1 -0
- package/dist/adapters/redis-key-prefix.d.ts +22 -0
- package/dist/adapters/redis-key-prefix.d.ts.map +1 -0
- package/dist/adapters/redis-key-prefix.js +41 -0
- package/dist/adapters/redis-key-prefix.js.map +1 -0
- package/dist/core/cache-logger.d.ts +28 -0
- package/dist/core/cache-logger.d.ts.map +1 -0
- package/dist/core/cache-logger.js +70 -0
- package/dist/core/cache-logger.js.map +1 -0
- package/dist/core/cache-provider-registry.d.ts.map +1 -1
- package/dist/core/cache-provider-registry.js.map +1 -1
- package/dist/core/cache-provider.d.ts.map +1 -1
- package/dist/core/key-builder.d.ts.map +1 -1
- package/dist/core/key-builder.js +3 -2
- package/dist/core/key-builder.js.map +1 -1
- package/dist/core/native-cache.d.ts.map +1 -1
- package/dist/core/native-cache.js +2 -1
- package/dist/core/native-cache.js.map +1 -1
- package/dist/core/pending-cache.d.ts.map +1 -1
- package/dist/core/pending-cache.js +6 -3
- package/dist/core/pending-cache.js.map +1 -1
- package/dist/core/redis-cache-client.d.ts +156 -0
- package/dist/core/redis-cache-client.d.ts.map +1 -0
- package/dist/core/redis-cache-client.js +3 -0
- package/dist/core/redis-cache-client.js.map +1 -0
- package/dist/core/redis-cache.d.ts +39 -7
- package/dist/core/redis-cache.d.ts.map +1 -1
- package/dist/core/redis-cache.js +63 -22
- package/dist/core/redis-cache.js.map +1 -1
- package/dist/decorators/cache-evict.d.ts +1 -1
- package/dist/decorators/cache-evict.d.ts.map +1 -1
- package/dist/decorators/cache-evict.js +68 -14
- package/dist/decorators/cache-evict.js.map +1 -1
- package/dist/decorators/cache.d.ts +1 -1
- package/dist/decorators/cache.d.ts.map +1 -1
- package/dist/decorators/cache.js +85 -23
- package/dist/decorators/cache.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/jest.config.js +11 -14
- package/package.json +26 -29
- package/src/adapters/ioredis-cache-client.ts +89 -0
- package/src/adapters/node-redis-cache-client.ts +95 -0
- package/src/adapters/redis-key-prefix.ts +38 -0
- package/src/core/cache-logger.ts +105 -0
- package/src/core/cache-provider-registry.ts +39 -39
- package/src/core/cache-provider.ts +28 -28
- package/src/core/key-builder.ts +33 -31
- package/src/core/native-cache.ts +55 -53
- package/src/core/pending-cache.ts +29 -26
- package/src/core/redis-cache-client.ts +160 -0
- package/src/core/redis-cache.ts +104 -62
- package/src/decorators/cache-evict.ts +129 -73
- package/src/decorators/cache.ts +203 -129
- package/src/index.ts +11 -9
- package/test/cache-evict-logging.test.ts +362 -0
- package/test/cache-evict.test.ts +165 -165
- package/test/cache-logger.integration.test.ts +129 -0
- package/test/cache-logger.test.ts +153 -0
- package/test/cache-logging.test.ts +544 -0
- package/test/cache-provider-registry.test.ts +24 -24
- package/test/cache.test.ts +255 -256
- package/test/fixtures/cache-logger-child.mjs +108 -0
- package/test/helpers/legacy-redis-cache.ts +22 -0
- package/test/helpers/package-consumer.ts +231 -0
- package/test/helpers/redis-fixture.ts +142 -0
- package/test/ioredis-cache-client.test.ts +143 -0
- package/test/key-builder.test.ts +28 -28
- package/test/native-cache.test.ts +77 -71
- package/test/node-redis-cache-client.test.ts +149 -0
- package/test/pending-cache.test.ts +69 -56
- package/test/redis-cache-client-lifecycle.test.ts +112 -0
- package/test/redis-cache-client-types.test.ts +184 -0
- package/test/redis-cache-client.integration.test.ts +327 -0
- package/test/redis-cache-decorator.test.ts +201 -0
- package/test/redis-cache-package.integration.test.ts +328 -0
- package/test/redis-cache-provider.test.ts +269 -0
- package/test/type-contract/contract.ts +64 -0
- package/test/type-contract/tsconfig.json +12 -0
- package/tsconfig.json +8 -8
package/test/cache.test.ts
CHANGED
|
@@ -1,256 +1,255 @@
|
|
|
1
|
-
import { Cache } from '../src/decorators/cache';
|
|
2
|
-
import { CacheProviderRegistry } from '../src/core/cache-provider-registry';
|
|
3
|
-
import { MemoryCacheProvider } from '../src/core/native-cache';
|
|
4
|
-
|
|
5
|
-
describe('@Cache 装饰器', () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
});
|
|
1
|
+
import { Cache } from '../src/decorators/cache';
|
|
2
|
+
import { CacheProviderRegistry } from '../src/core/cache-provider-registry';
|
|
3
|
+
import { MemoryCacheProvider } from '../src/core/native-cache';
|
|
4
|
+
|
|
5
|
+
describe('@Cache 装饰器', () => {
|
|
6
|
+
beforeAll(() => {
|
|
7
|
+
CacheProviderRegistry.register('memory', new MemoryCacheProvider());
|
|
8
|
+
CacheProviderRegistry.setDefault('memory');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
const provider = CacheProviderRegistry.get();
|
|
13
|
+
provider.clear();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
jest.useRealTimers();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('基础缓存', () => {
|
|
21
|
+
it('应缓存方法结果', async () => {
|
|
22
|
+
class UserService {
|
|
23
|
+
callCount = 0;
|
|
24
|
+
|
|
25
|
+
@Cache('user-cache')
|
|
26
|
+
async getUser(id: number) {
|
|
27
|
+
this.callCount++;
|
|
28
|
+
return { id, name: 'test' };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const service = new UserService();
|
|
33
|
+
const result1 = await service.getUser(1);
|
|
34
|
+
const result2 = await service.getUser(1);
|
|
35
|
+
|
|
36
|
+
expect(result1).toEqual({ id: 1, name: 'test' });
|
|
37
|
+
expect(result2).toEqual({ id: 1, name: 'test' });
|
|
38
|
+
expect(service.callCount).toBe(1);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('不同参数应分别缓存', async () => {
|
|
42
|
+
class UserService {
|
|
43
|
+
callCount = 0;
|
|
44
|
+
|
|
45
|
+
@Cache('user-cache')
|
|
46
|
+
async getUser(id: number) {
|
|
47
|
+
this.callCount++;
|
|
48
|
+
return { id };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const service = new UserService();
|
|
53
|
+
await service.getUser(1);
|
|
54
|
+
await service.getUser(2);
|
|
55
|
+
|
|
56
|
+
expect(service.callCount).toBe(2);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('TTL 过期', () => {
|
|
61
|
+
it('应在 TTL 后失效', async () => {
|
|
62
|
+
jest.useFakeTimers();
|
|
63
|
+
|
|
64
|
+
class UserService {
|
|
65
|
+
callCount = 0;
|
|
66
|
+
|
|
67
|
+
@Cache('user-cache', { ttl: 60 })
|
|
68
|
+
async getUser(id: number) {
|
|
69
|
+
this.callCount++;
|
|
70
|
+
return { id };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const service = new UserService();
|
|
75
|
+
const result1 = await service.getUser(1);
|
|
76
|
+
expect(result1).toEqual({ id: 1 });
|
|
77
|
+
|
|
78
|
+
jest.advanceTimersByTime(60001);
|
|
79
|
+
|
|
80
|
+
const result2 = await service.getUser(1);
|
|
81
|
+
expect(result2).toEqual({ id: 1 });
|
|
82
|
+
expect(service.callCount).toBe(2);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('缓存命中', () => {
|
|
87
|
+
it('应直接返回缓存值', async () => {
|
|
88
|
+
class UserService {
|
|
89
|
+
callCount = 0;
|
|
90
|
+
|
|
91
|
+
@Cache('user-cache')
|
|
92
|
+
async getUser(id: number) {
|
|
93
|
+
this.callCount++;
|
|
94
|
+
return { id, timestamp: Date.now() };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const service = new UserService();
|
|
99
|
+
const result1 = await service.getUser(1);
|
|
100
|
+
const result2 = await service.getUser(1);
|
|
101
|
+
|
|
102
|
+
expect(result1.id).toBe(1);
|
|
103
|
+
expect(result2.id).toBe(1);
|
|
104
|
+
expect(result1.timestamp).toBe(result2.timestamp);
|
|
105
|
+
expect(service.callCount).toBe(1);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('错误结果缓存', () => {
|
|
110
|
+
it('应缓存错误结果并重新抛出原始异常', async () => {
|
|
111
|
+
const expectedError = new Error('user not found');
|
|
112
|
+
|
|
113
|
+
class UserService {
|
|
114
|
+
callCount = 0;
|
|
115
|
+
|
|
116
|
+
@Cache('user-cache')
|
|
117
|
+
async getUser(_id: number) {
|
|
118
|
+
this.callCount++;
|
|
119
|
+
throw expectedError;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const service = new UserService();
|
|
124
|
+
|
|
125
|
+
await expect(service.getUser(1)).rejects.toBe(expectedError);
|
|
126
|
+
await expect(service.getUser(1)).rejects.toBe(expectedError);
|
|
127
|
+
expect(service.callCount).toBe(1);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('请求合并', () => {
|
|
132
|
+
it('并发请求应返回同一 Promise', async () => {
|
|
133
|
+
let resolvePromise: ((value: number) => void) | undefined;
|
|
134
|
+
let callCount = 0;
|
|
135
|
+
const resultPromise = new Promise<number>((resolve) => {
|
|
136
|
+
resolvePromise = resolve;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
class UserService {
|
|
140
|
+
@Cache('user-cache')
|
|
141
|
+
async getUser(_id: number): Promise<number> {
|
|
142
|
+
callCount++;
|
|
143
|
+
return resultPromise;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const service = new UserService();
|
|
148
|
+
const promise1 = service.getUser(1);
|
|
149
|
+
const promise2 = service.getUser(1);
|
|
150
|
+
|
|
151
|
+
await Promise.resolve();
|
|
152
|
+
resolvePromise!(42);
|
|
153
|
+
|
|
154
|
+
const [result1, result2] = await Promise.all([promise1, promise2]);
|
|
155
|
+
expect(callCount).toBe(1);
|
|
156
|
+
expect(promise1).toBe(promise2);
|
|
157
|
+
expect(result1).toBe(42);
|
|
158
|
+
expect(result2).toBe(42);
|
|
159
|
+
expect(callCount).toBe(1);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('key 选项', () => {
|
|
164
|
+
it('key 为 undefined 时使用自动生成逻辑', async () => {
|
|
165
|
+
class UserService {
|
|
166
|
+
callCount = 0;
|
|
167
|
+
|
|
168
|
+
@Cache('user-cache', { key: undefined })
|
|
169
|
+
async getUser(id: number) {
|
|
170
|
+
this.callCount++;
|
|
171
|
+
return { id };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const service = new UserService();
|
|
176
|
+
await service.getUser(1);
|
|
177
|
+
await service.getUser(1);
|
|
178
|
+
expect(service.callCount).toBe(1);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('key 为 null 时使用自动生成逻辑', async () => {
|
|
182
|
+
class UserService {
|
|
183
|
+
callCount = 0;
|
|
184
|
+
|
|
185
|
+
@Cache('user-cache', { key: null })
|
|
186
|
+
async getUser(id: number) {
|
|
187
|
+
this.callCount++;
|
|
188
|
+
return { id };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const service = new UserService();
|
|
193
|
+
await service.getUser(1);
|
|
194
|
+
await service.getUser(1);
|
|
195
|
+
expect(service.callCount).toBe(1);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('key 为字符串时使用字符串值', async () => {
|
|
199
|
+
class UserService {
|
|
200
|
+
callCount = 0;
|
|
201
|
+
|
|
202
|
+
@Cache('user-cache', { key: 'specific-key' })
|
|
203
|
+
async getUser(id: number) {
|
|
204
|
+
this.callCount++;
|
|
205
|
+
return { id };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const service = new UserService();
|
|
210
|
+
await service.getUser(1);
|
|
211
|
+
await service.getUser(2);
|
|
212
|
+
expect(service.callCount).toBe(1);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('key 为函数时使用函数返回值', async () => {
|
|
216
|
+
class UserService {
|
|
217
|
+
callCount = 0;
|
|
218
|
+
|
|
219
|
+
@Cache('user-cache', { key: (...args: unknown[]) => String(args[0]) })
|
|
220
|
+
async getUser(id: number) {
|
|
221
|
+
this.callCount++;
|
|
222
|
+
return { id };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const service = new UserService();
|
|
227
|
+
await service.getUser(1);
|
|
228
|
+
await service.getUser(1);
|
|
229
|
+
expect(service.callCount).toBe(1);
|
|
230
|
+
await service.getUser(2);
|
|
231
|
+
expect(service.callCount).toBe(2);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('key 函数抛出异常时降级到自动生成', async () => {
|
|
235
|
+
class UserService {
|
|
236
|
+
callCount = 0;
|
|
237
|
+
|
|
238
|
+
@Cache('user-cache', {
|
|
239
|
+
key: () => {
|
|
240
|
+
throw new Error('bad key');
|
|
241
|
+
},
|
|
242
|
+
})
|
|
243
|
+
async getUser(id: number) {
|
|
244
|
+
this.callCount++;
|
|
245
|
+
return { id };
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const service = new UserService();
|
|
250
|
+
await service.getUser(1);
|
|
251
|
+
await service.getUser(1);
|
|
252
|
+
expect(service.callCount).toBe(1);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import cachePackage from '../../dist/index.js';
|
|
7
|
+
import loggerPackage from '../../../logger/dist/index.js';
|
|
8
|
+
|
|
9
|
+
const { Cache, CacheProviderRegistry, MemoryCacheProvider } = cachePackage;
|
|
10
|
+
const { LoggerContext, LoggerFactory } = loggerPackage;
|
|
11
|
+
const CACHE_LOGGER_NAME = '@jintianxiayu/cache-decorator';
|
|
12
|
+
const profileLevel = process.argv[2];
|
|
13
|
+
const fixtureDirectory = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const fixtureRequire = createRequire(import.meta.url);
|
|
15
|
+
if (profileLevel !== 'debug' && profileLevel !== 'error') {
|
|
16
|
+
throw new Error('Expected cache Logger profile level: debug or error');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 将 legacy method decorator 应用到 JavaScript fixture 的指定方法。 */
|
|
20
|
+
function decorateMethod(target, methodName, decorator) {
|
|
21
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, methodName);
|
|
22
|
+
assert.ok(descriptor);
|
|
23
|
+
decorator(target, methodName, descriptor);
|
|
24
|
+
Object.defineProperty(target, methodName, descriptor);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 在应用初始化 Logger 后执行正常缓存和 Provider 失败调用。 */
|
|
28
|
+
async function main() {
|
|
29
|
+
LoggerFactory.init({
|
|
30
|
+
root: {
|
|
31
|
+
level: 'error',
|
|
32
|
+
console: { enabled: false, colors: false, format: 'json' },
|
|
33
|
+
file: { enabled: false },
|
|
34
|
+
},
|
|
35
|
+
loggers: {
|
|
36
|
+
[CACHE_LOGGER_NAME]: {
|
|
37
|
+
level: profileLevel,
|
|
38
|
+
console: { enabled: true, colors: false, format: 'json' },
|
|
39
|
+
file: { enabled: false },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
masking: { enabled: true },
|
|
43
|
+
processErrors: {
|
|
44
|
+
uncaughtException: false,
|
|
45
|
+
unhandledRejection: false,
|
|
46
|
+
exitOnError: false,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const cachePackageRoot = resolve(fixtureDirectory, '../..');
|
|
51
|
+
const requireFromCache = createRequire(join(cachePackageRoot, 'package.json'));
|
|
52
|
+
const directLoggerEntry = fixtureRequire.resolve('@jintianxiayu/logger');
|
|
53
|
+
const cacheLoggerEntry = requireFromCache.resolve('@jintianxiayu/logger');
|
|
54
|
+
assert.equal(realpathSync(directLoggerEntry), realpathSync(cacheLoggerEntry));
|
|
55
|
+
const namedLogger = LoggerFactory.getLogger(CACHE_LOGGER_NAME);
|
|
56
|
+
|
|
57
|
+
const memoryProvider = new MemoryCacheProvider();
|
|
58
|
+
const providerError = Object.assign(new Error('redis unavailable'), {
|
|
59
|
+
password: 'provider-password-secret',
|
|
60
|
+
email: 'provider@example.com',
|
|
61
|
+
});
|
|
62
|
+
const failingProvider = {
|
|
63
|
+
get: async () => {
|
|
64
|
+
throw providerError;
|
|
65
|
+
},
|
|
66
|
+
set: () => undefined,
|
|
67
|
+
delete: () => undefined,
|
|
68
|
+
clear: () => undefined,
|
|
69
|
+
deleteByPattern: () => undefined,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
class SuccessService {
|
|
73
|
+
getUser() {
|
|
74
|
+
return { id: 17, password: 'business-value-secret' };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
decorateMethod(SuccessService.prototype, 'getUser', Cache('fixture-users', { providerName: 'memory' }));
|
|
78
|
+
|
|
79
|
+
class FailureService {
|
|
80
|
+
getUser() {
|
|
81
|
+
throw new Error('business method must not run');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
decorateMethod(FailureService.prototype, 'getUser', Cache('fixture-failure-users', { providerName: 'failing' }));
|
|
85
|
+
|
|
86
|
+
CacheProviderRegistry.register('memory', memoryProvider);
|
|
87
|
+
CacheProviderRegistry.register('failing', failingProvider);
|
|
88
|
+
try {
|
|
89
|
+
const result = await LoggerContext.withContext({ traceId: 'cache-fixture-trace' }, () =>
|
|
90
|
+
new SuccessService().getUser()
|
|
91
|
+
);
|
|
92
|
+
assert.deepEqual(result, { id: 17, password: 'business-value-secret' });
|
|
93
|
+
|
|
94
|
+
const failure = LoggerContext.withContext({ traceId: 'cache-fixture-trace' }, () =>
|
|
95
|
+
new FailureService().getUser()
|
|
96
|
+
);
|
|
97
|
+
await assert.rejects(failure, (error) => error === providerError);
|
|
98
|
+
assert.strictEqual(LoggerFactory.getLogger(CACHE_LOGGER_NAME), namedLogger);
|
|
99
|
+
} finally {
|
|
100
|
+
CacheProviderRegistry.clear();
|
|
101
|
+
await LoggerFactory.shutdown({ timeout: 2_000 });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
main().catch((error) => {
|
|
106
|
+
process.stderr.write(`${error instanceof Error ? error.stack : String(error)}\n`);
|
|
107
|
+
process.exitCode = 1;
|
|
108
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import Redis from 'ioredis';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 使用 0.1.3 的字符串/JSON 与秒级 TTL 协议写入兼容性数据。
|
|
5
|
+
* @param client 由测试创建并持有的 ioredis 连接。
|
|
6
|
+
* @param key 旧版本收到的原始缓存 key。
|
|
7
|
+
* @param value 旧版本支持的字符串或 JSON 可序列化值。
|
|
8
|
+
* @param ttl 可选的秒级 TTL;0 与 undefined 使用普通 SET。
|
|
9
|
+
* @returns 旧版写入命令成功后完成。
|
|
10
|
+
* @throws 无法序列化或 Redis 命令失败时传播错误。
|
|
11
|
+
*/
|
|
12
|
+
export async function writeLegacyRedisCache(client: Redis, key: string, value: unknown, ttl?: number): Promise<void> {
|
|
13
|
+
const serialized = typeof value === 'string' ? value : JSON.stringify(value);
|
|
14
|
+
if (typeof serialized !== 'string') {
|
|
15
|
+
throw new TypeError('Legacy cache fixture value must serialize to a string');
|
|
16
|
+
}
|
|
17
|
+
if (ttl) {
|
|
18
|
+
await client.setex(key, ttl, serialized);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
await client.set(key, serialized);
|
|
22
|
+
}
|