@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.
Files changed (91) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +270 -133
  3. package/dist/adapters/ioredis-cache-client.d.ts +9 -0
  4. package/dist/adapters/ioredis-cache-client.d.ts.map +1 -0
  5. package/dist/adapters/ioredis-cache-client.js +73 -0
  6. package/dist/adapters/ioredis-cache-client.js.map +1 -0
  7. package/dist/adapters/node-redis-cache-client.d.ts +10 -0
  8. package/dist/adapters/node-redis-cache-client.d.ts.map +1 -0
  9. package/dist/adapters/node-redis-cache-client.js +81 -0
  10. package/dist/adapters/node-redis-cache-client.js.map +1 -0
  11. package/dist/adapters/redis-key-prefix.d.ts +22 -0
  12. package/dist/adapters/redis-key-prefix.d.ts.map +1 -0
  13. package/dist/adapters/redis-key-prefix.js +41 -0
  14. package/dist/adapters/redis-key-prefix.js.map +1 -0
  15. package/dist/core/cache-logger.d.ts +28 -0
  16. package/dist/core/cache-logger.d.ts.map +1 -0
  17. package/dist/core/cache-logger.js +70 -0
  18. package/dist/core/cache-logger.js.map +1 -0
  19. package/dist/core/cache-provider-registry.d.ts.map +1 -1
  20. package/dist/core/cache-provider-registry.js.map +1 -1
  21. package/dist/core/cache-provider.d.ts.map +1 -1
  22. package/dist/core/key-builder.d.ts.map +1 -1
  23. package/dist/core/key-builder.js +3 -2
  24. package/dist/core/key-builder.js.map +1 -1
  25. package/dist/core/native-cache.d.ts.map +1 -1
  26. package/dist/core/native-cache.js +2 -1
  27. package/dist/core/native-cache.js.map +1 -1
  28. package/dist/core/pending-cache.d.ts.map +1 -1
  29. package/dist/core/pending-cache.js +6 -3
  30. package/dist/core/pending-cache.js.map +1 -1
  31. package/dist/core/redis-cache-client.d.ts +156 -0
  32. package/dist/core/redis-cache-client.d.ts.map +1 -0
  33. package/dist/core/redis-cache-client.js +3 -0
  34. package/dist/core/redis-cache-client.js.map +1 -0
  35. package/dist/core/redis-cache.d.ts +39 -7
  36. package/dist/core/redis-cache.d.ts.map +1 -1
  37. package/dist/core/redis-cache.js +63 -22
  38. package/dist/core/redis-cache.js.map +1 -1
  39. package/dist/decorators/cache-evict.d.ts +1 -1
  40. package/dist/decorators/cache-evict.d.ts.map +1 -1
  41. package/dist/decorators/cache-evict.js +68 -14
  42. package/dist/decorators/cache-evict.js.map +1 -1
  43. package/dist/decorators/cache.d.ts +1 -1
  44. package/dist/decorators/cache.d.ts.map +1 -1
  45. package/dist/decorators/cache.js +85 -23
  46. package/dist/decorators/cache.js.map +1 -1
  47. package/dist/index.d.ts +3 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +6 -0
  50. package/dist/index.js.map +1 -1
  51. package/jest.config.js +11 -14
  52. package/package.json +26 -29
  53. package/src/adapters/ioredis-cache-client.ts +89 -0
  54. package/src/adapters/node-redis-cache-client.ts +95 -0
  55. package/src/adapters/redis-key-prefix.ts +38 -0
  56. package/src/core/cache-logger.ts +105 -0
  57. package/src/core/cache-provider-registry.ts +39 -39
  58. package/src/core/cache-provider.ts +28 -28
  59. package/src/core/key-builder.ts +33 -31
  60. package/src/core/native-cache.ts +55 -53
  61. package/src/core/pending-cache.ts +29 -26
  62. package/src/core/redis-cache-client.ts +160 -0
  63. package/src/core/redis-cache.ts +104 -62
  64. package/src/decorators/cache-evict.ts +129 -73
  65. package/src/decorators/cache.ts +203 -129
  66. package/src/index.ts +11 -9
  67. package/test/cache-evict-logging.test.ts +362 -0
  68. package/test/cache-evict.test.ts +165 -165
  69. package/test/cache-logger.integration.test.ts +129 -0
  70. package/test/cache-logger.test.ts +153 -0
  71. package/test/cache-logging.test.ts +544 -0
  72. package/test/cache-provider-registry.test.ts +24 -24
  73. package/test/cache.test.ts +255 -256
  74. package/test/fixtures/cache-logger-child.mjs +108 -0
  75. package/test/helpers/legacy-redis-cache.ts +22 -0
  76. package/test/helpers/package-consumer.ts +231 -0
  77. package/test/helpers/redis-fixture.ts +142 -0
  78. package/test/ioredis-cache-client.test.ts +143 -0
  79. package/test/key-builder.test.ts +28 -28
  80. package/test/native-cache.test.ts +77 -71
  81. package/test/node-redis-cache-client.test.ts +149 -0
  82. package/test/pending-cache.test.ts +69 -56
  83. package/test/redis-cache-client-lifecycle.test.ts +112 -0
  84. package/test/redis-cache-client-types.test.ts +184 -0
  85. package/test/redis-cache-client.integration.test.ts +327 -0
  86. package/test/redis-cache-decorator.test.ts +201 -0
  87. package/test/redis-cache-package.integration.test.ts +328 -0
  88. package/test/redis-cache-provider.test.ts +269 -0
  89. package/test/type-contract/contract.ts +64 -0
  90. package/test/type-contract/tsconfig.json +12 -0
  91. package/tsconfig.json +8 -8
