mcp-cnbs 1.2.0 → 1.3.1

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/dist/index.js CHANGED
@@ -15,128 +15,75 @@ import axios from "axios";
15
15
  import https from "https";
16
16
 
17
17
  // src/services/cache.ts
18
- import fs from "fs";
19
- import path from "path";
20
- import { promisify } from "util";
21
- var writeFileAsync = promisify(fs.writeFile);
22
- var readFileAsync = promisify(fs.readFile);
23
- var mkdirAsync = promisify(fs.mkdir);
24
- var existsAsync = promisify(fs.exists);
25
18
  var CnbsLruCache = class {
26
19
  entryMap = /* @__PURE__ */ new Map();
27
20
  head = null;
28
21
  tail = null;
29
- persistPath = null;
30
22
  capacity;
31
23
  defaultExpire;
32
24
  maxMemorySize;
33
25
  currentMemorySize = 0;
34
- persistInterval;
35
26
  cleanupInterval;
36
- compression;
37
- // 统计信息
27
+ lastCleanupTime = 0;
38
28
  totalHits = 0;
39
29
  totalMisses = 0;
40
30
  evictionCount = 0;
41
31
  expirationCount = 0;
42
- persistenceCount = 0;
43
- // 定时器
44
- persistTimer = null;
45
- cleanupTimer = null;
46
- // 并发控制
47
- persistLock = false;
48
32
  constructor(options = {}) {
49
- this.persistPath = options.persistPath || null;
50
- this.capacity = options.capacity || 1e3;
51
- this.defaultExpire = options.defaultExpire || 24 * 60 * 60 * 1e3;
52
- this.maxMemorySize = options.maxMemorySize || 100 * 1024 * 1024;
53
- this.persistInterval = options.persistInterval || 5 * 60 * 1e3;
54
- this.cleanupInterval = options.cleanupInterval || 60 * 1e3;
55
- this.compression = options.compression || false;
56
- this.loadFromDisk();
57
- this.startTimers();
58
- }
59
- // 启动定时器
60
- startTimers() {
61
- this.persistTimer = setInterval(() => {
62
- this.saveToDiskAsync();
63
- }, this.persistInterval);
64
- this.cleanupTimer = setInterval(() => {
65
- this.cleanupExpired();
66
- }, this.cleanupInterval);
67
- }
68
- // 停止定时器
69
- stopTimers() {
70
- if (this.persistTimer) {
71
- clearInterval(this.persistTimer);
72
- this.persistTimer = null;
73
- }
74
- if (this.cleanupTimer) {
75
- clearInterval(this.cleanupTimer);
76
- this.cleanupTimer = null;
77
- }
78
- }
79
- // 前置条目
33
+ this.capacity = options.capacity ?? 1e3;
34
+ this.defaultExpire = options.defaultExpire ?? 24 * 60 * 60 * 1e3;
35
+ this.maxMemorySize = options.maxMemorySize ?? 100 * 1024 * 1024;
36
+ this.cleanupInterval = options.cleanupInterval ?? 60 * 1e3;
37
+ }
38
+ // ─── 按需清理(替代 setInterval)────────────────────────────────────────────
39
+ maybeCleanup() {
40
+ const now = Date.now();
41
+ if (now - this.lastCleanupTime < this.cleanupInterval) return;
42
+ this.lastCleanupTime = now;
43
+ this.cleanupExpired();
44
+ }
45
+ cleanupExpired() {
46
+ const now = Date.now();
47
+ const expiredKeys = [];
48
+ this.entryMap.forEach((entry, key) => {
49
+ if (now > entry.expireAt) expiredKeys.push(key);
50
+ });
51
+ for (const key of expiredKeys) {
52
+ const entry = this.entryMap.get(key);
53
+ if (entry) {
54
+ this.removeEntryFromList(entry);
55
+ this.entryMap.delete(key);
56
+ this.expirationCount++;
57
+ }
58
+ }
59
+ }
60
+ // ─── 双向链表操作 ────────────────────────────────────────────────────────────
80
61
  prependEntry(entry) {
81
62
  entry.prev = null;
82
63
  entry.next = this.head;
83
- if (this.head !== null) {
84
- this.head.prev = entry;
85
- }
64
+ if (this.head !== null) this.head.prev = entry;
86
65
  this.head = entry;
87
- if (this.tail === null) {
88
- this.tail = entry;
89
- }
66
+ if (this.tail === null) this.tail = entry;
90
67
  }
