@taicode/common-base 3.7.7 → 3.7.8
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/output/batching-buffer/batching-buffer.d.ts +133 -0
- package/output/batching-buffer/batching-buffer.d.ts.map +1 -0
- package/output/batching-buffer/batching-buffer.js +264 -0
- package/output/batching-buffer/batching-buffer.test.d.ts +2 -0
- package/output/batching-buffer/batching-buffer.test.d.ts.map +1 -0
- package/output/batching-buffer/batching-buffer.test.js +358 -0
- package/output/batching-buffer/index.d.ts +3 -0
- package/output/batching-buffer/index.d.ts.map +1 -0
- package/output/batching-buffer/index.js +1 -0
- package/output/catch/catch.d.ts +46 -0
- package/output/catch/catch.d.ts.map +1 -0
- package/output/catch/catch.js +48 -0
- package/output/catch/catch.test.d.ts +2 -0
- package/output/catch/catch.test.d.ts.map +1 -0
- package/output/catch/catch.test.js +43 -0
- package/output/catch/index.d.ts +2 -0
- package/output/catch/index.d.ts.map +1 -0
- package/output/catch/index.js +1 -0
- package/output/color/color.d.ts +6 -0
- package/output/color/color.d.ts.map +1 -0
- package/output/color/color.js +56 -0
- package/output/color/color.test.d.ts +2 -0
- package/output/color/color.test.d.ts.map +1 -0
- package/output/color/color.test.js +110 -0
- package/output/color/index.d.ts +2 -0
- package/output/color/index.d.ts.map +1 -0
- package/output/color/index.js +1 -0
- package/output/debounce/debounce.d.ts +7 -0
- package/output/debounce/debounce.d.ts.map +1 -0
- package/output/debounce/debounce.js +36 -0
- package/output/debounce/debounce.test.d.ts +2 -0
- package/output/debounce/debounce.test.d.ts.map +1 -0
- package/output/debounce/debounce.test.js +135 -0
- package/output/debounce/index.d.ts +2 -0
- package/output/debounce/index.d.ts.map +1 -0
- package/output/debounce/index.js +1 -0
- package/output/disposer/disposer.d.ts +8 -0
- package/output/disposer/disposer.d.ts.map +1 -0
- package/output/disposer/disposer.js +21 -0
- package/output/disposer/disposer.test.d.ts +2 -0
- package/output/disposer/disposer.test.d.ts.map +1 -0
- package/output/disposer/disposer.test.js +216 -0
- package/output/disposer/index.d.ts +2 -0
- package/output/disposer/index.d.ts.map +1 -0
- package/output/disposer/index.js +1 -0
- package/output/error/error.d.ts +128 -0
- package/output/error/error.d.ts.map +1 -0
- package/output/error/error.js +141 -0
- package/output/error/error.test.d.ts +2 -0
- package/output/error/error.test.d.ts.map +1 -0
- package/output/error/error.test.js +420 -0
- package/output/error/index.d.ts +3 -0
- package/output/error/index.d.ts.map +1 -0
- package/output/error/index.js +1 -0
- package/output/event-emitter/event-emitter.d.ts +33 -0
- package/output/event-emitter/event-emitter.d.ts.map +1 -0
- package/output/event-emitter/event-emitter.js +66 -0
- package/output/event-emitter/event-emitter.test.d.ts +2 -0
- package/output/event-emitter/event-emitter.test.d.ts.map +1 -0
- package/output/event-emitter/event-emitter.test.js +177 -0
- package/output/event-emitter/index.d.ts +2 -0
- package/output/event-emitter/index.d.ts.map +1 -0
- package/output/event-emitter/index.js +1 -0
- package/output/flow-queue/flow-queue.d.ts +486 -0
- package/output/flow-queue/flow-queue.d.ts.map +1 -0
- package/output/flow-queue/flow-queue.js +654 -0
- package/output/flow-queue/flow-queue.test.d.ts +2 -0
- package/output/flow-queue/flow-queue.test.d.ts.map +1 -0
- package/output/flow-queue/flow-queue.test.js +1205 -0
- package/output/flow-queue/index.d.ts +3 -0
- package/output/flow-queue/index.d.ts.map +1 -0
- package/output/flow-queue/index.js +3 -0
- package/output/index.d.ts +18 -0
- package/output/index.d.ts.map +1 -0
- package/output/index.js +26 -0
- package/output/logger/formatter.d.ts +70 -0
- package/output/logger/formatter.d.ts.map +1 -0
- package/output/logger/formatter.js +206 -0
- package/output/logger/formatter.test.d.ts +2 -0
- package/output/logger/formatter.test.d.ts.map +1 -0
- package/output/logger/formatter.test.js +296 -0
- package/output/logger/index.d.ts +4 -0
- package/output/logger/index.d.ts.map +1 -0
- package/output/logger/index.js +3 -0
- package/output/logger/logger.d.ts +64 -0
- package/output/logger/logger.d.ts.map +1 -0
- package/output/logger/logger.js +179 -0
- package/output/logger/logger.test.d.ts +2 -0
- package/output/logger/logger.test.d.ts.map +1 -0
- package/output/logger/logger.test.js +299 -0
- package/output/logger/transport.d.ts +124 -0
- package/output/logger/transport.d.ts.map +1 -0
- package/output/logger/transport.js +153 -0
- package/output/logger/transport.test.d.ts +2 -0
- package/output/logger/transport.test.d.ts.map +1 -0
- package/output/logger/transport.test.js +309 -0
- package/output/lru-cache/index.d.ts +2 -0
- package/output/lru-cache/index.d.ts.map +1 -0
- package/output/lru-cache/index.js +1 -0
- package/output/lru-cache/lru-cache.d.ts +22 -0
- package/output/lru-cache/lru-cache.d.ts.map +1 -0
- package/output/lru-cache/lru-cache.js +147 -0
- package/output/lru-cache/lru-cache.test.d.ts +2 -0
- package/output/lru-cache/lru-cache.test.d.ts.map +1 -0
- package/output/lru-cache/lru-cache.test.js +239 -0
- package/output/number/index.d.ts +2 -0
- package/output/number/index.d.ts.map +1 -0
- package/output/number/index.js +1 -0
- package/output/number/number.d.ts +2 -0
- package/output/number/number.d.ts.map +1 -0
- package/output/number/number.js +23 -0
- package/output/number/number.test.d.ts +2 -0
- package/output/number/number.test.d.ts.map +1 -0
- package/output/number/number.test.js +50 -0
- package/output/object/index.d.ts +2 -0
- package/output/object/index.d.ts.map +1 -0
- package/output/object/index.js +1 -0
- package/output/object/object.d.ts +7 -0
- package/output/object/object.d.ts.map +1 -0
- package/output/object/object.js +27 -0
- package/output/object/object.test.d.ts +2 -0
- package/output/object/object.test.d.ts.map +1 -0
- package/output/object/object.test.js +42 -0
- package/output/ring-cache/index.d.ts +6 -0
- package/output/ring-cache/index.d.ts.map +1 -0
- package/output/ring-cache/index.js +5 -0
- package/output/ring-cache/ring-cache.d.ts +125 -0
- package/output/ring-cache/ring-cache.d.ts.map +1 -0
- package/output/ring-cache/ring-cache.js +197 -0
- package/output/ring-cache/ring-cache.test.d.ts +2 -0
- package/output/ring-cache/ring-cache.test.d.ts.map +1 -0
- package/output/ring-cache/ring-cache.test.js +196 -0
- package/output/service/index.d.ts +2 -0
- package/output/service/index.d.ts.map +1 -0
- package/output/service/index.js +1 -0
- package/output/service/service.d.ts +6 -0
- package/output/service/service.d.ts.map +1 -0
- package/output/service/service.js +3 -0
- package/output/string/index.d.ts +2 -0
- package/output/string/index.d.ts.map +1 -0
- package/output/string/index.js +1 -0
- package/output/string/string.d.ts +3 -0
- package/output/string/string.d.ts.map +1 -0
- package/output/string/string.js +9 -0
- package/output/string/string.test.d.ts +2 -0
- package/output/string/string.test.d.ts.map +1 -0
- package/output/string/string.test.js +77 -0
- package/output/throttle/index.d.ts +2 -0
- package/output/throttle/index.d.ts.map +1 -0
- package/output/throttle/index.js +1 -0
- package/output/throttle/throttle.d.ts +12 -0
- package/output/throttle/throttle.d.ts.map +1 -0
- package/output/throttle/throttle.js +55 -0
- package/output/throttle/throttle.test.d.ts +2 -0
- package/output/throttle/throttle.test.d.ts.map +1 -0
- package/output/throttle/throttle.test.js +177 -0
- package/output/ttl-cache/index.d.ts +2 -0
- package/output/ttl-cache/index.d.ts.map +1 -0
- package/output/ttl-cache/index.js +1 -0
- package/output/ttl-cache/ttl-cache.d.ts +148 -0
- package/output/ttl-cache/ttl-cache.d.ts.map +1 -0
- package/output/ttl-cache/ttl-cache.js +313 -0
- package/output/ttl-cache/ttl-cache.test.d.ts +2 -0
- package/output/ttl-cache/ttl-cache.test.d.ts.map +1 -0
- package/output/ttl-cache/ttl-cache.test.js +222 -0
- package/package.json +1 -1
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { throttle, throttleFn } from './throttle.js';
|
|
3
|
+
describe('throttle', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
vi.useFakeTimers();
|
|
6
|
+
});
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
vi.restoreAllMocks();
|
|
9
|
+
vi.useRealTimers();
|
|
10
|
+
});
|
|
11
|
+
describe('throttleFn', () => {
|
|
12
|
+
it('应该立即执行第一次调用', () => {
|
|
13
|
+
const mockFn = vi.fn().mockReturnValue('result');
|
|
14
|
+
const throttledFn = throttleFn(mockFn, 100);
|
|
15
|
+
const result = throttledFn('arg1', 'arg2');
|
|
16
|
+
expect(mockFn).toHaveBeenCalledOnce();
|
|
17
|
+
expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
|
|
18
|
+
expect(result).toBe('result');
|
|
19
|
+
});
|
|
20
|
+
it('应该在间隔时间内忽略后续调用', () => {
|
|
21
|
+
const mockFn = vi.fn().mockReturnValue('result');
|
|
22
|
+
const throttledFn = throttleFn(mockFn, 100);
|
|
23
|
+
// 第一次调用应该立即执行
|
|
24
|
+
throttledFn('first');
|
|
25
|
+
expect(mockFn).toHaveBeenCalledOnce();
|
|
26
|
+
// 在间隔时间内的调用应该被忽略
|
|
27
|
+
throttledFn('second');
|
|
28
|
+
throttledFn('third');
|
|
29
|
+
expect(mockFn).toHaveBeenCalledOnce();
|
|
30
|
+
});
|
|
31
|
+
it('应该在间隔时间结束后允许新的调用', () => {
|
|
32
|
+
const mockFn = vi.fn().mockReturnValue('result');
|
|
33
|
+
const throttledFn = throttleFn(mockFn, 100);
|
|
34
|
+
// 第一次调用
|
|
35
|
+
throttledFn('first');
|
|
36
|
+
expect(mockFn).toHaveBeenCalledOnce();
|
|
37
|
+
// 推进时间超过间隔
|
|
38
|
+
vi.advanceTimersByTime(150);
|
|
39
|
+
// 现在应该允许新的调用
|
|
40
|
+
throttledFn('second');
|
|
41
|
+
expect(mockFn).toHaveBeenCalledTimes(2);
|
|
42
|
+
expect(mockFn).toHaveBeenLastCalledWith('second');
|
|
43
|
+
});
|
|
44
|
+
it('应该支持 leading: false 选项', () => {
|
|
45
|
+
const mockFn = vi.fn().mockReturnValue('result');
|
|
46
|
+
const throttledFn = throttleFn(mockFn, 100, { leading: false });
|
|
47
|
+
// 第一次调用不应该立即执行
|
|
48
|
+
throttledFn('first');
|
|
49
|
+
expect(mockFn).not.toHaveBeenCalled();
|
|
50
|
+
});
|
|
51
|
+
it('应该支持 trailing: true 选项', () => {
|
|
52
|
+
const mockFn = vi.fn().mockReturnValue('result');
|
|
53
|
+
const throttledFn = throttleFn(mockFn, 100, { trailing: true });
|
|
54
|
+
// 第一次调用
|
|
55
|
+
throttledFn('first');
|
|
56
|
+
expect(mockFn).toHaveBeenCalledOnce();
|
|
57
|
+
// 在间隔时间内的调用
|
|
58
|
+
throttledFn('second');
|
|
59
|
+
throttledFn('third');
|
|
60
|
+
// 推进时间,应该执行 trailing 调用
|
|
61
|
+
vi.advanceTimersByTime(100);
|
|
62
|
+
expect(mockFn).toHaveBeenCalledTimes(2);
|
|
63
|
+
expect(mockFn).toHaveBeenLastCalledWith('third');
|
|
64
|
+
});
|
|
65
|
+
it('应该支持 trailing: false 选项', () => {
|
|
66
|
+
const mockFn = vi.fn().mockReturnValue('result');
|
|
67
|
+
const throttledFn = throttleFn(mockFn, 100, { trailing: false });
|
|
68
|
+
// 第一次调用
|
|
69
|
+
throttledFn('first');
|
|
70
|
+
expect(mockFn).toHaveBeenCalledOnce();
|
|
71
|
+
// 在间隔时间内的调用
|
|
72
|
+
throttledFn('second');
|
|
73
|
+
throttledFn('third');
|
|
74
|
+
// 推进时间,不应该执行 trailing 调用
|
|
75
|
+
vi.advanceTimersByTime(100);
|
|
76
|
+
expect(mockFn).toHaveBeenCalledOnce();
|
|
77
|
+
});
|
|
78
|
+
it('应该保持 this 上下文', () => {
|
|
79
|
+
class TestClass {
|
|
80
|
+
value = 'test-value';
|
|
81
|
+
getValue() {
|
|
82
|
+
return this.value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const instance = new TestClass();
|
|
86
|
+
const throttledMethod = throttleFn(instance.getValue, 100);
|
|
87
|
+
const result = throttledMethod.call(instance);
|
|
88
|
+
expect(result).toBe('test-value');
|
|
89
|
+
});
|
|
90
|
+
it('应该返回最后一次执行的结果', () => {
|
|
91
|
+
const mockFn = vi.fn()
|
|
92
|
+
.mockReturnValueOnce('first')
|
|
93
|
+
.mockReturnValueOnce('second');
|
|
94
|
+
const throttledFn = throttleFn(mockFn, 100);
|
|
95
|
+
const result1 = throttledFn();
|
|
96
|
+
expect(result1).toBe('first');
|
|
97
|
+
// 在间隔时间内,应该返回缓存的结果
|
|
98
|
+
const result2 = throttledFn();
|
|
99
|
+
expect(result2).toBe('first');
|
|
100
|
+
// 推进时间后的新调用
|
|
101
|
+
vi.advanceTimersByTime(150);
|
|
102
|
+
const result3 = throttledFn();
|
|
103
|
+
expect(result3).toBe('second');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('throttle decorator', () => {
|
|
107
|
+
it('应该装饰类方法', () => {
|
|
108
|
+
class TestClass {
|
|
109
|
+
callCount = 0;
|
|
110
|
+
@throttle(100)
|
|
111
|
+
increment() {
|
|
112
|
+
this.callCount++;
|
|
113
|
+
return this.callCount;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const instance = new TestClass();
|
|
117
|
+
// 第一次调用应该立即执行
|
|
118
|
+
const result1 = instance.increment();
|
|
119
|
+
expect(result1).toBe(1);
|
|
120
|
+
expect(instance.callCount).toBe(1);
|
|
121
|
+
// 快速连续调用应该被节流
|
|
122
|
+
instance.increment();
|
|
123
|
+
instance.increment();
|
|
124
|
+
expect(instance.callCount).toBe(1);
|
|
125
|
+
});
|
|
126
|
+
it('应该为每个实例独立工作', () => {
|
|
127
|
+
class TestClass {
|
|
128
|
+
callCount = 0;
|
|
129
|
+
@throttle(100)
|
|
130
|
+
increment() {
|
|
131
|
+
this.callCount++;
|
|
132
|
+
return this.callCount;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const instance1 = new TestClass();
|
|
136
|
+
const instance2 = new TestClass();
|
|
137
|
+
instance1.increment();
|
|
138
|
+
instance2.increment();
|
|
139
|
+
expect(instance1.callCount).toBe(1);
|
|
140
|
+
expect(instance2.callCount).toBe(1);
|
|
141
|
+
});
|
|
142
|
+
it('应该支持带选项的装饰器', () => {
|
|
143
|
+
class TestClass {
|
|
144
|
+
callCount = 0;
|
|
145
|
+
@throttle(100, { leading: false, trailing: true })
|
|
146
|
+
increment() {
|
|
147
|
+
this.callCount++;
|
|
148
|
+
return this.callCount;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const instance = new TestClass();
|
|
152
|
+
// leading: false,第一次调用不应该立即执行
|
|
153
|
+
instance.increment();
|
|
154
|
+
expect(instance.callCount).toBe(0);
|
|
155
|
+
// trailing: true,应该在延迟后执行
|
|
156
|
+
vi.advanceTimersByTime(100);
|
|
157
|
+
expect(instance.callCount).toBe(1);
|
|
158
|
+
});
|
|
159
|
+
it('应该处理带参数的方法', () => {
|
|
160
|
+
class TestClass {
|
|
161
|
+
lastArgs = [];
|
|
162
|
+
@throttle(100)
|
|
163
|
+
saveArgs(...args) {
|
|
164
|
+
this.lastArgs = args;
|
|
165
|
+
return args.length;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const instance = new TestClass();
|
|
169
|
+
const result = instance.saveArgs(1, 2, 3);
|
|
170
|
+
expect(result).toBe(3);
|
|
171
|
+
expect(instance.lastArgs).toEqual([1, 2, 3]);
|
|
172
|
+
// 在间隔时间内的调用应该被忽略
|
|
173
|
+
instance.saveArgs(4, 5, 6, 7);
|
|
174
|
+
expect(instance.lastArgs).toEqual([1, 2, 3]); // 应该保持原值
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/ttl-cache/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TtlCache } from './ttl-cache.js';
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TTL 缓存配置选项
|
|
3
|
+
*/
|
|
4
|
+
export interface TtlCacheOptions {
|
|
5
|
+
/** 默认 TTL 时间(毫秒),默认为 5 分钟 */
|
|
6
|
+
defaultTtl?: number;
|
|
7
|
+
/** 清理间隔(毫秒),默认为 1 分钟 */
|
|
8
|
+
cleanupInterval?: number;
|
|
9
|
+
/** 最大缓存条目数量,默认无限制 */
|
|
10
|
+
maxSize?: number;
|
|
11
|
+
/** 是否启用自动清理,默认为 true */
|
|
12
|
+
enableCleanup?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* TTL 缓存统计信息
|
|
16
|
+
*/
|
|
17
|
+
export interface TtlCacheStats {
|
|
18
|
+
/** 当前条目数量 */
|
|
19
|
+
size: number;
|
|
20
|
+
/** 最大条目数量限制 */
|
|
21
|
+
maxSize?: number;
|
|
22
|
+
/** 命中次数 */
|
|
23
|
+
hits: number;
|
|
24
|
+
/** 未命中次数 */
|
|
25
|
+
misses: number;
|
|
26
|
+
/** 过期次数 */
|
|
27
|
+
expiredCount: number;
|
|
28
|
+
/** 命中率 */
|
|
29
|
+
hitRate: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* TTL(Time To Live)缓存实现
|
|
33
|
+
* 支持过期时间、自动清理、统计信息等功能
|
|
34
|
+
*
|
|
35
|
+
* 特点:
|
|
36
|
+
* - 支持为每个条目设置独立的 TTL
|
|
37
|
+
* - 自动定期清理过期条目
|
|
38
|
+
* - 支持最大容量限制
|
|
39
|
+
* - 提供详细的统计信息
|
|
40
|
+
* - 优雅的资源清理机制
|
|
41
|
+
*/
|
|
42
|
+
export declare class TtlCache<T> {
|
|
43
|
+
private cache;
|
|
44
|
+
private cleanupTimer?;
|
|
45
|
+
private readonly options;
|
|
46
|
+
private stats;
|
|
47
|
+
constructor(options?: TtlCacheOptions);
|
|
48
|
+
/**
|
|
49
|
+
* 设置缓存项
|
|
50
|
+
* @param key 缓存键
|
|
51
|
+
* @param value 缓存值
|
|
52
|
+
* @param ttl 过期时间(毫秒),默认使用构造函数中的值
|
|
53
|
+
*/
|
|
54
|
+
set(key: string, value: T, ttl?: number): void;
|
|
55
|
+
/**
|
|
56
|
+
* 获取缓存项
|
|
57
|
+
* @param key 缓存键
|
|
58
|
+
* @returns 缓存值或 undefined
|
|
59
|
+
*/
|
|
60
|
+
get(key: string): T | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* 检查缓存项是否存在且未过期
|
|
63
|
+
* @param key 缓存键
|
|
64
|
+
*/
|
|
65
|
+
has(key: string): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* 删除缓存项
|
|
68
|
+
* @param key 缓存键
|
|
69
|
+
* @returns 是否成功删除
|
|
70
|
+
*/
|
|
71
|
+
delete(key: string): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* 清空所有缓存
|
|
74
|
+
*/
|
|
75
|
+
clear(): void;
|
|
76
|
+
/**
|
|
77
|
+
* 获取当前缓存大小
|
|
78
|
+
*/
|
|
79
|
+
get size(): number;
|
|
80
|
+
/**
|
|
81
|
+
* 获取缓存容量
|
|
82
|
+
*/
|
|
83
|
+
get capacity(): number;
|
|
84
|
+
/**
|
|
85
|
+
* 获取所有未过期的键
|
|
86
|
+
*/
|
|
87
|
+
keys(): string[];
|
|
88
|
+
/**
|
|
89
|
+
* 获取所有未过期的值
|
|
90
|
+
*/
|
|
91
|
+
values(): T[];
|
|
92
|
+
/**
|
|
93
|
+
* 获取所有未过期的键值对
|
|
94
|
+
*/
|
|
95
|
+
entries(): Array<[string, T]>;
|
|
96
|
+
/**
|
|
97
|
+
* 获取或设置缓存(如果不存在)
|
|
98
|
+
* @param key 缓存键
|
|
99
|
+
* @param factory 工厂函数,用于生成缓存值
|
|
100
|
+
* @param ttl 过期时间(毫秒)
|
|
101
|
+
*/
|
|
102
|
+
getOrSet(key: string, factory: () => Promise<T>, ttl?: number): Promise<T>;
|
|
103
|
+
getOrSet(key: string, factory: () => T, ttl?: number): T;
|
|
104
|
+
/**
|
|
105
|
+
* 获取缓存统计信息
|
|
106
|
+
*/
|
|
107
|
+
getStats(): TtlCacheStats;
|
|
108
|
+
/**
|
|
109
|
+
* 重置统计信息
|
|
110
|
+
*/
|
|
111
|
+
resetStats(): void;
|
|
112
|
+
/**
|
|
113
|
+
* 手动清理过期缓存
|
|
114
|
+
* @returns 清理的条目数量
|
|
115
|
+
*/
|
|
116
|
+
cleanup(): number;
|
|
117
|
+
/**
|
|
118
|
+
* 获取指定键的剩余 TTL 时间
|
|
119
|
+
* @param key 缓存键
|
|
120
|
+
* @returns 剩余时间(毫秒),如果不存在或已过期返回 -1
|
|
121
|
+
*/
|
|
122
|
+
getTtl(key: string): number;
|
|
123
|
+
/**
|
|
124
|
+
* 销毁缓存实例,清理所有资源
|
|
125
|
+
*/
|
|
126
|
+
destroy(): void;
|
|
127
|
+
/**
|
|
128
|
+
* 启动定期清理过期缓存
|
|
129
|
+
*/
|
|
130
|
+
private startCleanup;
|
|
131
|
+
/**
|
|
132
|
+
* 停止定期清理
|
|
133
|
+
*/
|
|
134
|
+
private stopCleanup;
|
|
135
|
+
/**
|
|
136
|
+
* 驱逐最旧的缓存条目
|
|
137
|
+
*/
|
|
138
|
+
private evictOldest;
|
|
139
|
+
/**
|
|
140
|
+
* 更新统计信息
|
|
141
|
+
*/
|
|
142
|
+
private updateStats;
|
|
143
|
+
/**
|
|
144
|
+
* 更新命中率
|
|
145
|
+
*/
|
|
146
|
+
private updateHitRate;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=ttl-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ttl-cache.d.ts","sourceRoot":"","sources":["../../source/ttl-cache/ttl-cache.ts"],"names":[],"mappings":"AAYA;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,6BAA6B;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,wBAAwB;IACxB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,qBAAqB;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,wBAAwB;IACxB,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,aAAa;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,eAAe;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,WAAW;IACX,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU;IACV,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,QAAQ,CAAC,CAAC;IACrB,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,YAAY,CAAC,CAAK;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2B;IACnD,OAAO,CAAC,KAAK,CAAe;gBAEhB,OAAO,GAAE,eAAoB;IAiCzC;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI;IA0B9C;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAuB/B;;;OAGG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAiBzB;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAQ5B;;OAEG;IACH,KAAK,IAAI,IAAI;IAMb;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED;;OAEG;IACH,IAAI,IAAI,MAAM,EAAE;IAahB;;OAEG;IACH,MAAM,IAAI,CAAC,EAAE;IAab;;OAEG;IACH,OAAO,IAAI,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAa7B;;;;;OAKG;IACG,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAChF,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,CAAC;IAoBxD;;OAEG;IACH,QAAQ,IAAI,aAAa;IAKzB;;OAEG;IACH,UAAU,IAAI,IAAI;IAWlB;;;OAGG;IACH,OAAO,IAAI,MAAM;IAmBjB;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAc3B;;OAEG;IACH,OAAO,IAAI,IAAI;IAKf;;OAEG;IACH,OAAO,CAAC,YAAY;IAUpB;;OAEG;IACH,OAAO,CAAC,WAAW;IAOnB;;OAEG;IACH,OAAO,CAAC,WAAW;IAgBnB;;OAEG;IACH,OAAO,CAAC,WAAW;IAKnB;;OAEG;IACH,OAAO,CAAC,aAAa;CAItB"}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TTL(Time To Live)缓存实现
|
|
3
|
+
* 支持过期时间、自动清理、统计信息等功能
|
|
4
|
+
*
|
|
5
|
+
* 特点:
|
|
6
|
+
* - 支持为每个条目设置独立的 TTL
|
|
7
|
+
* - 自动定期清理过期条目
|
|
8
|
+
* - 支持最大容量限制
|
|
9
|
+
* - 提供详细的统计信息
|
|
10
|
+
* - 优雅的资源清理机制
|
|
11
|
+
*/
|
|
12
|
+
export class TtlCache {
|
|
13
|
+
cache;
|
|
14
|
+
cleanupTimer;
|
|
15
|
+
options;
|
|
16
|
+
stats;
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.options = {
|
|
19
|
+
defaultTtl: options.defaultTtl ?? 5 * 60 * 1000, // 默认5分钟
|
|
20
|
+
cleanupInterval: options.cleanupInterval ?? 60 * 1000, // 默认1分钟清理一次
|
|
21
|
+
maxSize: options.maxSize ?? Infinity,
|
|
22
|
+
enableCleanup: options.enableCleanup ?? true
|
|
23
|
+
};
|
|
24
|
+
if (this.options.defaultTtl <= 0) {
|
|
25
|
+
throw new Error('defaultTtl must be greater than 0');
|
|
26
|
+
}
|
|
27
|
+
if (this.options.cleanupInterval <= 0) {
|
|
28
|
+
throw new Error('cleanupInterval must be greater than 0');
|
|
29
|
+
}
|
|
30
|
+
if (this.options.maxSize <= 0) {
|
|
31
|
+
throw new Error('maxSize must be greater than 0');
|
|
32
|
+
}
|
|
33
|
+
this.cache = new Map();
|
|
34
|
+
this.stats = {
|
|
35
|
+
size: 0,
|
|
36
|
+
maxSize: this.options.maxSize === Infinity ? undefined : this.options.maxSize,
|
|
37
|
+
hits: 0,
|
|
38
|
+
misses: 0,
|
|
39
|
+
expiredCount: 0,
|
|
40
|
+
hitRate: 0
|
|
41
|
+
};
|
|
42
|
+
if (this.options.enableCleanup) {
|
|
43
|
+
this.startCleanup();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 设置缓存项
|
|
48
|
+
* @param key 缓存键
|
|
49
|
+
* @param value 缓存值
|
|
50
|
+
* @param ttl 过期时间(毫秒),默认使用构造函数中的值
|
|
51
|
+
*/
|
|
52
|
+
set(key, value, ttl) {
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
const actualTtl = ttl ?? this.options.defaultTtl;
|
|
55
|
+
const expireAt = now + actualTtl;
|
|
56
|
+
// 检查容量限制
|
|
57
|
+
if (!this.cache.has(key) && this.cache.size >= this.options.maxSize) {
|
|
58
|
+
// 尝试清理过期条目
|
|
59
|
+
this.cleanup();
|
|
60
|
+
// 如果清理后仍然超过容量,删除最旧的条目
|
|
61
|
+
if (this.cache.size >= this.options.maxSize) {
|
|
62
|
+
this.evictOldest();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const entry = {
|
|
66
|
+
value,
|
|
67
|
+
expireAt,
|
|
68
|
+
createdAt: now
|
|
69
|
+
};
|
|
70
|
+
this.cache.set(key, entry);
|
|
71
|
+
this.updateStats();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 获取缓存项
|
|
75
|
+
* @param key 缓存键
|
|
76
|
+
* @returns 缓存值或 undefined
|
|
77
|
+
*/
|
|
78
|
+
get(key) {
|
|
79
|
+
const entry = this.cache.get(key);
|
|
80
|
+
if (!entry) {
|
|
81
|
+
this.stats.misses++;
|
|
82
|
+
this.updateHitRate();
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
const now = Date.now();
|
|
86
|
+
if (now > entry.expireAt) {
|
|
87
|
+
this.cache.delete(key);
|
|
88
|
+
this.stats.expiredCount++;
|
|
89
|
+
this.stats.misses++;
|
|
90
|
+
this.updateStats();
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
this.stats.hits++;
|
|
94
|
+
this.updateHitRate();
|
|
95
|
+
return entry.value;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 检查缓存项是否存在且未过期
|
|
99
|
+
* @param key 缓存键
|
|
100
|
+
*/
|
|
101
|
+
has(key) {
|
|
102
|
+
const entry = this.cache.get(key);
|
|
103
|
+
if (!entry) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
if (now > entry.expireAt) {
|
|
108
|
+
this.cache.delete(key);
|
|
109
|
+
this.stats.expiredCount++;
|
|
110
|
+
this.updateStats();
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 删除缓存项
|
|
117
|
+
* @param key 缓存键
|
|
118
|
+
* @returns 是否成功删除
|
|
119
|
+
*/
|
|
120
|
+
delete(key) {
|
|
121
|
+
const result = this.cache.delete(key);
|
|
122
|
+
if (result) {
|
|
123
|
+
this.updateStats();
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 清空所有缓存
|
|
129
|
+
*/
|
|
130
|
+
clear() {
|
|
131
|
+
this.cache.clear();
|
|
132
|
+
this.stats.expiredCount = 0;
|
|
133
|
+
this.updateStats();
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 获取当前缓存大小
|
|
137
|
+
*/
|
|
138
|
+
get size() {
|
|
139
|
+
return this.cache.size;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 获取缓存容量
|
|
143
|
+
*/
|
|
144
|
+
get capacity() {
|
|
145
|
+
return this.options.maxSize;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 获取所有未过期的键
|
|
149
|
+
*/
|
|
150
|
+
keys() {
|
|
151
|
+
const keys = [];
|
|
152
|
+
const now = Date.now();
|
|
153
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
154
|
+
if (now <= entry.expireAt) {
|
|
155
|
+
keys.push(key);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return keys;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* 获取所有未过期的值
|
|
162
|
+
*/
|
|
163
|
+
values() {
|
|
164
|
+
const values = [];
|
|
165
|
+
const now = Date.now();
|
|
166
|
+
for (const entry of this.cache.values()) {
|
|
167
|
+
if (now <= entry.expireAt) {
|
|
168
|
+
values.push(entry.value);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return values;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* 获取所有未过期的键值对
|
|
175
|
+
*/
|
|
176
|
+
entries() {
|
|
177
|
+
const entries = [];
|
|
178
|
+
const now = Date.now();
|
|
179
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
180
|
+
if (now <= entry.expireAt) {
|
|
181
|
+
entries.push([key, entry.value]);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return entries;
|
|
185
|
+
}
|
|
186
|
+
getOrSet(key, factory, ttl) {
|
|
187
|
+
const cached = this.get(key);
|
|
188
|
+
if (cached !== undefined) {
|
|
189
|
+
return cached;
|
|
190
|
+
}
|
|
191
|
+
const result = factory();
|
|
192
|
+
if (result instanceof Promise) {
|
|
193
|
+
return result.then(value => {
|
|
194
|
+
this.set(key, value, ttl);
|
|
195
|
+
return value;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
this.set(key, result, ttl);
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* 获取缓存统计信息
|
|
203
|
+
*/
|
|
204
|
+
getStats() {
|
|
205
|
+
this.updateStats();
|
|
206
|
+
return { ...this.stats };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* 重置统计信息
|
|
210
|
+
*/
|
|
211
|
+
resetStats() {
|
|
212
|
+
this.stats = {
|
|
213
|
+
size: this.cache.size,
|
|
214
|
+
maxSize: this.options.maxSize === Infinity ? undefined : this.options.maxSize,
|
|
215
|
+
hits: 0,
|
|
216
|
+
misses: 0,
|
|
217
|
+
expiredCount: 0,
|
|
218
|
+
hitRate: 0
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* 手动清理过期缓存
|
|
223
|
+
* @returns 清理的条目数量
|
|
224
|
+
*/
|
|
225
|
+
cleanup() {
|
|
226
|
+
const now = Date.now();
|
|
227
|
+
let expiredCount = 0;
|
|
228
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
229
|
+
if (now > entry.expireAt) {
|
|
230
|
+
this.cache.delete(key);
|
|
231
|
+
expiredCount++;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (expiredCount > 0) {
|
|
235
|
+
this.stats.expiredCount += expiredCount;
|
|
236
|
+
this.updateStats();
|
|
237
|
+
}
|
|
238
|
+
return expiredCount;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* 获取指定键的剩余 TTL 时间
|
|
242
|
+
* @param key 缓存键
|
|
243
|
+
* @returns 剩余时间(毫秒),如果不存在或已过期返回 -1
|
|
244
|
+
*/
|
|
245
|
+
getTtl(key) {
|
|
246
|
+
const entry = this.cache.get(key);
|
|
247
|
+
if (!entry) {
|
|
248
|
+
return -1;
|
|
249
|
+
}
|
|
250
|
+
const now = Date.now();
|
|
251
|
+
if (now > entry.expireAt) {
|
|
252
|
+
return -1;
|
|
253
|
+
}
|
|
254
|
+
return entry.expireAt - now;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* 销毁缓存实例,清理所有资源
|
|
258
|
+
*/
|
|
259
|
+
destroy() {
|
|
260
|
+
this.clear();
|
|
261
|
+
this.stopCleanup();
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* 启动定期清理过期缓存
|
|
265
|
+
*/
|
|
266
|
+
startCleanup() {
|
|
267
|
+
if (this.cleanupTimer) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
this.cleanupTimer = setInterval(() => {
|
|
271
|
+
this.cleanup();
|
|
272
|
+
}, this.options.cleanupInterval);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* 停止定期清理
|
|
276
|
+
*/
|
|
277
|
+
stopCleanup() {
|
|
278
|
+
if (this.cleanupTimer) {
|
|
279
|
+
clearInterval(this.cleanupTimer);
|
|
280
|
+
this.cleanupTimer = undefined;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* 驱逐最旧的缓存条目
|
|
285
|
+
*/
|
|
286
|
+
evictOldest() {
|
|
287
|
+
let oldestKey;
|
|
288
|
+
let oldestTime = Infinity;
|
|
289
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
290
|
+
if (entry.createdAt < oldestTime) {
|
|
291
|
+
oldestTime = entry.createdAt;
|
|
292
|
+
oldestKey = key;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (oldestKey) {
|
|
296
|
+
this.cache.delete(oldestKey);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* 更新统计信息
|
|
301
|
+
*/
|
|
302
|
+
updateStats() {
|
|
303
|
+
this.stats.size = this.cache.size;
|
|
304
|
+
this.updateHitRate();
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* 更新命中率
|
|
308
|
+
*/
|
|
309
|
+
updateHitRate() {
|
|
310
|
+
const total = this.stats.hits + this.stats.misses;
|
|
311
|
+
this.stats.hitRate = total > 0 ? this.stats.hits / total : 0;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ttl-cache.test.d.ts","sourceRoot":"","sources":["../../source/ttl-cache/ttl-cache.test.ts"],"names":[],"mappings":""}
|