@@ -1,129 +1,203 @@
1
- import 'reflect-metadata';
2
- import { CacheProviderRegistry } from '../core/cache-provider-registry';
3
- import { KeyBuilder } from '../core/key-builder';
4
- import { PendingCache } from '../core/pending-cache';
5
-
6
- /**
7
- * 缓存 key 解析器类型
8
- * - null: 使用自动生成逻辑
9
- * - string: 直接作为 key 值的一部分
10
- * - function: 接收方法参数数组,返回自定义字符串
11
- */
12
- export type CacheKeyResolver = null | string | ((...args: unknown[]) => string);
13
-
14
- /**
15
- * @Cache 装饰器配置项
16
- */
17
- export interface CacheOptions {
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
- }
36
-
37
- /**
38
- * 成功缓存条目
39
- */
40
- interface SuccessCacheEntry<T> {
41
- value: T;
42
- }
43
-
44
- /**
45
- * 错误缓存条目
46
- */
47
- interface ErrorCacheEntry {
48
- error: unknown;
49
- }
50
-
51
- /**
52
- * 缓存条目联合类型
53
- */
54
- type CacheEntry<T> = SuccessCacheEntry<T> | ErrorCacheEntry;
55
-
56
- const pendingCache = new PendingCache();
57
-
58
- /**
59
- * 解析缓存 key
60
- * @param cacheName 缓存名称
61
- * @param keyResolver key 解析器
62
- * @param args 方法参数数组
63
- * @returns 解析后的缓存 key
64
- */
65
- function resolveCacheKey(cacheName: string, keyResolver: CacheKeyResolver | undefined, args: unknown[]): string {
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
- }
78
-
79
- /**
80
- * 缓存装饰器
81
- * 为方法添加声明式缓存功能,支持 TTL 过期和请求合并
82
- * @param cacheName 缓存名称
83
- * @param options 配置项
84
- */
85
- export function Cache(cacheName: string, options?: CacheOptions) {
86
- return function <T>(
87
- _target: object,
88
- _propertyKey: string,
89
- descriptor: PropertyDescriptor,
90
- ) {
91
- const originalMethod = descriptor.value;
92
-
93
- descriptor.value = async function (...args: unknown[]) {
94
- const cacheKey = resolveCacheKey(cacheName, options?.key, args);
95
- const providerName = options?.providerName;
96
- const provider = CacheProviderRegistry.get(providerName);
97
-
98
- const cached = await provider.get(cacheKey);
99
- if (cached !== undefined) {
100
- const entry = cached as CacheEntry<T>;
101
- if ('error' in entry) {
102
- throw entry.error;
103
- }
104
- return entry.value;
105
- }
106
-
107
- const pending = pendingCache.get<T>(cacheKey);
108
- if (pending) {
109
- return pending;
110
- }
111
-
112
- const promise = (async () => {
113
- try {
114
- const result = await originalMethod.apply(this, args);
115
- provider.set(cacheKey, { value: result } as CacheEntry<T>, options?.ttl);
116
- return result;
117
- } catch (error) {
118
- provider.set(cacheKey, { error } as CacheEntry<T>, options?.ttl);
119
- throw error;
120
- }
121
- })();
122
-
123
- pendingCache.set(cacheKey, promise);
124
- return promise;
125
- };
126
- };
127
- }
128
-
129
- export { CacheProviderRegistry } from '../core/cache-provider-registry';
1
+ import 'reflect-metadata';
2
+ import { cacheProviderLabel, logCacheEvent, type CacheLogContext } from '../core/cache-logger';
3
+ import type { CacheProvider } from '../core/cache-provider';
4
+ import { CacheProviderRegistry } from '../core/cache-provider-registry';
5
+ import { KeyBuilder } from '../core/key-builder';
6
+ import { PendingCache } from '../core/pending-cache';
7
+
8
+ /**
9
+ * 缓存 key 解析器类型
10
+ * - null: 使用自动生成逻辑
11
+ * - string: 直接作为 key 值的一部分
12
+ * - function: 接收方法参数数组,返回自定义字符串
13
+ */
14
+ export type CacheKeyResolver = null | string | ((...args: unknown[]) => string);
15
+
16
+ /**
17
+ * @Cache 装饰器配置项
18
+ */
19
+ export interface CacheOptions {
20
+ /**
21
+ * 过期时间(秒)
22
+ */
23
+ ttl?: number;
24
+
25
+ /**
26
+ * 指定 CacheProvider 名称
27
+ */
28
+ providerName?: string;
29
+
30
+ /**
31
+ * 自定义缓存 key 生成逻辑
32
+ * - undefined/null: 使用默认逻辑 KeyBuilder.build(cacheName, args)
33
+ * - string: 使用 KeyBuilder.build(cacheName, [key])
34
+ * - function: 调用函数后使用 KeyBuilder.build(cacheName, [result])
35
+ */
36
+ key?: CacheKeyResolver;
37
+ }
38
+
39
+ /**
40
+ * 成功缓存条目
41
+ */
42
+ interface SuccessCacheEntry<T> {
43
+ value: T;
44
+ }
45
+
46
+ /**
47
+ * 错误缓存条目
48
+ */
49
+ interface ErrorCacheEntry {
50
+ error: unknown;
51
+ }
52
+
53
+ /**
54
+ * 缓存条目联合类型
55
+ */
56
+ type CacheEntry<T> = SuccessCacheEntry<T> | ErrorCacheEntry;
57
+
58
+ interface CacheWriteRequest<T> {
59
+ readonly provider: CacheProvider;
60
+ readonly cacheKey: string;
61
+ readonly entry: CacheEntry<T>;
62
+ readonly entryType: 'value' | 'error';
63
+ readonly ttl: number | undefined;
64
+ readonly logContext: CacheLogContext;
65
+ }
66
+
67
+ const pendingCache = new PendingCache();
68
+
69
+ /**
70
+ * 获取配置指向的缓存 Provider,并在解析失败时记录原始错误。
71
+ * @param providerName decorator 配置中的 Provider 名称。
72
+ * @param logContext 当前方法的稳定日志上下文。
73
+ * @returns 已注册的缓存 Provider。
74
+ * @throws Provider 注册表抛出的原始错误。
75
+ */
76
+ function resolveCacheProvider(providerName: string | undefined, logContext: CacheLogContext): CacheProvider {
77
+ try {
78
+ return CacheProviderRegistry.get(providerName);
79
+ } catch (error) {
80
+ logCacheEvent('cache.operation_failed', { ...logContext, operation: 'provider_resolution', error });
81
+ throw error;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * 保持 fire-and-forget 语义提交缓存写入,仅捕获调用当下可观察的同步失败。
87
+ * @param request 写入所需 Provider、entry 和无业务数据日志上下文。
88
+ * @returns 无返回值;异步写入 Promise 不会被等待或消费。
89
+ * @throws Provider set 同步抛出的原始错误。
90
+ */
91
+ function dispatchCacheWrite<T>(request: CacheWriteRequest<T>): void {
92
+ try {
93
+ request.provider.set(request.cacheKey, request.entry, request.ttl);
94
+ } catch (error) {
95
+ logCacheEvent('cache.operation_failed', { ...request.logContext, operation: 'write', error });
96
+ throw error;
97
+ }
98
+ logCacheEvent('cache.write_dispatched', { ...request.logContext, entryType: request.entryType });
99
+ }
100
+
101
+ /**
102
+ * 解析缓存 key
103
+ * @param keyResolver key 解析器
104
+ * @param args 方法参数数组
105
+ * @param logContext 不包含业务参数和值的日志上下文
106
+ * @returns 解析后的缓存 key
107
+ */
108
+ function resolveCacheKey(
109
+ keyResolver: CacheKeyResolver | undefined,
110
+ args: unknown[],
111
+ logContext: CacheLogContext
112
+ ): string {
113
+ if (keyResolver === undefined || keyResolver === null) {
114
+ return KeyBuilder.build(logContext.cacheName, args);
115
+ }
116
+ if (typeof keyResolver === 'string') {
117
+ return KeyBuilder.build(logContext.cacheName, [keyResolver]);
118
+ }
119
+ try {
120
+ return KeyBuilder.build(logContext.cacheName, [keyResolver(...args)]);
121
+ } catch (_error) {
122
+ logCacheEvent('cache.key_fallback', { ...logContext, reason: 'resolver_error' });
123
+ return KeyBuilder.build(logContext.cacheName, args);
124
+ }
125
+ }
126
+
127
+ /**
128
+ * 缓存装饰器
129
+ * 为方法添加声明式缓存功能,支持 TTL 过期和请求合并
130
+ * @param cacheName 缓存名称
131
+ * @param options 配置项
132
+ */
133
+ export function Cache(
134
+ cacheName: string,
135
+ options?: CacheOptions
136
+ ): (_target: object, _propertyKey: string, descriptor: PropertyDescriptor) => void {
137
+ return function <T>(_target: object, propertyKey: string, descriptor: PropertyDescriptor) {
138
+ const originalMethod = descriptor.value;
139
+ const logContext: CacheLogContext = {
140
+ cacheName,
141
+ methodName: propertyKey,
142
+ providerName: cacheProviderLabel(options?.providerName),
143
+ };
144
+
145
+ descriptor.value = function (...args: unknown[]): Promise<T> {
146
+ const cacheKey = resolveCacheKey(options?.key, args, logContext);
147
+
148
+ const pending = pendingCache.get<T>(cacheKey);
149
+ if (pending) {
150
+ logCacheEvent('cache.pending_hit', logContext);
151
+ return pending;
152
+ }
153
+
154
+ const promise = (async () => {
155
+ const provider = resolveCacheProvider(options?.providerName, logContext);
156
+ let cached: CacheEntry<T> | undefined;
157
+ try {
158
+ cached = await provider.get<CacheEntry<T>>(cacheKey);
159
+ } catch (error) {
160
+ logCacheEvent('cache.operation_failed', { ...logContext, operation: 'read', error });
161
+ throw error;
162
+ }
163
+ if (cached !== undefined) {
164
+ if ('error' in cached) {
165
+ logCacheEvent('cache.hit', { ...logContext, entryType: 'error' });
166
+ throw cached.error;
167
+ }
168
+ logCacheEvent('cache.hit', { ...logContext, entryType: 'value' });
169
+ return cached.value;
170
+ }
171
+ logCacheEvent('cache.miss', logContext);
172
+
173
+ try {
174
+ const result = (await originalMethod.apply(this, args)) as T;
175
+ dispatchCacheWrite({
176
+ provider,
177
+ cacheKey,
178
+ entry: { value: result },
179
+ entryType: 'value',
180
+ ttl: options?.ttl,
181
+ logContext,
182
+ });
183
+ return result;
184
+ } catch (error) {
185
+ dispatchCacheWrite({
186
+ provider,
187
+ cacheKey,
188
+ entry: { error },
189
+ entryType: 'error',
190
+ ttl: options?.ttl,
191
+ logContext,
192
+ });
193
+ throw error;
194
+ }
195
+ })();
196
+
197
+ pendingCache.set(cacheKey, promise);
198
+ return promise;
199
+ };
200
+ };
201
+ }
202
+
203
+ export { CacheProviderRegistry } from '../core/cache-provider-registry';
package/src/index.ts CHANGED
@@ -1,9 +1,11 @@
1
-
2
- export * from './core/cache-provider';
3
- export * from './core/cache-provider-registry';
4
- export * from './core/key-builder';
5
- export * from './core/native-cache';
6
- export * from './core/pending-cache';
7
- export * from './core/redis-cache';
8
- export * from './decorators/cache';
9
- export * from './decorators/cache-evict';
1
+ export { createIoredisCacheClient } from './adapters/ioredis-cache-client';
2
+ export { createNodeRedisCacheClient } from './adapters/node-redis-cache-client';
3
+ export * from './core/cache-provider';
4
+ export * from './core/cache-provider-registry';
5
+ export * from './core/key-builder';
6
+ export * from './core/native-cache';
7
+ export * from './core/pending-cache';
8
+ export * from './core/redis-cache-client';
9
+ export * from './core/redis-cache';
10
+ export * from './decorators/cache';
11
+ export * from './decorators/cache-evict';