91
- // 删除条目
92
- deleteEntry(entry) {
93
- if (entry.prev !== null) {
94
- entry.prev.next = entry.next;
95
- } else {
96
- this.head = entry.next;
97
- }
98
- if (entry.next !== null) {
99
- entry.next.prev = entry.prev;
100
- } else {
101
- this.tail = entry.prev;
102
- }
68
+ removeEntryFromList(entry) {
69
+ if (entry.prev !== null) entry.prev.next = entry.next;
70
+ else this.head = entry.next;
71
+ if (entry.next !== null) entry.next.prev = entry.prev;
72
+ else this.tail = entry.prev;
103
73
  this.currentMemorySize -= entry.size;
104
74
  }
105
- // 提升条目
106
75
  promoteEntry(entry) {
107
- this.deleteEntry(entry);
76
+ this.removeEntryFromList(entry);
108
77
  this.prependEntry(entry);
109
78
  }
110
- // 删除尾部条目
111
79
  dropTail() {
112
80
  if (this.tail === null) return null;
113
81
  const tailEntry = this.tail;
114
- this.deleteEntry(tailEntry);
82
+ this.removeEntryFromList(tailEntry);
115
83
  this.evictionCount++;
116
84
  return tailEntry;
117
85
  }
118
- // 清理过期数据
119
- cleanupExpired() {
120
- const now = Date.now();
121
- const expiredKeys = [];
122
- this.entryMap.forEach((entry, key) => {
123
- if (now > entry.expireAt) {
124
- expiredKeys.push(key);
125
- }
126
- });
127
- for (const key of expiredKeys) {
128
- const entry = this.entryMap.get(key);
129
- if (entry) {
130
- this.deleteEntry(entry);
131
- this.entryMap.delete(key);
132
- this.expirationCount++;
133
- }
134
- }
135
- if (expiredKeys.length > 0) {
136
- console.info(`Cleaned up ${expiredKeys.length} expired cache entries`);
137
- }
138
- }
139
- // 计算值的大小
86
+ // ─── 工具 ────────────────────────────────────────────────────────────────────
140
87
  calculateSize(value) {
141
88
  try {
142
89
  return JSON.stringify(value).length;
@@ -144,15 +91,16 @@ var CnbsLruCache = class {
144
91
  return 0;
145
92
  }
146
93
  }
147
- // 获取缓存
94
+ // ─── 公开 API ────────────────────────────────────────────────────────────────
148
95
  fetch(key) {
96
+ this.maybeCleanup();
149
97
  const entry = this.entryMap.get(key);
150
98
  if (!entry) {
151
99
  this.totalMisses++;
152
100
  return null;
153
101
  }
154
102
  if (Date.now() > entry.expireAt) {
155
- this.deleteEntry(entry);
103
+ this.removeEntryFromList(entry);
156
104
  this.entryMap.delete(key);
157
105
  this.expirationCount++;
158
106
  this.totalMisses++;
@@ -164,42 +112,34 @@ var CnbsLruCache = class {
164
112
  this.totalHits++;
165
113
  return entry.value;
166
114
  }
167
- // 批量获取缓存
168
115
  fetchMultiple(keys) {
169
116
  const result = /* @__PURE__ */ new Map();
170
117
  for (const key of keys) {
171
118
  const value = this.fetch(key);
172
- if (value !== null) {
173
- result.set(key, value);
174
- }
119
+ if (value !== null) result.set(key, value);
175
120
  }
176
121
  return result;
177
122
  }
178
- // 存储缓存
179
123
  store(key, value, ttl = this.defaultExpire) {
180
124
  const size = this.calculateSize(value);
181
125
  while (this.currentMemorySize + size > this.maxMemorySize && this.entryMap.size > 0) {
182
- const tailEntry = this.dropTail();
183
- if (tailEntry) {
184
- this.entryMap.delete(tailEntry.key);
185
- }
186
- }
187
- const existingEntry = this.entryMap.get(key);
188
- if (existingEntry) {
189
- this.currentMemorySize -= existingEntry.size;
190
- existingEntry.value = value;
191
- existingEntry.size = size;
192
- existingEntry.expireAt = Date.now() + ttl;
193
- existingEntry.hitCount = 1;
194
- existingEntry.lastHit = Date.now();
195
- this.promoteEntry(existingEntry);
126
+ const tail = this.dropTail();
127
+ if (tail) this.entryMap.delete(tail.key);
128
+ }
129
+ const existing = this.entryMap.get(key);
130
+ if (existing) {
131
+ this.currentMemorySize -= existing.size;
132
+ existing.value = value;
133
+ existing.size = size;
134
+ existing.expireAt = Date.now() + ttl;
135
+ existing.hitCount = 1;
136
+ existing.lastHit = Date.now();
137
+ this.promoteEntry(existing);
196
138
  this.currentMemorySize += size;
197
139
  } else {
198
140
  if (this.entryMap.size >= this.capacity) {
199
- const tailEntry = this.dropTail();
200
- if (tailEntry) {
201
- this.entryMap.delete(tailEntry.key);
202
- }
141
+ const tail = this.dropTail();
142
+ if (tail) this.entryMap.delete(tail.key);
203
143
  }
204
144
  const newEntry = {
205
145
  key,
@@ -216,75 +156,40 @@ var CnbsLruCache = class {
216
156
  this.currentMemorySize += size;
217
157
  }
218
158
  }
219
- // 批量存储缓存
220
159
  storeMultiple(items) {
221
- for (const item of items) {
222
- this.store(item.key, item.value, item.ttl);
223
- }
160
+ for (const item of items) this.store(item.key, item.value, item.ttl);
224
161
  }
225
- // 删除缓存
226
162
  remove(key) {
227
163
  const entry = this.entryMap.get(key);
228
164
  if (entry) {
229
- this.deleteEntry(entry);
165
+ this.removeEntryFromList(entry);
230
166
  this.entryMap.delete(key);
231
167
  }
232
168
  }
233
- // 批量删除缓存
234
169
  removeMultiple(keys) {
235
- for (const key of keys) {
236
- this.remove(key);
237
- }
170
+ for (const key of keys) this.remove(key);
238
171
  }
239
- // 清空缓存
240
172
  flush() {
241
173
  this.entryMap.clear();
242
174
  this.head = null;
243
175
  this.tail = null;
244
176
  this.currentMemorySize = 0;
245
- this.saveToDisk();
246
177
  }
247
- // 获取缓存数量
248
178
  count() {
249
179
  return this.entryMap.size;
250
180
  }
251
- // 获取内存使用
252
181
  getMemorySize() {
253
182
  return this.currentMemorySize;
254
183
  }
255
- // 获取统计信息
256
184
  getStats() {
257
- if (this.entryMap.size === 0) {
258
- return {
259
- size: 0,
260
- capacity: this.capacity,
261
- memorySize: this.currentMemorySize,
262
- maxMemorySize: this.maxMemorySize,
263
- oldestEntry: null,
264
- topHit: null,
265
- hitRate: 0,
266
- missRate: 0,
267
- totalHits: this.totalHits,
268
- totalMisses: this.totalMisses,
269
- evictionCount: this.evictionCount,
270
- expirationCount: this.expirationCount,
271
- persistenceCount: this.persistenceCount
272
- };
273
- }
274
185
  let oldestEntry = null;
275
186
  let topHit = null;
276
187
  this.entryMap.forEach((item, key) => {
277
188
  const age = Date.now() - item.lastHit;
278
- if (!oldestEntry || age > oldestEntry.age) {
279
- oldestEntry = { key, age };
280
- }
281
- if (!topHit || item.hitCount > topHit.count) {
282
- topHit = { key, count: item.hitCount };
283
- }
189
+ if (!oldestEntry || age > oldestEntry.age) oldestEntry = { key, age };
190
+ if (!topHit || item.hitCount > topHit.count) topHit = { key, count: item.hitCount };
284
191
  });
285
- const totalRequests = this.totalHits + this.totalMisses;
286
- const hitRate = totalRequests > 0 ? this.totalHits / totalRequests : 0;
287
- const missRate = totalRequests > 0 ? this.totalMisses / totalRequests : 0;
192
+ const total = this.totalHits + this.totalMisses;
288
193
  return {
289
194
  size: this.entryMap.size,
290
195
  capacity: this.capacity,
@@ -292,16 +197,16 @@ var CnbsLruCache = class {
292
197
  maxMemorySize: this.maxMemorySize,
293
198
  oldestEntry,
294
199
  topHit,
295
- hitRate: parseFloat((hitRate * 100).toFixed(2)),
296
- missRate: parseFloat((missRate * 100).toFixed(2)),
200
+ hitRate: total > 0 ? parseFloat((this.totalHits / total * 100).toFixed(2)) : 0,
201
+ missRate: total > 0 ? parseFloat((this.totalMisses / total * 100).toFixed(2)) : 0,
297
202
  totalHits: this.totalHits,
298
203
  totalMisses: this.totalMisses,
299
204
  evictionCount: this.evictionCount,
300
205
  expirationCount: this.expirationCount,
301
- persistenceCount: this.persistenceCount
206
+ persistenceCount: 0
207
+ // Workers 环境无持久化
302
208
  };
303
209
  }
304
- // 获取缓存信息
305
210
  getCacheInfo(key) {
306
211
  const entry = this.entryMap.get(key);
307
212
  if (!entry) return null;
@@ -312,96 +217,8 @@ var CnbsLruCache = class {
312
217
  hits: entry.hitCount
313
218
  };
314
219
  }
315
- // 同步保存到磁盘
316
- saveToDisk() {
317
- if (!this.persistPath) return;
318
- try {
319
- const dir = path.dirname(this.persistPath);
320
- if (!fs.existsSync(dir)) {
321
- fs.mkdirSync(dir, { recursive: true });
322
- }
323
- const dataToSave = [];
324
- let current = this.head;
325
- while (current !== null) {
326
- dataToSave.push({
327
- key: current.key,
328
- value: current.value,
329
- expireAt: current.expireAt,
330
- hitCount: current.hitCount,
331
- lastHit: current.lastHit,
332
- size: current.size
333
- });
334
- current = current.next;
335
- }
336
- fs.writeFileSync(this.persistPath, JSON.stringify(dataToSave));
337
- this.persistenceCount++;
338
- } catch (error) {
339
- console.error("Failed to save cache to disk:", error);
340
- }
341
- }
342
- // 异步保存到磁盘
343
- async saveToDiskAsync() {
344
- if (!this.persistPath || this.persistLock) return;
345
- this.persistLock = true;
346
- try {
347
- const dir = path.dirname(this.persistPath);
348
- if (!await existsAsync(dir)) {
349
- await mkdirAsync(dir, { recursive: true });
350
- }
351
- const dataToSave = [];
352
- let current = this.head;
353
- while (current !== null) {
354
- dataToSave.push({
355
- key: current.key,
356
- value: current.value,
357
- expireAt: current.expireAt,
358
- hitCount: current.hitCount,
359
- lastHit: current.lastHit,
360
- size: current.size
361
- });
362
- current = current.next;
363
- }
364
- await writeFileAsync(this.persistPath, JSON.stringify(dataToSave));
365
- this.persistenceCount++;
366
- } catch (error) {
367
- console.error("Failed to save cache to disk:", error);
368
- } finally {
369
- this.persistLock = false;
370
- }
371
- }
372
- // 从磁盘加载
373
- loadFromDisk() {
374
- if (!this.persistPath || !fs.existsSync(this.persistPath)) return;
375
- try {
376
- const data = fs.readFileSync(this.persistPath, "utf8");
377
- const cachedItems = JSON.parse(data);
378
- const now = Date.now();
379
- for (const item of cachedItems) {
380
- if (item.expireAt > now) {
381
- const newEntry = {
382
- key: item.key,
383
- value: item.value,
384
- expireAt: item.expireAt,
385
- hitCount: item.hitCount,
386
- lastHit: item.lastHit,
387
- prev: null,
388
- next: null,
389
- size: item.size
390
- };
391
- this.entryMap.set(item.key, newEntry);
392
- this.prependEntry(newEntry);
393
- this.currentMemorySize += item.size;
394
- }
395
- }
396
- console.info(`Loaded ${this.entryMap.size} cache entries from disk`);
397
- } catch (error) {
398
- console.error("Failed to load cache from disk:", error);
399
- }
400
- }
401
- // 关闭缓存
220
+ // close() 保留空实现,防止上层调用报错
402
221
  close() {
403
- this.stopTimers();
404
- this.saveToDisk();
405
222
  }
406
223
  };
407
224
  var CnbsCacheHub = class {
@@ -410,19 +227,14 @@ var CnbsCacheHub = class {
410
227
  capacity: 1e3,
411
228
  defaultExpire: 24 * 60 * 60 * 1e3,
412
229
  maxMemorySize: 100 * 1024 * 1024,
413
- persistInterval: 5 * 60 * 1e3,
414
- cleanupInterval: 60 * 1e3,
415
- compression: false
230
+ cleanupInterval: 60 * 1e3
416
231
  };
417
- // 获取缓存
418
232
  getCache(name, options = {}) {
419
233
  if (!this.caches.has(name)) {
420
- const cacheOptions = { ...this.defaultOptions, ...options };
421
- this.caches.set(name, new CnbsLruCache(cacheOptions));
234
+ this.caches.set(name, new CnbsLruCache({ ...this.defaultOptions, ...options }));
422
235
  }
423
236
  return this.caches.get(name);
424
237
  }
425
- // 删除缓存
426
238
  removeCache(name) {
427
239
  const cache = this.caches.get(name);
428
240
  if (cache) {
@@ -430,11 +242,9 @@ var CnbsCacheHub = class {
430
242
  this.caches.delete(name);
431
243
  }
432
244
  }
433
- // 清空所有缓存
434
245
  flushAll() {
435
246
  this.caches.forEach((cache) => cache.flush());
436
247
  }
437
- // 获取所有缓存统计
438
248
  getAllStats() {
439
249
  const stats = {};
440
250
  this.caches.forEach((cache, name) => {
@@ -442,34 +252,37 @@ var CnbsCacheHub = class {
442
252
  });
443
253
  return stats;
444
254
  }
445
- // 关闭所有缓存
446
255
  closeAll() {
447
256
  this.caches.forEach((cache) => cache.close());
448
257
  this.caches.clear();
449
258
  }
450
259
  };
451
- var cnbsCacheHub = new CnbsCacheHub();
260
+ var _hub = null;
261
+ function getCnbsCacheHub() {
262
+ if (!_hub) _hub = new CnbsCacheHub();
263
+ return _hub;
264
+ }
265
+ var cnbsCacheHub = new Proxy({}, {
266
+ get(_target, prop) {
267
+ return getCnbsCacheHub()[prop];
268
+ }
269
+ });
452
270
  var CacheKeyGenerator = class {
453
- // 生成搜索缓存键
454
271
  static generateSearchKey(keyword, pageNum = 1, pageSize = 10) {
455
272
  return `search_${keyword.toLowerCase()}_${pageNum}_${pageSize}`;
456
273
  }
457
- // 生成节点缓存键
458
274
  static generateNodeKey(category, parentId) {
459
- return `node_${category}_${parentId || "root"}`;
275
+ return `node_${category}_${parentId ?? "root"}`;
460
276
  }
461
- // 生成指标缓存键
462
277
  static generateMetricKey(setId, name) {
463
- return `metric_${setId}_${name || "all"}`;
278
+ return `metric_${setId}_${name ?? "all"}`;
464
279
  }
465
- // 生成数据系列缓存键
466
280
  static generateSeriesKey(setId, metricIds, periods, areas) {
467
- const metricKey = metricIds.sort().join("_");
468
- const periodKey = periods.sort().join("_");
281
+ const metricKey = [...metricIds].sort().join("_");
282
+ const periodKey = [...periods].sort().join("_");
469
283
  const areaKey = areas ? areas.map((a) => a.code).sort().join("_") : "000000000000";
470
284
  return `series_${setId}_${metricKey}_${periodKey}_${areaKey}`;
471
285
  }
472
- // 生成数据源缓存键
473
286
  static generateDataSourceKey(source, params) {
474
287
  const sortedParams = Object.keys(params).sort().map((key) => `${key}=${params[key]}`).join("&");
475
288
  return `datasource_${source}_${sortedParams}`;
@@ -827,11 +640,11 @@ var CnbsBoundaryHandler = class {
827
640
  return array[index];
828
641
  }
829
642
  // 检查对象属性
830
- static safePropertyAccess(obj, path2, defaultValue) {
643
+ static safePropertyAccess(obj, path, defaultValue) {
831
644
  if (!obj) {
832
645
  return defaultValue;
833
646
  }
834
- const parts = path2.split(".");
647
+ const parts = path.split(".");
835
648
  let current = obj;
836
649
  for (const part of parts) {
837
650
  if (current[part] === void 0) {