@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 CHANGED
@@ -46,15 +46,15 @@ CacheProviderRegistry.register('memory', new MemoryCacheProvider());
46
46
  CacheProviderRegistry.setDefault('memory');
47
47
 
48
48
  class UserService {
49
- @Cache('user-cache', { ttl: 60000 })
50
- async getUser(id: number) {
51
- return { id, name: 'test' };
52
- }
53
-
54
- @CacheEvict('user-cache')
55
- async updateUser(id: number) {
56
- return { id, updated: true };
57
- }
49
+ @Cache('user-cache', { ttl: 60000 })
50
+ async getUser(id: number) {
51
+ return { id, name: 'test' };
52
+ }
53
+
54
+ @CacheEvict('user-cache')
55
+ async updateUser(id: number) {
56
+ return { id, updated: true };
57
+ }
58
58
  }
59
59
  ```
60
60
 
@@ -84,8 +84,6 @@ class UserService {
84
84
  }
85
85
  ```
86
86
 
87
-
88
-
89
87
  ## API
90
88
 
91
89
  ### @Cache(cacheName, options?)
@@ -96,9 +94,9 @@ class UserService {
96
94
  - `options.ttl`: 过期时间(秒)
97
95
  - `options.providerName`: 指定 CacheProvider
98
96
  - `options.key`: 自定义缓存 key,支持以下形式:
99
- - `undefined/null`: 使用默认逻辑,基于方法参数生成 key
100
- - `string`: 直接作为 key 值,格式为 `cacheName:keyValue`
101
- - `function`: 接收方法参数数组,返回自定义字符串
97
+ - `undefined/null`: 使用默认逻辑,基于方法参数生成 key
98
+ - `string`: 直接作为 key 值,格式为 `cacheName:keyValue`
99
+ - `function`: 接收方法参数数组,返回自定义字符串
102
100
 
103
101
  ### @CacheEvict(cacheName, options?)
104
102
 
@@ -130,4 +128,4 @@ Redis 实现,适用于分布式场景。
130
128
  ```typescript
131
129
  import Redis from 'ioredis';
132
130
  const redisProvider = new RedisCacheProvider(new Redis());
133
- ```
131
+ ```
package/jest.config.js CHANGED
@@ -1,14 +1,11 @@
1
1
  module.exports = {
2
- preset: 'ts-jest',
3
- testEnvironment: 'node',
4
- roots: ['<rootDir>'],
5
- testMatch: ['**/test/**/*.test.ts'],
6
- moduleFileExtensions: ['ts', 'js', 'json'],
7
- collectCoverageFrom: [
8
- 'src/**/*.ts',
9
- '!src/**/*.d.ts'
10
- ],
11
- transform: {
12
- '^.+\\.ts$': 'ts-jest'
13
- }
14
- };
2
+ preset: 'ts-jest',
3
+ testEnvironment: 'node',
4
+ roots: ['<rootDir>'],
5
+ testMatch: ['**/test/**/*.test.ts'],
6
+ moduleFileExtensions: ['ts', 'js', 'json'],
7
+ collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts'],
8
+ transform: {
9
+ '^.+\\.ts$': 'ts-jest',
10
+ },
11
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jintianxiayu/cache-decorator",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Method caching decorators for TypeScript",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -26,5 +26,5 @@
26
26
  "publishConfig": {
27
27
  "access": "public"
28
28
  },
29
- "gitHead": "357e736abbe6b25a707663c1b9e027b2e8b532a6"
29
+ "gitHead": "0518a4e3649799cc6c176dabfebeb86bd0891085"
30
30
  }
@@ -8,46 +8,46 @@ let defaultName: string | undefined;
8
8
  * 用于注册和获取不同类型的 CacheProvider
9
9
  */
10
10
  export class CacheProviderRegistry {
11
- /**
12
- * 注册缓存提供者
13
- * @param name 提供者名称
14
- * @param provider 缓存提供者实例
15
- */
16
- static register(name: string, provider: CacheProvider): void {
17
- providers.set(name, provider);
18
- }
19
-
20
- /**
21
- * 获取缓存提供者
22
- * @param name 提供者名称,不指定则使用默认提供者
23
- * @returns 缓存提供者实例
24
- * @throws 未注册且无默认提供者时抛出错误
25
- */
26
- static get(name?: string): CacheProvider {
27
- const providerName = name || defaultName;
28
- if (!providerName) {
29
- throw new Error('No CacheProvider registered and no default set');
11
+ /**
12
+ * 注册缓存提供者
13
+ * @param name 提供者名称
14
+ * @param provider 缓存提供者实例
15
+ */
16
+ static register(name: string, provider: CacheProvider): void {
17
+ providers.set(name, provider);
30
18
  }
31
- const provider = providers.get(providerName);
32
- if (!provider) {
33
- throw new Error(`CacheProvider "${providerName}" not found`);
19
+
20
+ /**
21
+ * 获取缓存提供者
22
+ * @param name 提供者名称,不指定则使用默认提供者
23
+ * @returns 缓存提供者实例
24
+ * @throws 未注册且无默认提供者时抛出错误
25
+ */
26
+ static get(name?: string): CacheProvider {
27
+ const providerName = name || defaultName;
28
+ if (!providerName) {
29
+ throw new Error('No CacheProvider registered and no default set');
30
+ }
31
+ const provider = providers.get(providerName);
32
+ if (!provider) {
33
+ throw new Error(`CacheProvider "${providerName}" not found`);
34
+ }
35
+ return provider;
34
36
  }
35
- return provider;
36
- }
37
37
 
38
- /**
39
- * 设置默认缓存提供者
40
- * @param name 提供者名称
41
- */
42
- static setDefault(name: string): void {
43
- defaultName = name;
44
- }
38
+ /**
39
+ * 设置默认缓存提供者
40
+ * @param name 提供者名称
41
+ */
42
+ static setDefault(name: string): void {
43
+ defaultName = name;
44
+ }
45
45
 
46
- /**
47
- * 清空注册表
48
- */
49
- static clear(): void {
50
- providers.clear();
51
- defaultName = undefined;
52
- }
53
- }
46
+ /**
47
+ * 清空注册表
48
+ */
49
+ static clear(): void {
50
+ providers.clear();
51
+ defaultName = undefined;
52
+ }
53
+ }
@@ -3,35 +3,35 @@
3
3
  * 支持可插拔的缓存实现(Memory、Redis、自定义)
4
4
  */
5
5
  export interface CacheProvider {
6
- /**
7
- * 获取缓存值
8
- * @param key 缓存键
9
- * @returns 缓存值,不存在或已过期返回 undefined
10
- */
11
- get<T>(key: string): T | undefined | Promise<T | undefined>;
6
+ /**
7
+ * 获取缓存值
8
+ * @param key 缓存键
9
+ * @returns 缓存值,不存在或已过期返回 undefined
10
+ */
11
+ get<T>(key: string): T | undefined | Promise<T | undefined>;
12
12
 
13
- /**
14
- * 设置缓存值
15
- * @param key 缓存键
16
- * @param value 缓存值
17
- * @param ttl 过期时间(秒),不设置则永不过期
18
- */
19
- set<T>(key: string, value: T, ttl?: number): void | Promise<void>;
13
+ /**
14
+ * 设置缓存值
15
+ * @param key 缓存键
16
+ * @param value 缓存值
17
+ * @param ttl 过期时间(秒),不设置则永不过期
18
+ */
19
+ set<T>(key: string, value: T, ttl?: number): void | Promise<void>;
20
20
 
21
- /**
22
- * 删除指定缓存
23
- * @param key 缓存键
24
- */
25
- delete(key: string): void | Promise<void>;
21
+ /**
22
+ * 删除指定缓存
23
+ * @param key 缓存键
24
+ */
25
+ delete(key: string): void | Promise<void>;
26
26
 
27
- /**
28
- * 清除所有缓存
29
- */
30
- clear(): void | Promise<void>;
27
+ /**
28
+ * 清除所有缓存
29
+ */
30
+ clear(): void | Promise<void>;
31
31
 
32
- /**
33
- * 根据模式删除匹配的缓存键
34
- * @param pattern glob模式字符串(如 user:*),末尾的 * 作为前缀匹配
35
- */
36
- deleteByPattern(pattern: string): void | Promise<void>;
37
- }
32
+ /**
33
+ * 根据模式删除匹配的缓存键
34
+ * @param pattern glob模式字符串(如 user:*),末尾的 * 作为前缀匹配
35
+ */
36
+ deleteByPattern(pattern: string): void | Promise<void>;
37
+ }
@@ -3,29 +3,29 @@
3
3
  * 负责将缓存名称和参数转换为统一的 key 字符串
4
4
  */
5
5
  export class KeyBuilder {
6
- /**
7
- * 构建缓存 key
8
- * @param cacheName 缓存名称
9
- * @param args 方法参数列表
10
- * @returns 格式为 {cacheName}:{serializedArgs} 的 key 字符串
11
- */
12
- static build(cacheName: string, args: unknown[]): string {
13
- if (args.length === 0) {
14
- return cacheName;
15
- }
16
- const serializedArgs = args
17
- .map(arg => {
18
- if (arg === null || arg === undefined) return 'null';
19
- if (typeof arg === 'object') {
20
- try {
21
- return JSON.stringify(arg);
22
- } catch {
23
- return String(arg);
24
- }
6
+ /**
7
+ * 构建缓存 key
8
+ * @param cacheName 缓存名称
9
+ * @param args 方法参数列表
10
+ * @returns 格式为 {cacheName}:{serializedArgs} 的 key 字符串
11
+ */
12
+ static build(cacheName: string, args: unknown[]): string {
13
+ if (args.length === 0) {
14
+ return cacheName;
25
15
  }
26
- return String(arg);
27
- })
28
- .join('&');
29
- return `${cacheName}:${serializedArgs}`;
30
- }
31
- }
16
+ const serializedArgs = args
17
+ .map((arg) => {
18
+ if (arg === null || arg === undefined) return 'null';
19
+ if (typeof arg === 'object') {
20
+ try {
21
+ return JSON.stringify(arg);
22
+ } catch {
23
+ return String(arg);
24
+ }
25
+ }
26
+ return String(arg);
27
+ })
28
+ .join('&');
29
+ return `${cacheName}:${serializedArgs}`;
30
+ }
31
+ }
@@ -4,8 +4,8 @@ import { CacheProvider } from './cache-provider';
4
4
  * 缓存条目结构
5
5
  */
6
6
  interface CacheEntry<T> {
7
- value: T;
8
- expiresAt: number | null;
7
+ value: T;
8
+ expiresAt: number | null;
9
9
  }
10
10
 
11
11
  /**
@@ -13,41 +13,41 @@ interface CacheEntry<T> {
13
13
  * 适用于单机应用,缓存随进程重启清除
14
14
  */
15
15
  export class MemoryCacheProvider implements CacheProvider {
16
- private cache = new Map<string, CacheEntry<unknown>>();
16
+ private cache = new Map<string, CacheEntry<unknown>>();
17
17
 
18
- get<T>(key: string): T | undefined {
19
- const entry = this.cache.get(key);
20
- if (!entry) return undefined;
21
- if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {
22
- this.cache.delete(key);
23
- return undefined;
18
+ get<T>(key: string): T | undefined {
19
+ const entry = this.cache.get(key);
20
+ if (!entry) return undefined;
21
+ if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {
22
+ this.cache.delete(key);
23
+ return undefined;
24
+ }
25
+ return entry.value as T;
24
26
  }
25
- return entry.value as T;
26
- }
27
27
 
28
- set<T>(key: string, value: T, ttl?: number): void {
29
- const expiresAt = ttl ? Date.now() + ttl * 1000 : null;
30
- this.cache.set(key, { value, expiresAt });
31
- }
28
+ set<T>(key: string, value: T, ttl?: number): void {
29
+ const expiresAt = ttl ? Date.now() + ttl * 1000 : null;
30
+ this.cache.set(key, { value, expiresAt });
31
+ }
32
32
 
33
- delete(key: string): void {
34
- this.cache.delete(key);
35
- }
33
+ delete(key: string): void {
34
+ this.cache.delete(key);
35
+ }
36
36
 
37
- clear(): void {
38
- this.cache.clear();
39
- }
37
+ clear(): void {
38
+ this.cache.clear();
39
+ }
40
40
 
41
- /**
42
- * 根据模式删除匹配的缓存键
43
- * @param pattern glob模式字符串(如 user:*),末尾的 * 作为前缀匹配
44
- */
45
- deleteByPattern(pattern: string): void {
46
- const prefix = pattern.replace(/\*$/, '');
47
- for (const key of this.cache.keys()) {
48
- if (key.startsWith(prefix)) {
49
- this.cache.delete(key);
50
- }
41
+ /**
42
+ * 根据模式删除匹配的缓存键
43
+ * @param pattern glob模式字符串(如 user:*),末尾的 * 作为前缀匹配
44
+ */
45
+ deleteByPattern(pattern: string): void {
46
+ const prefix = pattern.replace(/\*$/, '');
47
+ for (const key of this.cache.keys()) {
48
+ if (key.startsWith(prefix)) {
49
+ this.cache.delete(key);
50
+ }
51
+ }
51
52
  }
52
- }
53
- }
53
+ }
@@ -3,24 +3,24 @@
3
3
  * 用于在并发场景下返回同一个 Promise,避免重复执行
4
4
  */
5
5
  export class PendingCache {
6
- private pending = new Map<string, Promise<unknown>>();
6
+ private pending = new Map<string, Promise<unknown>>();
7
7
 
8
- get<T>(key: string): Promise<T> | undefined {
9
- return this.pending.get(key) as Promise<T> | undefined;
10
- }
8
+ get<T>(key: string): Promise<T> | undefined {
9
+ return this.pending.get(key) as Promise<T> | undefined;
10
+ }
11
11
 
12
- set<T>(key: string, promise: Promise<T>): void {
13
- this.pending.set(key, promise);
14
- promise.finally(() => {
15
- this.pending.delete(key);
16
- });
17
- }
12
+ set<T>(key: string, promise: Promise<T>): void {
13
+ this.pending.set(key, promise);
14
+ promise.finally(() => {
15
+ this.pending.delete(key);
16
+ });
17
+ }
18
18
 
19
- delete(key: string): void {
20
- this.pending.delete(key);
21
- }
19
+ delete(key: string): void {
20
+ this.pending.delete(key);
21
+ }
22
22
 
23
- clear(): void {
24
- this.pending.clear();
25
- }
26
- }
23
+ clear(): void {
24
+ this.pending.clear();
25
+ }
26
+ }
@@ -6,57 +6,51 @@ import Redis from 'ioredis';
6
6
  * 适用于分布式场景,支持跨进程共享缓存
7
7
  */
8
8
  export class RedisCacheProvider implements CacheProvider {
9
- private redis: Redis;
9
+ private redis: Redis;
10
10
 
11
- constructor(redis?: Redis) {
12
- this.redis = redis ?? new Redis();
13
- }
11
+ constructor(redis?: Redis) {
12
+ this.redis = redis ?? new Redis();
13
+ }
14
14
 
15
- async get<T>(key: string): Promise<T | undefined> {
16
- const value = await this.redis.get(key);
17
- if (value === null) return undefined;
18
- try {
19
- return JSON.parse(value) as T;
20
- } catch {
21
- return value as unknown as T;
15
+ async get<T>(key: string): Promise<T | undefined> {
16
+ const value = await this.redis.get(key);
17
+ if (value === null) return undefined;
18
+ try {
19
+ return JSON.parse(value) as T;
20
+ } catch {
21
+ return value as unknown as T;
22
+ }
22
23
  }
23
- }
24
24
 
25
- async set<T>(key: string, value: T, ttl?: number): Promise<void> {
26
- const serialized = typeof value === 'string' ? value : JSON.stringify(value);
27
- if (ttl) {
28
- await this.redis.setex(key, ttl, serialized);
29
- } else {
30
- await this.redis.set(key, serialized);
25
+ async set<T>(key: string, value: T, ttl?: number): Promise<void> {
26
+ const serialized = typeof value === 'string' ? value : JSON.stringify(value);
27
+ if (ttl) {
28
+ await this.redis.setex(key, ttl, serialized);
29
+ } else {
30
+ await this.redis.set(key, serialized);
31
+ }
31
32
  }
32
- }
33
33
 
34
- async delete(key: string): Promise<void> {
35
- await this.redis.del(key);
36
- }
34
+ async delete(key: string): Promise<void> {
35
+ await this.redis.del(key);
36
+ }
37
37
 
38
- async clear(): Promise<void> {
39
- await this.redis.flushdb();
40
- }
38
+ async clear(): Promise<void> {
39
+ await this.redis.flushdb();
40
+ }
41
41
 
42
- /**
43
- * 根据模式删除匹配的缓存键
44
- * @param pattern glob模式字符串(如 user:*),使用 SCAN 游标迭代避免阻塞 Redis
45
- */
46
- async deleteByPattern(pattern: string): Promise<void> {
47
- let cursor = '0';
48
- do {
49
- const [nextCursor, keys] = await this.redis.scan(
50
- cursor,
51
- 'MATCH',
52
- pattern,
53
- 'COUNT',
54
- 100,
55
- );
56
- cursor = nextCursor;
57
- if (keys.length > 0) {
58
- await this.redis.del(...keys);
59
- }
60
- } while (cursor !== '0');
61
- }
62
- }
42
+ /**
43
+ * 根据模式删除匹配的缓存键
44
+ * @param pattern glob模式字符串(如 user:*),使用 SCAN 游标迭代避免阻塞 Redis
45
+ */
46
+ async deleteByPattern(pattern: string): Promise<void> {
47
+ let cursor = '0';
48
+ do {
49
+ const [nextCursor, keys] = await this.redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
50
+ cursor = nextCursor;
51
+ if (keys.length > 0) {
52
+ await this.redis.del(...keys);
53
+ }
54
+ } while (cursor !== '0');
55
+ }
56
+ }
@@ -6,15 +6,15 @@ import { CacheOptions, CacheKeyResolver } from './cache';
6
6
  * @CacheEvict 装饰器配置项
7
7
  */
8
8
  export interface CacheEvictOptions extends Pick<CacheOptions, 'key'> {
9
- /**
10
- * 是否清除所有条目,默认为 false
11
- */
12
- allEntries?: boolean;
9
+ /**
10
+ * 是否清除所有条目,默认为 false
11
+ */
12
+ allEntries?: boolean;
13
13
 
14
- /**
15
- * 指定 CacheProvider 名称
16
- */
17
- providerName?: string;
14
+ /**
15
+ * 指定 CacheProvider 名称
16
+ */
17
+ providerName?: string;
18
18
  }
19
19
 
20
20
  /**
@@ -25,17 +25,17 @@ export interface CacheEvictOptions extends Pick<CacheOptions, 'key'> {
25
25
  * @returns 解析后的缓存 key
26
26
  */
27
27
  function resolveCacheKey(cacheName: string, keyResolver: CacheKeyResolver | undefined, args: unknown[]): string {
28
- if (keyResolver === undefined || keyResolver === null) {
29
- return KeyBuilder.build(cacheName, args);
30
- }
31
- if (typeof keyResolver === 'string') {
32
- return KeyBuilder.build(cacheName, [keyResolver]);
33
- }
34
- try {
35
- return KeyBuilder.build(cacheName, [keyResolver(...args)]);
36
- } catch {
37
- return KeyBuilder.build(cacheName, args);
38
- }
28
+ if (keyResolver === undefined || keyResolver === null) {
29
+ return KeyBuilder.build(cacheName, args);
30
+ }
31
+ if (typeof keyResolver === 'string') {
32
+ return KeyBuilder.build(cacheName, [keyResolver]);
33
+ }
34
+ try {
35
+ return KeyBuilder.build(cacheName, [keyResolver(...args)]);
36
+ } catch {
37
+ return KeyBuilder.build(cacheName, args);
38
+ }
39
39
  }
40
40
 
41
41
  /**
@@ -45,29 +45,25 @@ function resolveCacheKey(cacheName: string, keyResolver: CacheKeyResolver | unde
45
45
  * @param options 配置项
46
46
  */
47
47
  export function CacheEvict(cacheName: string, options?: CacheEvictOptions) {
48
- return function (
49
- _target: object,
50
- _propertyKey: string,
51
- descriptor: PropertyDescriptor,
52
- ) {
53
- const originalMethod = descriptor.value;
48
+ return function (_target: object, _propertyKey: string, descriptor: PropertyDescriptor) {
49
+ const originalMethod = descriptor.value;
54
50
 
55
- descriptor.value = async function (...args: unknown[]) {
56
- const result = await originalMethod.apply(this, args);
51
+ descriptor.value = async function (...args: unknown[]) {
52
+ const result = await originalMethod.apply(this, args);
57
53
 
58
- const providerName = options?.providerName;
59
- const provider = CacheProviderRegistry.get(providerName);
54
+ const providerName = options?.providerName;
55
+ const provider = CacheProviderRegistry.get(providerName);
60
56
 
61
- if (options?.allEntries) {
62
- await provider.deleteByPattern(cacheName + '*');
63
- } else {
64
- const cacheKey = resolveCacheKey(cacheName, options?.key, args);
65
- provider.delete(cacheKey);
66
- }
57
+ if (options?.allEntries) {
58
+ await provider.deleteByPattern(cacheName + '*');
59
+ } else {
60
+ const cacheKey = resolveCacheKey(cacheName, options?.key, args);
61
+ provider.delete(cacheKey);
62
+ }
67
63
 
68
- return result;
64
+ return result;
65
+ };
69
66
  };
70
- };
71
67
  }
72
68
 
73
- export { CacheProviderRegistry } from '../core/cache-provider-registry';
69
+ export { CacheProviderRegistry } from '../core/cache-provider-registry';