@taicode/common-base 1.2.0 → 1.4.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/output/error/error.d.ts +59 -9
- package/output/error/error.d.ts.map +1 -1
- package/output/error/error.js +50 -6
- package/output/error/index.d.ts +1 -1
- package/output/error/index.d.ts.map +1 -1
- package/output/logger/formatter.d.ts +71 -0
- package/output/logger/formatter.d.ts.map +1 -0
- package/output/logger/formatter.js +222 -0
- package/output/logger/formatter.test.d.ts +26 -0
- package/output/logger/formatter.test.d.ts.map +1 -0
- package/output/logger/formatter.test.js +315 -0
- package/output/logger/index.d.ts +3 -17
- package/output/logger/index.d.ts.map +1 -1
- package/output/logger/index.js +3 -34
- package/output/logger/logger.d.ts +51 -0
- package/output/logger/logger.d.ts.map +1 -0
- package/output/logger/logger.js +118 -0
- package/output/logger/logger.test.d.ts +19 -0
- package/output/logger/logger.test.d.ts.map +1 -1
- package/output/logger/logger.test.js +201 -15
- 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/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 +195 -0
- package/package.json +1 -1
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { MemoryTransport } from './transport';
|
|
3
|
+
// 创建一个测试用的 Transport 实现
|
|
4
|
+
class TestTransport {
|
|
5
|
+
name;
|
|
6
|
+
level;
|
|
7
|
+
enabled = true;
|
|
8
|
+
loggedRecords = [];
|
|
9
|
+
static LOG_LEVELS = ['debug', 'info', 'warn', 'error'];
|
|
10
|
+
constructor(name = 'test', options = {}) {
|
|
11
|
+
this.name = name;
|
|
12
|
+
this.level = options.level;
|
|
13
|
+
this.enabled = options.enabled ?? true;
|
|
14
|
+
}
|
|
15
|
+
shouldLog(level) {
|
|
16
|
+
if (!this.enabled) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
if (!this.level) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
const currentIndex = TestTransport.LOG_LEVELS.indexOf(level);
|
|
23
|
+
const minIndex = TestTransport.LOG_LEVELS.indexOf(this.level);
|
|
24
|
+
return currentIndex >= minIndex;
|
|
25
|
+
}
|
|
26
|
+
log(record) {
|
|
27
|
+
if (!this.shouldLog(record.level)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this.loggedRecords.push(record);
|
|
31
|
+
}
|
|
32
|
+
clear() {
|
|
33
|
+
this.loggedRecords = [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
describe('TestTransport', () => {
|
|
37
|
+
let transport;
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
transport = new TestTransport();
|
|
40
|
+
});
|
|
41
|
+
describe('基本功能', () => {
|
|
42
|
+
it('应该有正确的名称', () => {
|
|
43
|
+
expect(transport.name).toBe('test');
|
|
44
|
+
});
|
|
45
|
+
it('应该默认启用', () => {
|
|
46
|
+
expect(transport.enabled).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
it('应该记录日志', () => {
|
|
49
|
+
const record = {
|
|
50
|
+
level: 'info',
|
|
51
|
+
timestamp: new Date(),
|
|
52
|
+
args: ['测试消息']
|
|
53
|
+
};
|
|
54
|
+
transport.log(record);
|
|
55
|
+
expect(transport.loggedRecords).toHaveLength(1);
|
|
56
|
+
expect(transport.loggedRecords[0]).toEqual(record);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe('日志级别控制', () => {
|
|
60
|
+
it('应该遵守最低日志级别', () => {
|
|
61
|
+
transport = new TestTransport('test', { level: 'warn' });
|
|
62
|
+
const debugRecord = {
|
|
63
|
+
level: 'debug',
|
|
64
|
+
timestamp: new Date(),
|
|
65
|
+
args: ['debug消息']
|
|
66
|
+
};
|
|
67
|
+
const warnRecord = {
|
|
68
|
+
level: 'warn',
|
|
69
|
+
timestamp: new Date(),
|
|
70
|
+
args: ['warn消息']
|
|
71
|
+
};
|
|
72
|
+
transport.log(debugRecord);
|
|
73
|
+
transport.log(warnRecord);
|
|
74
|
+
expect(transport.loggedRecords).toHaveLength(1);
|
|
75
|
+
expect(transport.loggedRecords[0].level).toBe('warn');
|
|
76
|
+
});
|
|
77
|
+
it('应该记录等于或高于最低级别的日志', () => {
|
|
78
|
+
transport = new TestTransport('test', { level: 'info' });
|
|
79
|
+
const levels = ['debug', 'info', 'warn', 'error'];
|
|
80
|
+
levels.forEach(level => {
|
|
81
|
+
transport.log({
|
|
82
|
+
level,
|
|
83
|
+
timestamp: new Date(),
|
|
84
|
+
args: [`${level}消息`]
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
expect(transport.loggedRecords).toHaveLength(3); // info, warn, error
|
|
88
|
+
expect(transport.loggedRecords.map(r => r.level)).toEqual(['info', 'warn', 'error']);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe('启用状态控制', () => {
|
|
92
|
+
it('应该在禁用时不记录日志', () => {
|
|
93
|
+
transport = new TestTransport('test', { enabled: false });
|
|
94
|
+
transport.log({
|
|
95
|
+
level: 'error',
|
|
96
|
+
timestamp: new Date(),
|
|
97
|
+
args: ['错误消息']
|
|
98
|
+
});
|
|
99
|
+
expect(transport.loggedRecords).toHaveLength(0);
|
|
100
|
+
});
|
|
101
|
+
it('应该支持动态启用/禁用', () => {
|
|
102
|
+
const record = {
|
|
103
|
+
level: 'info',
|
|
104
|
+
timestamp: new Date(),
|
|
105
|
+
args: ['测试消息']
|
|
106
|
+
};
|
|
107
|
+
transport.enabled = false;
|
|
108
|
+
transport.log(record);
|
|
109
|
+
expect(transport.loggedRecords).toHaveLength(0);
|
|
110
|
+
transport.enabled = true;
|
|
111
|
+
transport.log(record);
|
|
112
|
+
expect(transport.loggedRecords).toHaveLength(1);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
describe('MemoryTransport', () => {
|
|
117
|
+
let memoryTransport;
|
|
118
|
+
beforeEach(() => {
|
|
119
|
+
memoryTransport = new MemoryTransport();
|
|
120
|
+
});
|
|
121
|
+
describe('基本功能', () => {
|
|
122
|
+
it('应该有正确的名称', () => {
|
|
123
|
+
expect(memoryTransport.name).toBe('memory');
|
|
124
|
+
});
|
|
125
|
+
it('应该存储日志记录', () => {
|
|
126
|
+
const record = {
|
|
127
|
+
level: 'info',
|
|
128
|
+
timestamp: new Date(),
|
|
129
|
+
args: ['测试消息', { data: 'value' }],
|
|
130
|
+
metadata: { source: 'test' }
|
|
131
|
+
};
|
|
132
|
+
memoryTransport.log(record);
|
|
133
|
+
const records = memoryTransport.getRecords();
|
|
134
|
+
expect(records).toHaveLength(1);
|
|
135
|
+
expect(records[0].level).toBe('info');
|
|
136
|
+
expect(records[0].args).toEqual(['测试消息', { data: 'value' }]);
|
|
137
|
+
expect(records[0].metadata).toEqual({ source: 'test' });
|
|
138
|
+
expect(records[0].id).toBeDefined();
|
|
139
|
+
});
|
|
140
|
+
it('应该生成唯一ID', () => {
|
|
141
|
+
const record1 = {
|
|
142
|
+
level: 'info',
|
|
143
|
+
timestamp: new Date(),
|
|
144
|
+
args: ['消息1']
|
|
145
|
+
};
|
|
146
|
+
const record2 = {
|
|
147
|
+
level: 'info',
|
|
148
|
+
timestamp: new Date(),
|
|
149
|
+
args: ['消息2']
|
|
150
|
+
};
|
|
151
|
+
memoryTransport.log(record1);
|
|
152
|
+
memoryTransport.log(record2);
|
|
153
|
+
const records = memoryTransport.getRecords();
|
|
154
|
+
expect(records[0].id).not.toBe(records[1].id);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
describe('容量管理', () => {
|
|
158
|
+
it('应该遵守最大记录数限制', () => {
|
|
159
|
+
memoryTransport = new MemoryTransport({ maxRecords: 3 });
|
|
160
|
+
// 添加4条记录
|
|
161
|
+
for (let i = 0; i < 4; i++) {
|
|
162
|
+
memoryTransport.log({
|
|
163
|
+
level: 'info',
|
|
164
|
+
timestamp: new Date(),
|
|
165
|
+
args: [`消息${i}`]
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const records = memoryTransport.getRecords();
|
|
169
|
+
expect(records).toHaveLength(3);
|
|
170
|
+
// 应该保留最新的3条记录
|
|
171
|
+
expect(records.map(r => r.args[0])).toEqual(['消息1', '消息2', '消息3']);
|
|
172
|
+
});
|
|
173
|
+
it('应该支持自定义最大记录数', () => {
|
|
174
|
+
memoryTransport = new MemoryTransport({ maxRecords: 1 });
|
|
175
|
+
memoryTransport.log({
|
|
176
|
+
level: 'info',
|
|
177
|
+
timestamp: new Date(),
|
|
178
|
+
args: ['第一条']
|
|
179
|
+
});
|
|
180
|
+
memoryTransport.log({
|
|
181
|
+
level: 'info',
|
|
182
|
+
timestamp: new Date(),
|
|
183
|
+
args: ['第二条']
|
|
184
|
+
});
|
|
185
|
+
const records = memoryTransport.getRecords();
|
|
186
|
+
expect(records).toHaveLength(1);
|
|
187
|
+
expect(records[0].args[0]).toBe('第二条');
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
describe('查询功能', () => {
|
|
191
|
+
beforeEach(() => {
|
|
192
|
+
// 添加测试数据
|
|
193
|
+
const testData = [
|
|
194
|
+
{ level: 'debug', args: ['调试消息'] },
|
|
195
|
+
{ level: 'info', args: ['信息消息', { type: 'user' }] },
|
|
196
|
+
{ level: 'warn', args: ['警告消息'] },
|
|
197
|
+
{ level: 'error', args: ['错误消息', new Error('测试错误')] },
|
|
198
|
+
{ level: 'info', args: ['另一条信息'] }
|
|
199
|
+
];
|
|
200
|
+
testData.forEach(data => {
|
|
201
|
+
memoryTransport.log({
|
|
202
|
+
level: data.level,
|
|
203
|
+
timestamp: new Date(),
|
|
204
|
+
args: data.args
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
it('应该根据级别筛选记录', () => {
|
|
209
|
+
const infoRecords = memoryTransport.getRecordsByLevel('info');
|
|
210
|
+
expect(infoRecords).toHaveLength(2);
|
|
211
|
+
expect(infoRecords.every(r => r.level === 'info')).toBe(true);
|
|
212
|
+
const errorRecords = memoryTransport.getRecordsByLevel('error');
|
|
213
|
+
expect(errorRecords).toHaveLength(1);
|
|
214
|
+
expect(errorRecords[0].level).toBe('error');
|
|
215
|
+
});
|
|
216
|
+
it('应该根据时间范围筛选记录', () => {
|
|
217
|
+
const now = new Date();
|
|
218
|
+
const oneMinuteAgo = new Date(now.getTime() - 60000);
|
|
219
|
+
const oneMinuteLater = new Date(now.getTime() + 60000);
|
|
220
|
+
const records = memoryTransport.getRecordsByTimeRange(oneMinuteAgo, oneMinuteLater);
|
|
221
|
+
expect(records.length).toBeGreaterThan(0);
|
|
222
|
+
});
|
|
223
|
+
it('应该支持内容搜索', () => {
|
|
224
|
+
const results = memoryTransport.searchByContent('调试');
|
|
225
|
+
expect(results).toHaveLength(1);
|
|
226
|
+
expect(results[0].args[0]).toContain('调试');
|
|
227
|
+
const typeResults = memoryTransport.searchByContent('user');
|
|
228
|
+
expect(typeResults).toHaveLength(1);
|
|
229
|
+
expect(typeResults[0].args[1]).toEqual({ type: 'user' });
|
|
230
|
+
});
|
|
231
|
+
it('应该支持自定义搜索条件', () => {
|
|
232
|
+
const errorResults = memoryTransport.searchRecords(record => record.args.some(arg => arg instanceof Error));
|
|
233
|
+
expect(errorResults).toHaveLength(1);
|
|
234
|
+
expect(errorResults[0].level).toBe('error');
|
|
235
|
+
});
|
|
236
|
+
it('应该获取最近的记录', () => {
|
|
237
|
+
const recent = memoryTransport.getRecentRecords(2);
|
|
238
|
+
expect(recent).toHaveLength(2);
|
|
239
|
+
expect(recent[0].args[0]).toContain('错误消息');
|
|
240
|
+
expect(recent[1].args[0]).toContain('另一条信息');
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
describe('统计功能', () => {
|
|
244
|
+
beforeEach(() => {
|
|
245
|
+
// 添加不同级别的日志
|
|
246
|
+
memoryTransport.log({ level: 'debug', timestamp: new Date(), args: ['debug1'] });
|
|
247
|
+
memoryTransport.log({ level: 'debug', timestamp: new Date(), args: ['debug2'] });
|
|
248
|
+
memoryTransport.log({ level: 'info', timestamp: new Date(), args: ['info1'] });
|
|
249
|
+
memoryTransport.log({ level: 'warn', timestamp: new Date(), args: ['warn1'] });
|
|
250
|
+
memoryTransport.log({ level: 'error', timestamp: new Date(), args: ['error1'] });
|
|
251
|
+
});
|
|
252
|
+
it('应该提供正确的统计信息', () => {
|
|
253
|
+
const stats = memoryTransport.getStats();
|
|
254
|
+
expect(stats.total).toBe(5);
|
|
255
|
+
expect(stats.byLevel.debug).toBe(2);
|
|
256
|
+
expect(stats.byLevel.info).toBe(1);
|
|
257
|
+
expect(stats.byLevel.warn).toBe(1);
|
|
258
|
+
expect(stats.byLevel.error).toBe(1);
|
|
259
|
+
expect(stats.oldestTimestamp).toBeDefined();
|
|
260
|
+
expect(stats.newestTimestamp).toBeDefined();
|
|
261
|
+
});
|
|
262
|
+
it('应该处理空记录的统计', () => {
|
|
263
|
+
memoryTransport.clear();
|
|
264
|
+
const stats = memoryTransport.getStats();
|
|
265
|
+
expect(stats.total).toBe(0);
|
|
266
|
+
expect(stats.byLevel.debug).toBe(0);
|
|
267
|
+
expect(stats.oldestTimestamp).toBeUndefined();
|
|
268
|
+
expect(stats.newestTimestamp).toBeUndefined();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
describe('导出功能', () => {
|
|
272
|
+
beforeEach(() => {
|
|
273
|
+
memoryTransport.log({
|
|
274
|
+
level: 'info',
|
|
275
|
+
timestamp: new Date('2023-01-01T00:00:00.000Z'),
|
|
276
|
+
args: ['测试消息', { data: 'value' }]
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
it('应该导出为 JSON 格式', () => {
|
|
280
|
+
const json = memoryTransport.exportToJSON();
|
|
281
|
+
const parsed = JSON.parse(json);
|
|
282
|
+
expect(Array.isArray(parsed)).toBe(true);
|
|
283
|
+
expect(parsed).toHaveLength(1);
|
|
284
|
+
expect(parsed[0].level).toBe('info');
|
|
285
|
+
expect(parsed[0].args).toEqual(['测试消息', { data: 'value' }]);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
describe('清理功能', () => {
|
|
289
|
+
it('应该清空所有记录', () => {
|
|
290
|
+
memoryTransport.log({
|
|
291
|
+
level: 'info',
|
|
292
|
+
timestamp: new Date(),
|
|
293
|
+
args: ['测试消息']
|
|
294
|
+
});
|
|
295
|
+
expect(memoryTransport.getRecords()).toHaveLength(1);
|
|
296
|
+
memoryTransport.clear();
|
|
297
|
+
expect(memoryTransport.getRecords()).toHaveLength(0);
|
|
298
|
+
});
|
|
299
|
+
it('应该在关闭时清理资源', () => {
|
|
300
|
+
memoryTransport.log({
|
|
301
|
+
level: 'info',
|
|
302
|
+
timestamp: new Date(),
|
|
303
|
+
args: ['测试消息']
|
|
304
|
+
});
|
|
305
|
+
memoryTransport.close();
|
|
306
|
+
expect(memoryTransport.getRecords()).toHaveLength(0);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/ring-cache/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAc,cAAc,CAAA"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 环形缓存(Ring Cache)实现
|
|
3
|
+
* 专门用于日志场景的FIFO(先进先出)缓存
|
|
4
|
+
*
|
|
5
|
+
* 特点:
|
|
6
|
+
* - FIFO行为:新数据总是添加到尾部,当容量满时从头部移除旧数据
|
|
7
|
+
* - 固定容量:避免内存无限增长
|
|
8
|
+
* - 高效操作:O(1)的添加和访问操作
|
|
9
|
+
* - 适用场景:日志记录、事件历史、时间序列数据等
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* 环形缓存配置选项
|
|
13
|
+
*/
|
|
14
|
+
export interface RingCacheOptions {
|
|
15
|
+
/** 缓存容量,默认为1000 */
|
|
16
|
+
capacity?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 环形缓存条目
|
|
20
|
+
*/
|
|
21
|
+
export interface RingCacheEntry<T> {
|
|
22
|
+
/** 数据 */
|
|
23
|
+
data: T;
|
|
24
|
+
/** 添加时间戳 */
|
|
25
|
+
timestamp: Date;
|
|
26
|
+
/** 条目ID */
|
|
27
|
+
id: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 环形缓存统计信息
|
|
31
|
+
*/
|
|
32
|
+
export interface RingCacheStats {
|
|
33
|
+
/** 当前条目数量 */
|
|
34
|
+
size: number;
|
|
35
|
+
/** 缓存容量 */
|
|
36
|
+
capacity: number;
|
|
37
|
+
/** 是否已满 */
|
|
38
|
+
isFull: boolean;
|
|
39
|
+
/** 最旧条目的时间戳 */
|
|
40
|
+
oldestTimestamp?: Date;
|
|
41
|
+
/** 最新条目的时间戳 */
|
|
42
|
+
newestTimestamp?: Date;
|
|
43
|
+
/** 总共添加的条目数(包括已被覆盖的) */
|
|
44
|
+
totalAdded: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 环形缓存实现
|
|
48
|
+
* 使用固定大小的数组实现FIFO行为的环形缓存
|
|
49
|
+
*/
|
|
50
|
+
export declare class RingCache<T> {
|
|
51
|
+
private buffer;
|
|
52
|
+
private head;
|
|
53
|
+
private tail;
|
|
54
|
+
private size;
|
|
55
|
+
private capacity;
|
|
56
|
+
private totalAdded;
|
|
57
|
+
private nextId;
|
|
58
|
+
constructor(options?: RingCacheOptions);
|
|
59
|
+
/**
|
|
60
|
+
* 添加新条目到缓存
|
|
61
|
+
* 如果缓存已满,将覆盖最旧的条目
|
|
62
|
+
*/
|
|
63
|
+
add(data: T): RingCacheEntry<T>;
|
|
64
|
+
/**
|
|
65
|
+
* 获取所有缓存条目(按添加顺序)
|
|
66
|
+
*/
|
|
67
|
+
getAll(): RingCacheEntry<T>[];
|
|
68
|
+
/**
|
|
69
|
+
* 获取最近的N个条目
|
|
70
|
+
*/
|
|
71
|
+
getRecent(count: number): RingCacheEntry<T>[];
|
|
72
|
+
/**
|
|
73
|
+
* 获取最旧的N个条目
|
|
74
|
+
*/
|
|
75
|
+
getOldest(count: number): RingCacheEntry<T>[];
|
|
76
|
+
/**
|
|
77
|
+
* 根据时间范围获取条目
|
|
78
|
+
*/
|
|
79
|
+
getByTimeRange(start: Date, end: Date): RingCacheEntry<T>[];
|
|
80
|
+
/**
|
|
81
|
+
* 搜索满足条件的条目
|
|
82
|
+
*/
|
|
83
|
+
search(predicate: (entry: RingCacheEntry<T>) => boolean): RingCacheEntry<T>[];
|
|
84
|
+
/**
|
|
85
|
+
* 获取缓存统计信息
|
|
86
|
+
*/
|
|
87
|
+
getStats(): RingCacheStats;
|
|
88
|
+
/**
|
|
89
|
+
* 检查缓存是否为空
|
|
90
|
+
*/
|
|
91
|
+
isEmpty(): boolean;
|
|
92
|
+
/**
|
|
93
|
+
* 检查缓存是否已满
|
|
94
|
+
*/
|
|
95
|
+
isFull(): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* 获取当前缓存大小
|
|
98
|
+
*/
|
|
99
|
+
getSize(): number;
|
|
100
|
+
/**
|
|
101
|
+
* 获取缓存容量
|
|
102
|
+
*/
|
|
103
|
+
getCapacity(): number;
|
|
104
|
+
/**
|
|
105
|
+
* 清空缓存
|
|
106
|
+
*/
|
|
107
|
+
clear(): void;
|
|
108
|
+
/**
|
|
109
|
+
* 导出为数组格式
|
|
110
|
+
*/
|
|
111
|
+
toArray(): T[];
|
|
112
|
+
/**
|
|
113
|
+
* 导出为JSON格式
|
|
114
|
+
*/
|
|
115
|
+
toJSON(): string;
|
|
116
|
+
/**
|
|
117
|
+
* 生成唯一ID
|
|
118
|
+
*/
|
|
119
|
+
private generateId;
|
|
120
|
+
/**
|
|
121
|
+
* 创建迭代器,支持for...of语法
|
|
122
|
+
*/
|
|
123
|
+
[Symbol.iterator](): Iterator<RingCacheEntry<T>>;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=ring-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ring-cache.d.ts","sourceRoot":"","sources":["../../source/ring-cache/ring-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,mBAAmB;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,SAAS;IACT,IAAI,EAAE,CAAC,CAAA;IACP,YAAY;IACZ,SAAS,EAAE,IAAI,CAAA;IACf,WAAW;IACX,EAAE,EAAE,MAAM,CAAA;CACX;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,aAAa;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW;IACX,MAAM,EAAE,OAAO,CAAA;IACf,eAAe;IACf,eAAe,CAAC,EAAE,IAAI,CAAA;IACtB,eAAe;IACf,eAAe,CAAC,EAAE,IAAI,CAAA;IACtB,wBAAwB;IACxB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED;;;GAGG;AACH,qBAAa,SAAS,CAAC,CAAC;IACtB,OAAO,CAAC,MAAM,CAAmC;IACjD,OAAO,CAAC,IAAI,CAAY;IACxB,OAAO,CAAC,IAAI,CAAY;IACxB,OAAO,CAAC,IAAI,CAAY;IACxB,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,UAAU,CAAY;IAC9B,OAAO,CAAC,MAAM,CAAY;gBAEd,OAAO,GAAE,gBAAqB;IAQ1C;;;OAGG;IACH,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC;IAqB/B;;OAEG;IACH,MAAM,IAAI,cAAc,CAAC,CAAC,CAAC,EAAE;IAkB7B;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,EAAE;IAoB7C;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,EAAE;IAmB7C;;OAEG;IACH,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,cAAc,CAAC,CAAC,CAAC,EAAE;IAM3D;;OAEG;IACH,MAAM,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,OAAO,GAAG,cAAc,CAAC,CAAC,CAAC,EAAE;IAI7E;;OAEG;IACH,QAAQ,IAAI,cAAc;IAa1B;;OAEG;IACH,OAAO,IAAI,OAAO;IAIlB;;OAEG;IACH,MAAM,IAAI,OAAO;IAIjB;;OAEG;IACH,OAAO,IAAI,MAAM;IAIjB;;OAEG;IACH,WAAW,IAAI,MAAM;IAIrB;;OAEG;IACH,KAAK,IAAI,IAAI;IASb;;OAEG;IACH,OAAO,IAAI,CAAC,EAAE;IAId;;OAEG;IACH,MAAM,IAAI,MAAM;IAIhB;;OAEG;IACH,OAAO,CAAC,UAAU;IAIlB;;OAEG;IACF,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;CASlD"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 环形缓存(Ring Cache)实现
|
|
3
|
+
* 专门用于日志场景的FIFO(先进先出)缓存
|
|
4
|
+
*
|
|
5
|
+
* 特点:
|
|
6
|
+
* - FIFO行为:新数据总是添加到尾部,当容量满时从头部移除旧数据
|
|
7
|
+
* - 固定容量:避免内存无限增长
|
|
8
|
+
* - 高效操作:O(1)的添加和访问操作
|
|
9
|
+
* - 适用场景:日志记录、事件历史、时间序列数据等
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* 环形缓存实现
|
|
13
|
+
* 使用固定大小的数组实现FIFO行为的环形缓存
|
|
14
|
+
*/
|
|
15
|
+
export class RingCache {
|
|
16
|
+
buffer;
|
|
17
|
+
head = 0; // 指向最旧的元素
|
|
18
|
+
tail = 0; // 指向下一个要插入的位置
|
|
19
|
+
size = 0;
|
|
20
|
+
capacity;
|
|
21
|
+
totalAdded = 0;
|
|
22
|
+
nextId = 1;
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
this.capacity = options.capacity ?? 1000;
|
|
25
|
+
if (this.capacity <= 0) {
|
|
26
|
+
throw new Error('Capacity must be greater than 0');
|
|
27
|
+
}
|
|
28
|
+
this.buffer = new Array(this.capacity);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 添加新条目到缓存
|
|
32
|
+
* 如果缓存已满,将覆盖最旧的条目
|
|
33
|
+
*/
|
|
34
|
+
add(data) {
|
|
35
|
+
const entry = {
|
|
36
|
+
data,
|
|
37
|
+
timestamp: new Date(),
|
|
38
|
+
id: this.generateId()
|
|
39
|
+
};
|
|
40
|
+
this.buffer[this.tail] = entry;
|
|
41
|
+
this.tail = (this.tail + 1) % this.capacity;
|
|
42
|
+
this.totalAdded++;
|
|
43
|
+
if (this.size < this.capacity) {
|
|
44
|
+
this.size++;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// 缓存已满,头指针也需要移动
|
|
48
|
+
this.head = (this.head + 1) % this.capacity;
|
|
49
|
+
}
|
|
50
|
+
return entry;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 获取所有缓存条目(按添加顺序)
|
|
54
|
+
*/
|
|
55
|
+
getAll() {
|
|
56
|
+
if (this.size === 0) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
const result = [];
|
|
60
|
+
for (let i = 0; i < this.size; i++) {
|
|
61
|
+
const index = (this.head + i) % this.capacity;
|
|
62
|
+
const entry = this.buffer[index];
|
|
63
|
+
if (entry) {
|
|
64
|
+
result.push(entry);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 获取最近的N个条目
|
|
71
|
+
*/
|
|
72
|
+
getRecent(count) {
|
|
73
|
+
if (count <= 0 || this.size === 0) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
const actualCount = Math.min(count, this.size);
|
|
77
|
+
const result = [];
|
|
78
|
+
// 从尾部向前获取
|
|
79
|
+
for (let i = 0; i < actualCount; i++) {
|
|
80
|
+
const index = (this.tail - 1 - i + this.capacity) % this.capacity;
|
|
81
|
+
const entry = this.buffer[index];
|
|
82
|
+
if (entry) {
|
|
83
|
+
result.unshift(entry); // 保持时间顺序
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 获取最旧的N个条目
|
|
90
|
+
*/
|
|
91
|
+
getOldest(count) {
|
|
92
|
+
if (count <= 0 || this.size === 0) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
const actualCount = Math.min(count, this.size);
|
|
96
|
+
const result = [];
|
|
97
|
+
for (let i = 0; i < actualCount; i++) {
|
|
98
|
+
const index = (this.head + i) % this.capacity;
|
|
99
|
+
const entry = this.buffer[index];
|
|
100
|
+
if (entry) {
|
|
101
|
+
result.push(entry);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 根据时间范围获取条目
|
|
108
|
+
*/
|
|
109
|
+
getByTimeRange(start, end) {
|
|
110
|
+
return this.getAll().filter(entry => entry.timestamp >= start && entry.timestamp <= end);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 搜索满足条件的条目
|
|
114
|
+
*/
|
|
115
|
+
search(predicate) {
|
|
116
|
+
return this.getAll().filter(predicate);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 获取缓存统计信息
|
|
120
|
+
*/
|
|
121
|
+
getStats() {
|
|
122
|
+
const all = this.getAll();
|
|
123
|
+
return {
|
|
124
|
+
size: this.size,
|
|
125
|
+
capacity: this.capacity,
|
|
126
|
+
isFull: this.size >= this.capacity,
|
|
127
|
+
oldestTimestamp: all.length > 0 ? all[0].timestamp : undefined,
|
|
128
|
+
newestTimestamp: all.length > 0 ? all[all.length - 1].timestamp : undefined,
|
|
129
|
+
totalAdded: this.totalAdded
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 检查缓存是否为空
|
|
134
|
+
*/
|
|
135
|
+
isEmpty() {
|
|
136
|
+
return this.size === 0;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* 检查缓存是否已满
|
|
140
|
+
*/
|
|
141
|
+
isFull() {
|
|
142
|
+
return this.size >= this.capacity;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 获取当前缓存大小
|
|
146
|
+
*/
|
|
147
|
+
getSize() {
|
|
148
|
+
return this.size;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 获取缓存容量
|
|
152
|
+
*/
|
|
153
|
+
getCapacity() {
|
|
154
|
+
return this.capacity;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 清空缓存
|
|
158
|
+
*/
|
|
159
|
+
clear() {
|
|
160
|
+
this.buffer = new Array(this.capacity);
|
|
161
|
+
this.head = 0;
|
|
162
|
+
this.tail = 0;
|
|
163
|
+
this.size = 0;
|
|
164
|
+
this.totalAdded = 0;
|
|
165
|
+
this.nextId = 1;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* 导出为数组格式
|
|
169
|
+
*/
|
|
170
|
+
toArray() {
|
|
171
|
+
return this.getAll().map(entry => entry.data);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* 导出为JSON格式
|
|
175
|
+
*/
|
|
176
|
+
toJSON() {
|
|
177
|
+
return JSON.stringify(this.getAll(), null, 2);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* 生成唯一ID
|
|
181
|
+
*/
|
|
182
|
+
generateId() {
|
|
183
|
+
return `${Date.now()}-${this.nextId++}`;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 创建迭代器,支持for...of语法
|
|
187
|
+
*/
|
|
188
|
+
*[Symbol.iterator]() {
|
|
189
|
+
for (let i = 0; i < this.size; i++) {
|
|
190
|
+
const index = (this.head + i) % this.capacity;
|
|
191
|
+
const entry = this.buffer[index];
|
|
192
|
+
if (entry) {
|
|
193
|
+
yield entry;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ring-cache.test.d.ts","sourceRoot":"","sources":["../../source/ring-cache/ring-cache.test.ts"],"names":[],"mappings":""}
|