@lytjs/common-cache 6.5.0 → 6.6.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/README.md CHANGED
@@ -1,100 +1,100 @@
1
- # @lytjs/common-cache
2
-
3
- 缓存策略工具,提供 LRU 缓存、带过期时间的缓存和函数记忆化。
4
-
5
- ## 安装
6
-
7
- ```bash
8
- pnpm add @lytjs/common-cache
9
- ```
10
-
11
- ## API
12
-
13
- ### `LRUCache<K, V>`
14
-
15
- 基于双向链表实现的 LRU(最近最少使用)缓存。
16
-
17
- ```typescript
18
- import { LRUCache } from '@lytjs/common-cache';
19
-
20
- const cache = new LRUCache<string, number>(100);
21
- cache.set('key', 42);
22
- cache.get('key'); // 42
23
- ```
24
-
25
- ### `ExpiringCache<K, V>`
26
-
27
- 带过期时间的缓存,条目在指定时间后失效。
28
-
29
- ```typescript
30
- import { ExpiringCache } from '@lytjs/common-cache';
31
-
32
- const cache = new ExpiringCache<string, number>(5000); // 5 秒过期
33
- cache.set('key', 42);
34
- cache.get('key'); // 42(5 秒内有效)
35
- ```
36
-
37
- ### `memoize<T>(fn: T, options?): MemoizedFn<T>`
38
-
39
- 函数记忆化,缓存函数调用结果以提高重复调用的性能。
40
-
41
- ```typescript
42
- import { memoize } from '@lytjs/common-cache';
43
-
44
- const fn = memoize((a: number, b: number) => a + b);
45
- fn(1, 2); // 3(首次计算并缓存)
46
- fn(1, 2); // 3(直接返回缓存结果)
47
- fn.clear(); // 清空缓存
48
- ```
49
-
50
- ## 边界行为与已知限制
51
-
52
- ### `memoize()` 序列化限制
53
-
54
- 默认情况下,`memoize()` 使用 `JSON.stringify` 生成缓存 key。以下情况会导致缓存失效(每次调用都会重新执行原函数):
55
-
56
- | 限制 | 说明 |
57
- | --------------------- | ------------------------------------------------------------------------------ |
58
- | 循环引用 | `JSON.stringify` 遇到循环引用会抛出异常,被内部 `try/catch` 捕获后跳过缓存 |
59
- | `undefined` 值 | `JSON.stringify` 会忽略对象中的 `undefined` 值,可能导致不同参数生成相同的 key |
60
- | Symbol key | `JSON.stringify` 会忽略 Symbol 类型的键 |
61
- | `function` / `bigint` | `JSON.stringify` 会将函数转为 `undefined`,将 bigint 抛出异常 |
62
-
63
- **解决方案**:通过 `options.resolver` 提供自定义的 key 生成函数来处理上述情况。
64
-
65
- ```typescript
66
- const fn = memoize(complexFn, {
67
- resolver: (arg) => customKeyGenerator(arg),
68
- });
69
- ```
70
-
71
- ### `memoize()` maxSize 实际淘汰策略
72
-
73
- 当配置 `maxSize` 时,`memoize()` 的淘汰策略为 **FIFO(先进先出)**,而非 LRU。当缓存条目数超过 `maxSize` 时,会删除最早插入的条目(`Map.keys().next().value`),而不是最近最少使用的条目。
74
-
75
- ```typescript
76
- const fn = memoize(myFn, { maxSize: 3 });
77
- fn('a'); // 缓存: ['a']
78
- fn('b'); // 缓存: ['a', 'b']
79
- fn('c'); // 缓存: ['a', 'b', 'c']
80
- fn('d'); // 缓存: ['b', 'c', 'd']('a' 被淘汰,而非最少使用的条目)
81
- ```
82
-
83
- ### `ExpiringCache()` 无自动清理机制
84
-
85
- `ExpiringCache` 不会主动扫描和清理过期条目。过期条目仅在以下时机被**被动清理**:
86
-
87
- | 触发时机 | 行为 |
88
- | ---------- | -------------------------------------- |
89
- | `get(key)` | 如果条目已过期,删除并返回 `undefined` |
90
- | `has(key)` | 如果条目已过期,删除并返回 `false` |
91
-
92
- 这意味着如果大量条目过期后不再被访问,它们会一直占用内存。需要手动调用 `cleanup()` 方法来批量清理过期条目:
93
-
94
- ```typescript
95
- const cleaned = cache.cleanup(); // 返回清理的条目数量
96
- ```
97
-
98
- ## License
99
-
100
- MIT
1
+ # @lytjs/common-cache
2
+
3
+ 缓存策略工具,提供 LRU 缓存、带过期时间的缓存和函数记忆化。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pnpm add @lytjs/common-cache
9
+ ```
10
+
11
+ ## API
12
+
13
+ ### `LRUCache<K, V>`
14
+
15
+ 基于双向链表实现的 LRU(最近最少使用)缓存。
16
+
17
+ ```typescript
18
+ import { LRUCache } from '@lytjs/common-cache';
19
+
20
+ const cache = new LRUCache<string, number>(100);
21
+ cache.set('key', 42);
22
+ cache.get('key'); // 42
23
+ ```
24
+
25
+ ### `ExpiringCache<K, V>`
26
+
27
+ 带过期时间的缓存,条目在指定时间后失效。
28
+
29
+ ```typescript
30
+ import { ExpiringCache } from '@lytjs/common-cache';
31
+
32
+ const cache = new ExpiringCache<string, number>(5000); // 5 秒过期
33
+ cache.set('key', 42);
34
+ cache.get('key'); // 42(5 秒内有效)
35
+ ```
36
+
37
+ ### `memoize<T>(fn: T, options?): MemoizedFn<T>`
38
+
39
+ 函数记忆化,缓存函数调用结果以提高重复调用的性能。
40
+
41
+ ```typescript
42
+ import { memoize } from '@lytjs/common-cache';
43
+
44
+ const fn = memoize((a: number, b: number) => a + b);
45
+ fn(1, 2); // 3(首次计算并缓存)
46
+ fn(1, 2); // 3(直接返回缓存结果)
47
+ fn.clear(); // 清空缓存
48
+ ```
49
+
50
+ ## 边界行为与已知限制
51
+
52
+ ### `memoize()` 序列化限制
53
+
54
+ 默认情况下,`memoize()` 使用 `JSON.stringify` 生成缓存 key。以下情况会导致缓存失效(每次调用都会重新执行原函数):
55
+
56
+ | 限制 | 说明 |
57
+ | --------------------- | ------------------------------------------------------------------------------ |
58
+ | 循环引用 | `JSON.stringify` 遇到循环引用会抛出异常,被内部 `try/catch` 捕获后跳过缓存 |
59
+ | `undefined` 值 | `JSON.stringify` 会忽略对象中的 `undefined` 值,可能导致不同参数生成相同的 key |
60
+ | Symbol key | `JSON.stringify` 会忽略 Symbol 类型的键 |
61
+ | `function` / `bigint` | `JSON.stringify` 会将函数转为 `undefined`,将 bigint 抛出异常 |
62
+
63
+ **解决方案**:通过 `options.resolver` 提供自定义的 key 生成函数来处理上述情况。
64
+
65
+ ```typescript
66
+ const fn = memoize(complexFn, {
67
+ resolver: (arg) => customKeyGenerator(arg),
68
+ });
69
+ ```
70
+
71
+ ### `memoize()` maxSize 实际淘汰策略
72
+
73
+ 当配置 `maxSize` 时,`memoize()` 的淘汰策略为 **FIFO(先进先出)**,而非 LRU。当缓存条目数超过 `maxSize` 时,会删除最早插入的条目(`Map.keys().next().value`),而不是最近最少使用的条目。
74
+
75
+ ```typescript
76
+ const fn = memoize(myFn, { maxSize: 3 });
77
+ fn('a'); // 缓存: ['a']
78
+ fn('b'); // 缓存: ['a', 'b']
79
+ fn('c'); // 缓存: ['a', 'b', 'c']
80
+ fn('d'); // 缓存: ['b', 'c', 'd']('a' 被淘汰,而非最少使用的条目)
81
+ ```
82
+
83
+ ### `ExpiringCache()` 无自动清理机制
84
+
85
+ `ExpiringCache` 不会主动扫描和清理过期条目。过期条目仅在以下时机被**被动清理**:
86
+
87
+ | 触发时机 | 行为 |
88
+ | ---------- | -------------------------------------- |
89
+ | `get(key)` | 如果条目已过期,删除并返回 `undefined` |
90
+ | `has(key)` | 如果条目已过期,删除并返回 `false` |
91
+
92
+ 这意味着如果大量条目过期后不再被访问,它们会一直占用内存。需要手动调用 `cleanup()` 方法来批量清理过期条目:
93
+
94
+ ```typescript
95
+ const cleaned = cache.cleanup(); // 返回清理的条目数量
96
+ ```
97
+
98
+ ## License
99
+
100
+ MIT
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;AAkBO,IAAM,WAAN,MAAqB;AAAA,EAO1B,YAAY,OAAA,EAAiB;AAL7B,IAAA,IAAA,CAAQ,GAAA,uBAAiC,GAAA,EAAI;AAC7C,IAAA,IAAA,CAAQ,IAAA,GAA6B,IAAA;AACrC,IAAA,IAAA,CAAQ,IAAA,GAA6B,IAAA;AACrC,IAAA,IAAA,CAAQ,KAAA,GAAgB,CAAA;AAGtB,IAAA,IAAI,UAAU,CAAA,EAAG;AACf,MAAA,MAAM,IAAI,MAAM,qCAAqC,CAAA;AAAA,IACvD;AACA,IAAA,IAAA,CAAK,QAAA,GAAW,OAAA;AAAA,EAClB;AAAA,EAEA,IAAI,IAAA,GAAe;AACjB,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AAAA,EAEQ,WAAW,IAAA,EAA2B;AAC5C,IAAA,IAAI,IAAA,KAAS,KAAK,IAAA,EAAM;AACxB,IAAA,IAAA,CAAK,WAAW,IAAI,CAAA;AACpB,IAAA,IAAA,CAAK,UAAU,IAAI,CAAA;AAAA,EACrB;AAAA,EAEQ,UAAU,IAAA,EAA2B;AAC3C,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AACjB,IAAA,IAAI,KAAK,IAAA,EAAM;AACb,MAAA,IAAA,CAAK,KAAK,IAAA,GAAO,IAAA;AAAA,IACnB;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAI,CAAC,KAAK,IAAA,EAAM;AACd,MAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,IACd;AAAA,EACF;AAAA,EAEQ,WAAW,IAAA,EAA2B;AAC5C,IAAA,IAAI,KAAK,IAAA,EAAM;AACb,MAAA,IAAA,CAAK,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AAAA,IACxB,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AAAA,IACnB;AACA,IAAA,IAAI,KAAK,IAAA,EAAM;AACb,MAAA,IAAA,CAAK,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AAAA,IACxB,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AAAA,IACnB;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACd;AAAA,EAEA,IAAI,GAAA,EAAuB;AACzB,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA;AAC7B,IAAA,IAAI,CAAC,MAAM,OAAO,MAAA;AAClB,IAAA,IAAA,CAAK,WAAW,IAAI,CAAA;AACpB,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AAAA,EAEA,GAAA,CAAI,KAAQ,KAAA,EAAgB;AAC1B,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA;AACjC,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,QAAA,CAAS,KAAA,GAAQ,KAAA;AACjB,MAAA,IAAA,CAAK,WAAW,QAAQ,CAAA;AACxB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,OAAA,GAAyB;AAAA,MAC7B,GAAA;AAAA,MACA,KAAA;AAAA,MACA,IAAA,EAAM,IAAA;AAAA,MACN,IAAA,EAAM;AAAA,KACR;AACA,IAAA,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAA,EAAK,OAAO,CAAA;AACzB,IAAA,IAAA,CAAK,UAAU,OAAO,CAAA;AACtB,IAAA,IAAA,CAAK,KAAA,EAAA;AAEL,IAAA,IAAI,IAAA,CAAK,KAAA,GAAQ,IAAA,CAAK,QAAA,EAAU;AAC9B,MAAA,MAAM,UAAU,IAAA,CAAK,IAAA;AACrB,MAAA,IAAI,CAAC,OAAA,EAAS;AACd,MAAA,IAAA,CAAK,WAAW,OAAO,CAAA;AACvB,MAAA,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA;AAC3B,MAAA,IAAA,CAAK,KAAA,EAAA;AAAA,IACP;AAAA,EACF;AAAA,EAEA,IAAI,GAAA,EAAiB;AACnB,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA;AAAA,EACzB;AAAA,EAEA,OAAO,GAAA,EAAiB;AACtB,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA;AAC7B,IAAA,IAAI,CAAC,MAAM,OAAO,KAAA;AAClB,IAAA,IAAA,CAAK,WAAW,IAAI,CAAA;AACpB,IAAA,IAAA,CAAK,GAAA,CAAI,OAAO,GAAG,CAAA;AACnB,IAAA,IAAA,CAAK,KAAA,EAAA;AACL,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,IAAI,KAAA,EAAM;AACf,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,KAAA,GAAQ,CAAA;AAAA,EACf;AAAA,EAEA,QAAQ,QAAA,EAA4C;AAClD,IAAA,IAAI,UAAU,IAAA,CAAK,IAAA;AACnB,IAAA,OAAO,OAAA,EAAS;AACd,MAAA,QAAA,CAAS,OAAA,CAAQ,KAAA,EAAO,OAAA,CAAQ,GAAG,CAAA;AACnC,MAAA,OAAA,GAAU,OAAA,CAAQ,IAAA;AAAA,IACpB;AAAA,EACF;AACF;AAkBO,IAAM,gBAAN,MAA0B;AAAA,EAI/B,YAAY,KAAA,EAAe;AAF3B,IAAA,IAAA,CAAQ,GAAA,uBAAiC,GAAA,EAAI;AAG3C,IAAA,IAAI,QAAQ,CAAA,EAAG;AACb,MAAA,MAAM,IAAI,MAAM,wCAAwC,CAAA;AAAA,IAC1D;AACA,IAAA,IAAA,CAAK,GAAA,GAAM,KAAA;AAAA,EACb;AAAA,EAEA,IAAI,IAAA,GAAe;AACjB,IAAA,OAAO,KAAK,GAAA,CAAI,IAAA;AAAA,EAClB;AAAA,EAEQ,UAAU,KAAA,EAA+B;AAC/C,IAAA,OAAO,IAAA,CAAK,GAAA,EAAI,GAAI,KAAA,CAAM,MAAA;AAAA,EAC5B;AAAA,EAEA,IAAI,GAAA,EAAuB;AACzB,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA;AAC9B,IAAA,IAAI,CAAC,OAAO,OAAO,MAAA;AACnB,IAAA,IAAI,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,EAAG;AACzB,MAAA,IAAA,CAAK,GAAA,CAAI,OAAO,GAAG,CAAA;AACnB,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,OAAO,KAAA,CAAM,KAAA;AAAA,EACf;AAAA,EAEA,GAAA,CAAI,KAAQ,KAAA,EAAgB;AAC1B,IAAA,MAAM,KAAA,GAAuB;AAAA,MAC3B,KAAA;AAAA,MACA,MAAA,EAAQ,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA,CAAK;AAAA,KAC5B;AACA,IAAA,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAA,EAAK,KAAK,CAAA;AAAA,EACzB;AAAA,EAEA,IAAI,GAAA,EAAiB;AACnB,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA;AAC9B,IAAA,IAAI,CAAC,OAAO,OAAO,KAAA;AACnB,IAAA,IAAI,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,EAAG;AACzB,MAAA,IAAA,CAAK,GAAA,CAAI,OAAO,GAAG,CAAA;AACnB,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,OAAO,GAAA,EAAiB;AACtB,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA;AAAA,EAC5B;AAAA,EAEA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,IAAI,KAAA,EAAM;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAA,GAAkB;AAChB,IAAA,IAAI,OAAA,GAAU,CAAA;AACd,IAAA,IAAA,CAAK,GAAA,CAAI,OAAA,CAAQ,CAAC,KAAA,EAAO,GAAA,KAAQ;AAC/B,MAAA,IAAI,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,EAAG;AACzB,QAAA,IAAA,CAAK,GAAA,CAAI,OAAO,GAAG,CAAA;AACnB,QAAA,OAAA,EAAA;AAAA,MACF;AAAA,IACF,CAAC,CAAA;AACD,IAAA,OAAO,OAAA;AAAA,EACT;AACF;AAkBO,SAAS,OAAA,CACd,EAAA,EAEA,OAAA,EACA,aAAA,EACe;AACf,EAAA,MAAM,aAAA,GAAgB,aAAA,oBAAiB,IAAI,GAAA,EAA2B;AACtE,EAAA,MAAM,UAAU,OAAA,EAAS,OAAA;AAEzB,EAAA,MAAM,QAAA,IAAY,IAAI,IAAA,KAAuC;AAC3D,IAAA,IAAI,GAAA;AACJ,IAAA,IAAI;AACF,MAAA,GAAA,GAAM,OAAA,EAAS,QAAA,GACX,OAAA,CAAQ,QAAA,CAAS,GAAG,IAAI,CAAA,GACxB,IAAA,CAAK,SAAA,CAAU,KAAK,MAAA,KAAW,CAAA,GAAI,IAAA,CAAK,CAAC,IAAI,IAAI,CAAA;AAAA,IACvD,CAAA,CAAA,MAAQ;AACN,MAAA,GAAA,GAAM,MAAA;AAAA,IACR;AACA,IAAA,IAAI,CAAC,GAAA,EAAK,OAAO,EAAA,CAAG,GAAG,IAAI,CAAA;AAC3B,IAAA,IAAI,aAAA,CAAc,GAAA,CAAI,GAAG,CAAA,EAAG;AAC1B,MAAA,OAAO,aAAA,CAAc,IAAI,GAAG,CAAA;AAAA,IAC9B;AACA,IAAA,MAAM,MAAA,GAAS,EAAA,CAAG,GAAG,IAAI,CAAA;AACzB,IAAA,aAAA,CAAc,GAAA,CAAI,KAAK,MAAM,CAAA;AAC7B,IAAA,IAAI,OAAA,IAAW,aAAA,CAAc,IAAA,GAAO,OAAA,EAAS;AAC3C,MAAA,MAAM,QAAA,GAAW,aAAA,CAAc,IAAA,EAAK,CAAE,MAAK,CAAE,KAAA;AAC7C,MAAA,IAAI,QAAA,KAAa,MAAA,EAAW,aAAA,CAAc,MAAA,CAAO,QAAQ,CAAA;AAAA,IAC3D;AACA,IAAA,OAAO,MAAA;AAAA,EACT,CAAA,CAAA;AAEA,EAAA,QAAA,CAAS,QAAQ,MAAM;AACrB,IAAA,aAAA,CAAc,KAAA,EAAM;AAAA,EACtB,CAAA;AAEA,EAAA,OAAO,QAAA;AACT","file":"index.cjs","sourcesContent":["/**\r\n * @lytjs/common-cache\r\n * 缓存策略工具\r\n */\r\n\r\n/**\r\n * LRU 缓存节点\r\n */\r\nexport interface LRUNode<K, V> {\r\n key: K;\r\n value: V;\r\n prev: LRUNode<K, V> | null;\r\n next: LRUNode<K, V> | null;\r\n}\r\n\r\n/**\r\n * LRU(最近最少使用)缓存\r\n */\r\nexport class LRUCache<K, V> {\r\n private capacity: number;\r\n private map: Map<K, LRUNode<K, V>> = new Map();\r\n private head: LRUNode<K, V> | null = null;\r\n private tail: LRUNode<K, V> | null = null;\r\n private _size: number = 0;\r\n\r\n constructor(maxSize: number) {\r\n if (maxSize < 1) {\r\n throw new Error('LRUCache maxSize must be at least 1');\r\n }\r\n this.capacity = maxSize;\r\n }\r\n\r\n get size(): number {\r\n return this._size;\r\n }\r\n\r\n private moveToHead(node: LRUNode<K, V>): void {\r\n if (node === this.head) return;\r\n this.removeNode(node);\r\n this.addToHead(node);\r\n }\r\n\r\n private addToHead(node: LRUNode<K, V>): void {\r\n node.prev = null;\r\n node.next = this.head;\r\n if (this.head) {\r\n this.head.prev = node;\r\n }\r\n this.head = node;\r\n if (!this.tail) {\r\n this.tail = node;\r\n }\r\n }\r\n\r\n private removeNode(node: LRUNode<K, V>): void {\r\n if (node.prev) {\r\n node.prev.next = node.next;\r\n } else {\r\n this.head = node.next;\r\n }\r\n if (node.next) {\r\n node.next.prev = node.prev;\r\n } else {\r\n this.tail = node.prev;\r\n }\r\n node.prev = null;\r\n node.next = null;\r\n }\r\n\r\n get(key: K): V | undefined {\r\n const node = this.map.get(key);\r\n if (!node) return undefined;\r\n this.moveToHead(node);\r\n return node.value;\r\n }\r\n\r\n set(key: K, value: V): void {\r\n const existing = this.map.get(key);\r\n if (existing) {\r\n existing.value = value;\r\n this.moveToHead(existing);\r\n return;\r\n }\r\n\r\n const newNode: LRUNode<K, V> = {\r\n key,\r\n value,\r\n prev: null,\r\n next: null,\r\n };\r\n this.map.set(key, newNode);\r\n this.addToHead(newNode);\r\n this._size++;\r\n\r\n if (this._size > this.capacity) {\r\n const removed = this.tail;\r\n if (!removed) return;\r\n this.removeNode(removed);\r\n this.map.delete(removed.key);\r\n this._size--;\r\n }\r\n }\r\n\r\n has(key: K): boolean {\r\n return this.map.has(key);\r\n }\r\n\r\n delete(key: K): boolean {\r\n const node = this.map.get(key);\r\n if (!node) return false;\r\n this.removeNode(node);\r\n this.map.delete(key);\r\n this._size--;\r\n return true;\r\n }\r\n\r\n clear(): void {\r\n this.map.clear();\r\n this.head = null;\r\n this.tail = null;\r\n this._size = 0;\r\n }\r\n\r\n forEach(callback: (value: V, key: K) => void): void {\r\n let current = this.head;\r\n while (current) {\r\n callback(current.value, current.key);\r\n current = current.next;\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 缓存条目\r\n */\r\nexport interface CacheEntry<V> {\r\n value: V;\r\n expiry: number;\r\n}\r\n\r\n/**\r\n * 带过期时间的缓存\r\n *\r\n * @note 此实现采用惰性删除策略:过期条目仅在 get() / has() 访问时被移除,\r\n * 不会主动扫描清理。如果存在大量写入后不再访问的过期条目,\r\n * 它们将一直占用内存直到调用 cleanup() 或 clear()。\r\n * 对于写入密集但读取稀疏的场景,建议定期调用 cleanup() 释放内存。\r\n */\r\nexport class ExpiringCache<K, V> {\r\n private ttl: number;\r\n private map: Map<K, CacheEntry<V>> = new Map();\r\n\r\n constructor(ttlMs: number) {\r\n if (ttlMs < 1) {\r\n throw new Error('ExpiringCache TTL must be at least 1ms');\r\n }\r\n this.ttl = ttlMs;\r\n }\r\n\r\n get size(): number {\r\n return this.map.size;\r\n }\r\n\r\n private isExpired(entry: CacheEntry<V>): boolean {\r\n return Date.now() > entry.expiry;\r\n }\r\n\r\n get(key: K): V | undefined {\r\n const entry = this.map.get(key);\r\n if (!entry) return undefined;\r\n if (this.isExpired(entry)) {\r\n this.map.delete(key);\r\n return undefined;\r\n }\r\n return entry.value;\r\n }\r\n\r\n set(key: K, value: V): void {\r\n const entry: CacheEntry<V> = {\r\n value,\r\n expiry: Date.now() + this.ttl,\r\n };\r\n this.map.set(key, entry);\r\n }\r\n\r\n has(key: K): boolean {\r\n const entry = this.map.get(key);\r\n if (!entry) return false;\r\n if (this.isExpired(entry)) {\r\n this.map.delete(key);\r\n return false;\r\n }\r\n return true;\r\n }\r\n\r\n delete(key: K): boolean {\r\n return this.map.delete(key);\r\n }\r\n\r\n clear(): void {\r\n this.map.clear();\r\n }\r\n\r\n /**\r\n * 清理所有过期条目\r\n */\r\n cleanup(): number {\r\n let cleaned = 0;\r\n this.map.forEach((entry, key) => {\r\n if (this.isExpired(entry)) {\r\n this.map.delete(key);\r\n cleaned++;\r\n }\r\n });\r\n return cleaned;\r\n }\r\n}\r\n\r\n/**\r\n * Memoize 函数返回类型\r\n */\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\nexport interface MemoizedFn<T extends (...args: any[]) => any> {\r\n (...args: Parameters<T>): ReturnType<T>;\r\n clear: () => void;\r\n}\r\n\r\n/**\r\n * 函数记忆化\r\n *\r\n * 当指定 maxSize 时,采用 FIFO(先进先出)淘汰策略:\r\n * 达到容量上限时删除最早插入的缓存条目。\r\n */\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\nexport function memoize<T extends (...args: any[]) => any>(\r\n fn: T,\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n options?: { resolver?: (...args: any[]) => string; maxSize?: number },\r\n externalCache?: Map<string, ReturnType<T>>,\r\n): MemoizedFn<T> {\r\n const internalCache = externalCache ?? new Map<string, ReturnType<T>>();\r\n const maxSize = options?.maxSize;\r\n\r\n const memoized = ((...args: Parameters<T>): ReturnType<T> => {\r\n let key: string | undefined;\r\n try {\r\n key = options?.resolver\r\n ? options.resolver(...args)\r\n : JSON.stringify(args.length === 1 ? args[0] : args);\r\n } catch {\r\n key = undefined;\r\n }\r\n if (!key) return fn(...args);\r\n if (internalCache.has(key)) {\r\n return internalCache.get(key)!;\r\n }\r\n const result = fn(...args);\r\n internalCache.set(key, result);\r\n if (maxSize && internalCache.size > maxSize) {\r\n const firstKey = internalCache.keys().next().value;\r\n if (firstKey !== undefined) internalCache.delete(firstKey);\r\n }\r\n return result;\r\n }) as MemoizedFn<T>;\r\n\r\n memoized.clear = () => {\r\n internalCache.clear();\r\n };\r\n\r\n return memoized;\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;AAkBO,IAAM,WAAN,MAAqB;AAAA,EAO1B,YAAY,OAAA,EAAiB;AAL7B,IAAA,IAAA,CAAQ,GAAA,uBAAiC,GAAA,EAAI;AAC7C,IAAA,IAAA,CAAQ,IAAA,GAA6B,IAAA;AACrC,IAAA,IAAA,CAAQ,IAAA,GAA6B,IAAA;AACrC,IAAA,IAAA,CAAQ,KAAA,GAAgB,CAAA;AAGtB,IAAA,IAAI,UAAU,CAAA,EAAG;AACf,MAAA,MAAM,IAAI,MAAM,qCAAqC,CAAA;AAAA,IACvD;AACA,IAAA,IAAA,CAAK,QAAA,GAAW,OAAA;AAAA,EAClB;AAAA,EAEA,IAAI,IAAA,GAAe;AACjB,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AAAA,EAEQ,WAAW,IAAA,EAA2B;AAC5C,IAAA,IAAI,IAAA,KAAS,KAAK,IAAA,EAAM;AACxB,IAAA,IAAA,CAAK,WAAW,IAAI,CAAA;AACpB,IAAA,IAAA,CAAK,UAAU,IAAI,CAAA;AAAA,EACrB;AAAA,EAEQ,UAAU,IAAA,EAA2B;AAC3C,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AACjB,IAAA,IAAI,KAAK,IAAA,EAAM;AACb,MAAA,IAAA,CAAK,KAAK,IAAA,GAAO,IAAA;AAAA,IACnB;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAI,CAAC,KAAK,IAAA,EAAM;AACd,MAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,IACd;AAAA,EACF;AAAA,EAEQ,WAAW,IAAA,EAA2B;AAC5C,IAAA,IAAI,KAAK,IAAA,EAAM;AACb,MAAA,IAAA,CAAK,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AAAA,IACxB,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AAAA,IACnB;AACA,IAAA,IAAI,KAAK,IAAA,EAAM;AACb,MAAA,IAAA,CAAK,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AAAA,IACxB,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AAAA,IACnB;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACd;AAAA,EAEA,IAAI,GAAA,EAAuB;AACzB,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA;AAC7B,IAAA,IAAI,CAAC,MAAM,OAAO,MAAA;AAClB,IAAA,IAAA,CAAK,WAAW,IAAI,CAAA;AACpB,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AAAA,EAEA,GAAA,CAAI,KAAQ,KAAA,EAAgB;AAC1B,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA;AACjC,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,QAAA,CAAS,KAAA,GAAQ,KAAA;AACjB,MAAA,IAAA,CAAK,WAAW,QAAQ,CAAA;AACxB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,OAAA,GAAyB;AAAA,MAC7B,GAAA;AAAA,MACA,KAAA;AAAA,MACA,IAAA,EAAM,IAAA;AAAA,MACN,IAAA,EAAM;AAAA,KACR;AACA,IAAA,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAA,EAAK,OAAO,CAAA;AACzB,IAAA,IAAA,CAAK,UAAU,OAAO,CAAA;AACtB,IAAA,IAAA,CAAK,KAAA,EAAA;AAEL,IAAA,IAAI,IAAA,CAAK,KAAA,GAAQ,IAAA,CAAK,QAAA,EAAU;AAC9B,MAAA,MAAM,UAAU,IAAA,CAAK,IAAA;AACrB,MAAA,IAAI,CAAC,OAAA,EAAS;AACd,MAAA,IAAA,CAAK,WAAW,OAAO,CAAA;AACvB,MAAA,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA;AAC3B,MAAA,IAAA,CAAK,KAAA,EAAA;AAAA,IACP;AAAA,EACF;AAAA,EAEA,IAAI,GAAA,EAAiB;AACnB,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA;AAAA,EACzB;AAAA,EAEA,OAAO,GAAA,EAAiB;AACtB,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA;AAC7B,IAAA,IAAI,CAAC,MAAM,OAAO,KAAA;AAClB,IAAA,IAAA,CAAK,WAAW,IAAI,CAAA;AACpB,IAAA,IAAA,CAAK,GAAA,CAAI,OAAO,GAAG,CAAA;AACnB,IAAA,IAAA,CAAK,KAAA,EAAA;AACL,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,IAAI,KAAA,EAAM;AACf,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,KAAA,GAAQ,CAAA;AAAA,EACf;AAAA,EAEA,QAAQ,QAAA,EAA4C;AAClD,IAAA,IAAI,UAAU,IAAA,CAAK,IAAA;AACnB,IAAA,OAAO,OAAA,EAAS;AACd,MAAA,QAAA,CAAS,OAAA,CAAQ,KAAA,EAAO,OAAA,CAAQ,GAAG,CAAA;AACnC,MAAA,OAAA,GAAU,OAAA,CAAQ,IAAA;AAAA,IACpB;AAAA,EACF;AACF;AAkBO,IAAM,gBAAN,MAA0B;AAAA,EAI/B,YAAY,KAAA,EAAe;AAF3B,IAAA,IAAA,CAAQ,GAAA,uBAAiC,GAAA,EAAI;AAG3C,IAAA,IAAI,QAAQ,CAAA,EAAG;AACb,MAAA,MAAM,IAAI,MAAM,wCAAwC,CAAA;AAAA,IAC1D;AACA,IAAA,IAAA,CAAK,GAAA,GAAM,KAAA;AAAA,EACb;AAAA,EAEA,IAAI,IAAA,GAAe;AACjB,IAAA,OAAO,KAAK,GAAA,CAAI,IAAA;AAAA,EAClB;AAAA,EAEQ,UAAU,KAAA,EAA+B;AAC/C,IAAA,OAAO,IAAA,CAAK,GAAA,EAAI,GAAI,KAAA,CAAM,MAAA;AAAA,EAC5B;AAAA,EAEA,IAAI,GAAA,EAAuB;AACzB,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA;AAC9B,IAAA,IAAI,CAAC,OAAO,OAAO,MAAA;AACnB,IAAA,IAAI,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,EAAG;AACzB,MAAA,IAAA,CAAK,GAAA,CAAI,OAAO,GAAG,CAAA;AACnB,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,OAAO,KAAA,CAAM,KAAA;AAAA,EACf;AAAA,EAEA,GAAA,CAAI,KAAQ,KAAA,EAAgB;AAC1B,IAAA,MAAM,KAAA,GAAuB;AAAA,MAC3B,KAAA;AAAA,MACA,MAAA,EAAQ,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA,CAAK;AAAA,KAC5B;AACA,IAAA,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAA,EAAK,KAAK,CAAA;AAAA,EACzB;AAAA,EAEA,IAAI,GAAA,EAAiB;AACnB,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,GAAG,CAAA;AAC9B,IAAA,IAAI,CAAC,OAAO,OAAO,KAAA;AACnB,IAAA,IAAI,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,EAAG;AACzB,MAAA,IAAA,CAAK,GAAA,CAAI,OAAO,GAAG,CAAA;AACnB,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,OAAO,GAAA,EAAiB;AACtB,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA;AAAA,EAC5B;AAAA,EAEA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,IAAI,KAAA,EAAM;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAA,GAAkB;AAChB,IAAA,IAAI,OAAA,GAAU,CAAA;AACd,IAAA,IAAA,CAAK,GAAA,CAAI,OAAA,CAAQ,CAAC,KAAA,EAAO,GAAA,KAAQ;AAC/B,MAAA,IAAI,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,EAAG;AACzB,QAAA,IAAA,CAAK,GAAA,CAAI,OAAO,GAAG,CAAA;AACnB,QAAA,OAAA,EAAA;AAAA,MACF;AAAA,IACF,CAAC,CAAA;AACD,IAAA,OAAO,OAAA;AAAA,EACT;AACF;AAkBO,SAAS,OAAA,CACd,EAAA,EAEA,OAAA,EACA,aAAA,EACe;AACf,EAAA,MAAM,aAAA,GAAgB,aAAA,oBAAiB,IAAI,GAAA,EAA2B;AACtE,EAAA,MAAM,UAAU,OAAA,EAAS,OAAA;AAEzB,EAAA,MAAM,QAAA,IAAY,IAAI,IAAA,KAAuC;AAC3D,IAAA,IAAI,GAAA;AACJ,IAAA,IAAI;AACF,MAAA,GAAA,GAAM,OAAA,EAAS,QAAA,GACX,OAAA,CAAQ,QAAA,CAAS,GAAG,IAAI,CAAA,GACxB,IAAA,CAAK,SAAA,CAAU,KAAK,MAAA,KAAW,CAAA,GAAI,IAAA,CAAK,CAAC,IAAI,IAAI,CAAA;AAAA,IACvD,CAAA,CAAA,MAAQ;AACN,MAAA,GAAA,GAAM,MAAA;AAAA,IACR;AACA,IAAA,IAAI,CAAC,GAAA,EAAK,OAAO,EAAA,CAAG,GAAG,IAAI,CAAA;AAC3B,IAAA,IAAI,aAAA,CAAc,GAAA,CAAI,GAAG,CAAA,EAAG;AAC1B,MAAA,OAAO,aAAA,CAAc,IAAI,GAAG,CAAA;AAAA,IAC9B;AACA,IAAA,MAAM,MAAA,GAAS,EAAA,CAAG,GAAG,IAAI,CAAA;AACzB,IAAA,aAAA,CAAc,GAAA,CAAI,KAAK,MAAM,CAAA;AAC7B,IAAA,IAAI,OAAA,IAAW,aAAA,CAAc,IAAA,GAAO,OAAA,EAAS;AAC3C,MAAA,MAAM,QAAA,GAAW,aAAA,CAAc,IAAA,EAAK,CAAE,MAAK,CAAE,KAAA;AAC7C,MAAA,IAAI,QAAA,KAAa,MAAA,EAAW,aAAA,CAAc,MAAA,CAAO,QAAQ,CAAA;AAAA,IAC3D;AACA,IAAA,OAAO,MAAA;AAAA,EACT,CAAA,CAAA;AAEA,EAAA,QAAA,CAAS,QAAQ,MAAM;AACrB,IAAA,aAAA,CAAc,KAAA,EAAM;AAAA,EACtB,CAAA;AAEA,EAAA,OAAO,QAAA;AACT","file":"index.cjs","sourcesContent":["/**\n * @lytjs/common-cache\n * 缓存策略工具\n */\n\n/**\n * LRU 缓存节点\n */\nexport interface LRUNode<K, V> {\n key: K;\n value: V;\n prev: LRUNode<K, V> | null;\n next: LRUNode<K, V> | null;\n}\n\n/**\n * LRU(最近最少使用)缓存\n */\nexport class LRUCache<K, V> {\n private capacity: number;\n private map: Map<K, LRUNode<K, V>> = new Map();\n private head: LRUNode<K, V> | null = null;\n private tail: LRUNode<K, V> | null = null;\n private _size: number = 0;\n\n constructor(maxSize: number) {\n if (maxSize < 1) {\n throw new Error('LRUCache maxSize must be at least 1');\n }\n this.capacity = maxSize;\n }\n\n get size(): number {\n return this._size;\n }\n\n private moveToHead(node: LRUNode<K, V>): void {\n if (node === this.head) return;\n this.removeNode(node);\n this.addToHead(node);\n }\n\n private addToHead(node: LRUNode<K, V>): void {\n node.prev = null;\n node.next = this.head;\n if (this.head) {\n this.head.prev = node;\n }\n this.head = node;\n if (!this.tail) {\n this.tail = node;\n }\n }\n\n private removeNode(node: LRUNode<K, V>): void {\n if (node.prev) {\n node.prev.next = node.next;\n } else {\n this.head = node.next;\n }\n if (node.next) {\n node.next.prev = node.prev;\n } else {\n this.tail = node.prev;\n }\n node.prev = null;\n node.next = null;\n }\n\n get(key: K): V | undefined {\n const node = this.map.get(key);\n if (!node) return undefined;\n this.moveToHead(node);\n return node.value;\n }\n\n set(key: K, value: V): void {\n const existing = this.map.get(key);\n if (existing) {\n existing.value = value;\n this.moveToHead(existing);\n return;\n }\n\n const newNode: LRUNode<K, V> = {\n key,\n value,\n prev: null,\n next: null,\n };\n this.map.set(key, newNode);\n this.addToHead(newNode);\n this._size++;\n\n if (this._size > this.capacity) {\n const removed = this.tail;\n if (!removed) return;\n this.removeNode(removed);\n this.map.delete(removed.key);\n this._size--;\n }\n }\n\n has(key: K): boolean {\n return this.map.has(key);\n }\n\n delete(key: K): boolean {\n const node = this.map.get(key);\n if (!node) return false;\n this.removeNode(node);\n this.map.delete(key);\n this._size--;\n return true;\n }\n\n clear(): void {\n this.map.clear();\n this.head = null;\n this.tail = null;\n this._size = 0;\n }\n\n forEach(callback: (value: V, key: K) => void): void {\n let current = this.head;\n while (current) {\n callback(current.value, current.key);\n current = current.next;\n }\n }\n}\n\n/**\n * 缓存条目\n */\nexport interface CacheEntry<V> {\n value: V;\n expiry: number;\n}\n\n/**\n * 带过期时间的缓存\n *\n * @note 此实现采用惰性删除策略:过期条目仅在 get() / has() 访问时被移除,\n * 不会主动扫描清理。如果存在大量写入后不再访问的过期条目,\n * 它们将一直占用内存直到调用 cleanup() 或 clear()。\n * 对于写入密集但读取稀疏的场景,建议定期调用 cleanup() 释放内存。\n */\nexport class ExpiringCache<K, V> {\n private ttl: number;\n private map: Map<K, CacheEntry<V>> = new Map();\n\n constructor(ttlMs: number) {\n if (ttlMs < 1) {\n throw new Error('ExpiringCache TTL must be at least 1ms');\n }\n this.ttl = ttlMs;\n }\n\n get size(): number {\n return this.map.size;\n }\n\n private isExpired(entry: CacheEntry<V>): boolean {\n return Date.now() > entry.expiry;\n }\n\n get(key: K): V | undefined {\n const entry = this.map.get(key);\n if (!entry) return undefined;\n if (this.isExpired(entry)) {\n this.map.delete(key);\n return undefined;\n }\n return entry.value;\n }\n\n set(key: K, value: V): void {\n const entry: CacheEntry<V> = {\n value,\n expiry: Date.now() + this.ttl,\n };\n this.map.set(key, entry);\n }\n\n has(key: K): boolean {\n const entry = this.map.get(key);\n if (!entry) return false;\n if (this.isExpired(entry)) {\n this.map.delete(key);\n return false;\n }\n return true;\n }\n\n delete(key: K): boolean {\n return this.map.delete(key);\n }\n\n clear(): void {\n this.map.clear();\n }\n\n /**\n * 清理所有过期条目\n */\n cleanup(): number {\n let cleaned = 0;\n this.map.forEach((entry, key) => {\n if (this.isExpired(entry)) {\n this.map.delete(key);\n cleaned++;\n }\n });\n return cleaned;\n }\n}\n\n/**\n * Memoize 函数返回类型\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport interface MemoizedFn<T extends (...args: any[]) => any> {\n (...args: Parameters<T>): ReturnType<T>;\n clear: () => void;\n}\n\n/**\n * 函数记忆化\n *\n * 当指定 maxSize 时,采用 FIFO(先进先出)淘汰策略:\n * 达到容量上限时删除最早插入的缓存条目。\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function memoize<T extends (...args: any[]) => any>(\n fn: T,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n options?: { resolver?: (...args: any[]) => string; maxSize?: number },\n externalCache?: Map<string, ReturnType<T>>,\n): MemoizedFn<T> {\n const internalCache = externalCache ?? new Map<string, ReturnType<T>>();\n const maxSize = options?.maxSize;\n\n const memoized = ((...args: Parameters<T>): ReturnType<T> => {\n let key: string | undefined;\n try {\n key = options?.resolver\n ? options.resolver(...args)\n : JSON.stringify(args.length === 1 ? args[0] : args);\n } catch {\n key = undefined;\n }\n if (!key) return fn(...args);\n if (internalCache.has(key)) {\n return internalCache.get(key)!;\n }\n const result = fn(...args);\n internalCache.set(key, result);\n if (maxSize && internalCache.size > maxSize) {\n const firstKey = internalCache.keys().next().value;\n if (firstKey !== undefined) internalCache.delete(firstKey);\n }\n return result;\n }) as MemoizedFn<T>;\n\n memoized.clear = () => {\n internalCache.clear();\n };\n\n return memoized;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\r\n * @lytjs/common-cache\r\n * 缓存策略工具\r\n */\r\n\r\n/**\r\n * LRU 缓存节点\r\n */\r\nexport interface LRUNode<K, V> {\r\n key: K;\r\n value: V;\r\n prev: LRUNode<K, V> | null;\r\n next: LRUNode<K, V> | null;\r\n}\r\n\r\n/**\r\n * LRU(最近最少使用)缓存\r\n */\r\nexport class LRUCache<K, V> {\r\n private capacity: number;\r\n private map: Map<K, LRUNode<K, V>> = new Map();\r\n private head: LRUNode<K, V> | null = null;\r\n private tail: LRUNode<K, V> | null = null;\r\n private _size: number = 0;\r\n\r\n constructor(maxSize: number) {\r\n if (maxSize < 1) {\r\n throw new Error('LRUCache maxSize must be at least 1');\r\n }\r\n this.capacity = maxSize;\r\n }\r\n\r\n get size(): number {\r\n return this._size;\r\n }\r\n\r\n private moveToHead(node: LRUNode<K, V>): void {\r\n if (node === this.head) return;\r\n this.removeNode(node);\r\n this.addToHead(node);\r\n }\r\n\r\n private addToHead(node: LRUNode<K, V>): void {\r\n node.prev = null;\r\n node.next = this.head;\r\n if (this.head) {\r\n this.head.prev = node;\r\n }\r\n this.head = node;\r\n if (!this.tail) {\r\n this.tail = node;\r\n }\r\n }\r\n\r\n private removeNode(node: LRUNode<K, V>): void {\r\n if (node.prev) {\r\n node.prev.next = node.next;\r\n } else {\r\n this.head = node.next;\r\n }\r\n if (node.next) {\r\n node.next.prev = node.prev;\r\n } else {\r\n this.tail = node.prev;\r\n }\r\n node.prev = null;\r\n node.next = null;\r\n }\r\n\r\n get(key: K): V | undefined {\r\n const node = this.map.get(key);\r\n if (!node) return undefined;\r\n this.moveToHead(node);\r\n return node.value;\r\n }\r\n\r\n set(key: K, value: V): void {\r\n const existing = this.map.get(key);\r\n if (existing) {\r\n existing.value = value;\r\n this.moveToHead(existing);\r\n return;\r\n }\r\n\r\n const newNode: LRUNode<K, V> = {\r\n key,\r\n value,\r\n prev: null,\r\n next: null,\r\n };\r\n this.map.set(key, newNode);\r\n this.addToHead(newNode);\r\n this._size++;\r\n\r\n if (this._size > this.capacity) {\r\n const removed = this.tail;\r\n if (!removed) return;\r\n this.removeNode(removed);\r\n this.map.delete(removed.key);\r\n this._size--;\r\n }\r\n }\r\n\r\n has(key: K): boolean {\r\n return this.map.has(key);\r\n }\r\n\r\n delete(key: K): boolean {\r\n const node = this.map.get(key);\r\n if (!node) return false;\r\n this.removeNode(node);\r\n this.map.delete(key);\r\n this._size--;\r\n return true;\r\n }\r\n\r\n clear(): void {\r\n this.map.clear();\r\n this.head = null;\r\n this.tail = null;\r\n this._size = 0;\r\n }\r\n\r\n forEach(callback: (value: V, key: K) => void): void {\r\n let current = this.head;\r\n while (current) {\r\n callback(current.value, current.key);\r\n current = current.next;\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 缓存条目\r\n */\r\nexport interface CacheEntry<V> {\r\n value: V;\r\n expiry: number;\r\n}\r\n\r\n/**\r\n * 带过期时间的缓存\r\n *\r\n * @note 此实现采用惰性删除策略:过期条目仅在 get() / has() 访问时被移除,\r\n * 不会主动扫描清理。如果存在大量写入后不再访问的过期条目,\r\n * 它们将一直占用内存直到调用 cleanup() 或 clear()。\r\n * 对于写入密集但读取稀疏的场景,建议定期调用 cleanup() 释放内存。\r\n */\r\nexport class ExpiringCache<K, V> {\r\n private ttl: number;\r\n private map: Map<K, CacheEntry<V>> = new Map();\r\n\r\n constructor(ttlMs: number) {\r\n if (ttlMs < 1) {\r\n throw new Error('ExpiringCache TTL must be at least 1ms');\r\n }\r\n this.ttl = ttlMs;\r\n }\r\n\r\n get size(): number {\r\n return this.map.size;\r\n }\r\n\r\n private isExpired(entry: CacheEntry<V>): boolean {\r\n return Date.now() > entry.expiry;\r\n }\r\n\r\n get(key: K): V | undefined {\r\n const entry = this.map.get(key);\r\n if (!entry) return undefined;\r\n if (this.isExpired(entry)) {\r\n this.map.delete(key);\r\n return undefined;\r\n }\r\n return entry.value;\r\n }\r\n\r\n set(key: K, value: V): void {\r\n const entry: CacheEntry<V> = {\r\n value,\r\n expiry: Date.now() + this.ttl,\r\n };\r\n this.map.set(key, entry);\r\n }\r\n\r\n has(key: K): boolean {\r\n const entry = this.map.get(key);\r\n if (!entry) return false;\r\n if (this.isExpired(entry)) {\r\n this.map.delete(key);\r\n return false;\r\n }\r\n return true;\r\n }\r\n\r\n delete(key: K): boolean {\r\n return this.map.delete(key);\r\n }\r\n\r\n clear(): void {\r\n this.map.clear();\r\n }\r\n\r\n /**\r\n * 清理所有过期条目\r\n */\r\n cleanup(): number {\r\n let cleaned = 0;\r\n this.map.forEach((entry, key) => {\r\n if (this.isExpired(entry)) {\r\n this.map.delete(key);\r\n cleaned++;\r\n }\r\n });\r\n return cleaned;\r\n }\r\n}\r\n\r\n/**\r\n * Memoize 函数返回类型\r\n */\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\nexport interface MemoizedFn<T extends (...args: any[]) => any> {\r\n (...args: Parameters<T>): ReturnType<T>;\r\n clear: () => void;\r\n}\r\n\r\n/**\r\n * 函数记忆化\r\n *\r\n * 当指定 maxSize 时,采用 FIFO(先进先出)淘汰策略:\r\n * 达到容量上限时删除最早插入的缓存条目。\r\n */\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\nexport function memoize<T extends (...args: any[]) => any>(\r\n fn: T,\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n options?: { resolver?: (...args: any[]) => string; maxSize?: number },\r\n externalCache?: Map<string, ReturnType<T>>,\r\n): MemoizedFn<T> {\r\n const internalCache = externalCache ?? new Map<string, ReturnType<T>>();\r\n const maxSize = options?.maxSize;\r\n\r\n const memoized = ((...args: Parameters<T>): ReturnType<T> => {\r\n let key: string | undefined;\r\n try {\r\n key = options?.resolver\r\n ? options.resolver(...args)\r\n : JSON.stringify(args.length === 1 ? args[0] : args);\r\n } catch {\r\n key = undefined;\r\n }\r\n if (!key) return fn(...args);\r\n if (internalCache.has(key)) {\r\n return internalCache.get(key)!;\r\n }\r\n const result = fn(...args);\r\n internalCache.set(key, result);\r\n if (maxSize && internalCache.size > maxSize) {\r\n const firstKey = internalCache.keys().next().value;\r\n if (firstKey !== undefined) internalCache.delete(firstKey);\r\n }\r\n return result;\r\n }) as MemoizedFn<T>;\r\n\r\n memoized.clear = () => {\r\n internalCache.clear();\r\n };\r\n\r\n return memoized;\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @lytjs/common-cache\n * 缓存策略工具\n */\n\n/**\n * LRU 缓存节点\n */\nexport interface LRUNode<K, V> {\n key: K;\n value: V;\n prev: LRUNode<K, V> | null;\n next: LRUNode<K, V> | null;\n}\n\n/**\n * LRU(最近最少使用)缓存\n */\nexport class LRUCache<K, V> {\n private capacity: number;\n private map: Map<K, LRUNode<K, V>> = new Map();\n private head: LRUNode<K, V> | null = null;\n private tail: LRUNode<K, V> | null = null;\n private _size: number = 0;\n\n constructor(maxSize: number) {\n if (maxSize < 1) {\n throw new Error('LRUCache maxSize must be at least 1');\n }\n this.capacity = maxSize;\n }\n\n get size(): number {\n return this._size;\n }\n\n private moveToHead(node: LRUNode<K, V>): void {\n if (node === this.head) return;\n this.removeNode(node);\n this.addToHead(node);\n }\n\n private addToHead(node: LRUNode<K, V>): void {\n node.prev = null;\n node.next = this.head;\n if (this.head) {\n this.head.prev = node;\n }\n this.head = node;\n if (!this.tail) {\n this.tail = node;\n }\n }\n\n private removeNode(node: LRUNode<K, V>): void {\n if (node.prev) {\n node.prev.next = node.next;\n } else {\n this.head = node.next;\n }\n if (node.next) {\n node.next.prev = node.prev;\n } else {\n this.tail = node.prev;\n }\n node.prev = null;\n node.next = null;\n }\n\n get(key: K): V | undefined {\n const node = this.map.get(key);\n if (!node) return undefined;\n this.moveToHead(node);\n return node.value;\n }\n\n set(key: K, value: V): void {\n const existing = this.map.get(key);\n if (existing) {\n existing.value = value;\n this.moveToHead(existing);\n return;\n }\n\n const newNode: LRUNode<K, V> = {\n key,\n value,\n prev: null,\n next: null,\n };\n this.map.set(key, newNode);\n this.addToHead(newNode);\n this._size++;\n\n if (this._size > this.capacity) {\n const removed = this.tail;\n if (!removed) return;\n this.removeNode(removed);\n this.map.delete(removed.key);\n this._size--;\n }\n }\n\n has(key: K): boolean {\n return this.map.has(key);\n }\n\n delete(key: K): boolean {\n const node = this.map.get(key);\n if (!node) return false;\n this.removeNode(node);\n this.map.delete(key);\n this._size--;\n return true;\n }\n\n clear(): void {\n this.map.clear();\n this.head = null;\n this.tail = null;\n this._size = 0;\n }\n\n forEach(callback: (value: V, key: K) => void): void {\n let current = this.head;\n while (current) {\n callback(current.value, current.key);\n current = current.next;\n }\n }\n}\n\n/**\n * 缓存条目\n */\nexport interface CacheEntry<V> {\n value: V;\n expiry: number;\n}\n\n/**\n * 带过期时间的缓存\n *\n * @note 此实现采用惰性删除策略:过期条目仅在 get() / has() 访问时被移除,\n * 不会主动扫描清理。如果存在大量写入后不再访问的过期条目,\n * 它们将一直占用内存直到调用 cleanup() 或 clear()。\n * 对于写入密集但读取稀疏的场景,建议定期调用 cleanup() 释放内存。\n */\nexport class ExpiringCache<K, V> {\n private ttl: number;\n private map: Map<K, CacheEntry<V>> = new Map();\n\n constructor(ttlMs: number) {\n if (ttlMs < 1) {\n throw new Error('ExpiringCache TTL must be at least 1ms');\n }\n this.ttl = ttlMs;\n }\n\n get size(): number {\n return this.map.size;\n }\n\n private isExpired(entry: CacheEntry<V>): boolean {\n return Date.now() > entry.expiry;\n }\n\n get(key: K): V | undefined {\n const entry = this.map.get(key);\n if (!entry) return undefined;\n if (this.isExpired(entry)) {\n this.map.delete(key);\n return undefined;\n }\n return entry.value;\n }\n\n set(key: K, value: V): void {\n const entry: CacheEntry<V> = {\n value,\n expiry: Date.now() + this.ttl,\n };\n this.map.set(key, entry);\n }\n\n has(key: K): boolean {\n const entry = this.map.get(key);\n if (!entry) return false;\n if (this.isExpired(entry)) {\n this.map.delete(key);\n return false;\n }\n return true;\n }\n\n delete(key: K): boolean {\n return this.map.delete(key);\n }\n\n clear(): void {\n this.map.clear();\n }\n\n /**\n * 清理所有过期条目\n */\n cleanup(): number {\n let cleaned = 0;\n this.map.forEach((entry, key) => {\n if (this.isExpired(entry)) {\n this.map.delete(key);\n cleaned++;\n }\n });\n return cleaned;\n }\n}\n\n/**\n * Memoize 函数返回类型\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport interface MemoizedFn<T extends (...args: any[]) => any> {\n (...args: Parameters<T>): ReturnType<T>;\n clear: () => void;\n}\n\n/**\n * 函数记忆化\n *\n * 当指定 maxSize 时,采用 FIFO(先进先出)淘汰策略:\n * 达到容量上限时删除最早插入的缓存条目。\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function memoize<T extends (...args: any[]) => any>(\n fn: T,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n options?: { resolver?: (...args: any[]) => string; maxSize?: number },\n externalCache?: Map<string, ReturnType<T>>,\n): MemoizedFn<T> {\n const internalCache = externalCache ?? new Map<string, ReturnType<T>>();\n const maxSize = options?.maxSize;\n\n const memoized = ((...args: Parameters<T>): ReturnType<T> => {\n let key: string | undefined;\n try {\n key = options?.resolver\n ? options.resolver(...args)\n : JSON.stringify(args.length === 1 ? args[0] : args);\n } catch {\n key = undefined;\n }\n if (!key) return fn(...args);\n if (internalCache.has(key)) {\n return internalCache.get(key)!;\n }\n const result = fn(...args);\n internalCache.set(key, result);\n if (maxSize && internalCache.size > maxSize) {\n const firstKey = internalCache.keys().next().value;\n if (firstKey !== undefined) internalCache.delete(firstKey);\n }\n return result;\n }) as MemoizedFn<T>;\n\n memoized.clear = () => {\n internalCache.clear();\n };\n\n return memoized;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lytjs/common-cache",
3
- "version": "6.5.0",
3
+ "version": "6.6.0",
4
4
  "description": "Caching strategies for LytJS",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
package/src/index.ts CHANGED
@@ -1,271 +1,271 @@
1
- /**
2
- * @lytjs/common-cache
3
- * 缓存策略工具
4
- */
5
-
6
- /**
7
- * LRU 缓存节点
8
- */
9
- export interface LRUNode<K, V> {
10
- key: K;
11
- value: V;
12
- prev: LRUNode<K, V> | null;
13
- next: LRUNode<K, V> | null;
14
- }
15
-
16
- /**
17
- * LRU(最近最少使用)缓存
18
- */
19
- export class LRUCache<K, V> {
20
- private capacity: number;
21
- private map: Map<K, LRUNode<K, V>> = new Map();
22
- private head: LRUNode<K, V> | null = null;
23
- private tail: LRUNode<K, V> | null = null;
24
- private _size: number = 0;
25
-
26
- constructor(maxSize: number) {
27
- if (maxSize < 1) {
28
- throw new Error('LRUCache maxSize must be at least 1');
29
- }
30
- this.capacity = maxSize;
31
- }
32
-
33
- get size(): number {
34
- return this._size;
35
- }
36
-
37
- private moveToHead(node: LRUNode<K, V>): void {
38
- if (node === this.head) return;
39
- this.removeNode(node);
40
- this.addToHead(node);
41
- }
42
-
43
- private addToHead(node: LRUNode<K, V>): void {
44
- node.prev = null;
45
- node.next = this.head;
46
- if (this.head) {
47
- this.head.prev = node;
48
- }
49
- this.head = node;
50
- if (!this.tail) {
51
- this.tail = node;
52
- }
53
- }
54
-
55
- private removeNode(node: LRUNode<K, V>): void {
56
- if (node.prev) {
57
- node.prev.next = node.next;
58
- } else {
59
- this.head = node.next;
60
- }
61
- if (node.next) {
62
- node.next.prev = node.prev;
63
- } else {
64
- this.tail = node.prev;
65
- }
66
- node.prev = null;
67
- node.next = null;
68
- }
69
-
70
- get(key: K): V | undefined {
71
- const node = this.map.get(key);
72
- if (!node) return undefined;
73
- this.moveToHead(node);
74
- return node.value;
75
- }
76
-
77
- set(key: K, value: V): void {
78
- const existing = this.map.get(key);
79
- if (existing) {
80
- existing.value = value;
81
- this.moveToHead(existing);
82
- return;
83
- }
84
-
85
- const newNode: LRUNode<K, V> = {
86
- key,
87
- value,
88
- prev: null,
89
- next: null,
90
- };
91
- this.map.set(key, newNode);
92
- this.addToHead(newNode);
93
- this._size++;
94
-
95
- if (this._size > this.capacity) {
96
- const removed = this.tail;
97
- if (!removed) return;
98
- this.removeNode(removed);
99
- this.map.delete(removed.key);
100
- this._size--;
101
- }
102
- }
103
-
104
- has(key: K): boolean {
105
- return this.map.has(key);
106
- }
107
-
108
- delete(key: K): boolean {
109
- const node = this.map.get(key);
110
- if (!node) return false;
111
- this.removeNode(node);
112
- this.map.delete(key);
113
- this._size--;
114
- return true;
115
- }
116
-
117
- clear(): void {
118
- this.map.clear();
119
- this.head = null;
120
- this.tail = null;
121
- this._size = 0;
122
- }
123
-
124
- forEach(callback: (value: V, key: K) => void): void {
125
- let current = this.head;
126
- while (current) {
127
- callback(current.value, current.key);
128
- current = current.next;
129
- }
130
- }
131
- }
132
-
133
- /**
134
- * 缓存条目
135
- */
136
- export interface CacheEntry<V> {
137
- value: V;
138
- expiry: number;
139
- }
140
-
141
- /**
142
- * 带过期时间的缓存
143
- *
144
- * @note 此实现采用惰性删除策略:过期条目仅在 get() / has() 访问时被移除,
145
- * 不会主动扫描清理。如果存在大量写入后不再访问的过期条目,
146
- * 它们将一直占用内存直到调用 cleanup() 或 clear()。
147
- * 对于写入密集但读取稀疏的场景,建议定期调用 cleanup() 释放内存。
148
- */
149
- export class ExpiringCache<K, V> {
150
- private ttl: number;
151
- private map: Map<K, CacheEntry<V>> = new Map();
152
-
153
- constructor(ttlMs: number) {
154
- if (ttlMs < 1) {
155
- throw new Error('ExpiringCache TTL must be at least 1ms');
156
- }
157
- this.ttl = ttlMs;
158
- }
159
-
160
- get size(): number {
161
- return this.map.size;
162
- }
163
-
164
- private isExpired(entry: CacheEntry<V>): boolean {
165
- return Date.now() > entry.expiry;
166
- }
167
-
168
- get(key: K): V | undefined {
169
- const entry = this.map.get(key);
170
- if (!entry) return undefined;
171
- if (this.isExpired(entry)) {
172
- this.map.delete(key);
173
- return undefined;
174
- }
175
- return entry.value;
176
- }
177
-
178
- set(key: K, value: V): void {
179
- const entry: CacheEntry<V> = {
180
- value,
181
- expiry: Date.now() + this.ttl,
182
- };
183
- this.map.set(key, entry);
184
- }
185
-
186
- has(key: K): boolean {
187
- const entry = this.map.get(key);
188
- if (!entry) return false;
189
- if (this.isExpired(entry)) {
190
- this.map.delete(key);
191
- return false;
192
- }
193
- return true;
194
- }
195
-
196
- delete(key: K): boolean {
197
- return this.map.delete(key);
198
- }
199
-
200
- clear(): void {
201
- this.map.clear();
202
- }
203
-
204
- /**
205
- * 清理所有过期条目
206
- */
207
- cleanup(): number {
208
- let cleaned = 0;
209
- this.map.forEach((entry, key) => {
210
- if (this.isExpired(entry)) {
211
- this.map.delete(key);
212
- cleaned++;
213
- }
214
- });
215
- return cleaned;
216
- }
217
- }
218
-
219
- /**
220
- * Memoize 函数返回类型
221
- */
222
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
223
- export interface MemoizedFn<T extends (...args: any[]) => any> {
224
- (...args: Parameters<T>): ReturnType<T>;
225
- clear: () => void;
226
- }
227
-
228
- /**
229
- * 函数记忆化
230
- *
231
- * 当指定 maxSize 时,采用 FIFO(先进先出)淘汰策略:
232
- * 达到容量上限时删除最早插入的缓存条目。
233
- */
234
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
235
- export function memoize<T extends (...args: any[]) => any>(
236
- fn: T,
237
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
238
- options?: { resolver?: (...args: any[]) => string; maxSize?: number },
239
- externalCache?: Map<string, ReturnType<T>>,
240
- ): MemoizedFn<T> {
241
- const internalCache = externalCache ?? new Map<string, ReturnType<T>>();
242
- const maxSize = options?.maxSize;
243
-
244
- const memoized = ((...args: Parameters<T>): ReturnType<T> => {
245
- let key: string | undefined;
246
- try {
247
- key = options?.resolver
248
- ? options.resolver(...args)
249
- : JSON.stringify(args.length === 1 ? args[0] : args);
250
- } catch {
251
- key = undefined;
252
- }
253
- if (!key) return fn(...args);
254
- if (internalCache.has(key)) {
255
- return internalCache.get(key)!;
256
- }
257
- const result = fn(...args);
258
- internalCache.set(key, result);
259
- if (maxSize && internalCache.size > maxSize) {
260
- const firstKey = internalCache.keys().next().value;
261
- if (firstKey !== undefined) internalCache.delete(firstKey);
262
- }
263
- return result;
264
- }) as MemoizedFn<T>;
265
-
266
- memoized.clear = () => {
267
- internalCache.clear();
268
- };
269
-
270
- return memoized;
271
- }
1
+ /**
2
+ * @lytjs/common-cache
3
+ * 缓存策略工具
4
+ */
5
+
6
+ /**
7
+ * LRU 缓存节点
8
+ */
9
+ export interface LRUNode<K, V> {
10
+ key: K;
11
+ value: V;
12
+ prev: LRUNode<K, V> | null;
13
+ next: LRUNode<K, V> | null;
14
+ }
15
+
16
+ /**
17
+ * LRU(最近最少使用)缓存
18
+ */
19
+ export class LRUCache<K, V> {
20
+ private capacity: number;
21
+ private map: Map<K, LRUNode<K, V>> = new Map();
22
+ private head: LRUNode<K, V> | null = null;
23
+ private tail: LRUNode<K, V> | null = null;
24
+ private _size: number = 0;
25
+
26
+ constructor(maxSize: number) {
27
+ if (maxSize < 1) {
28
+ throw new Error('LRUCache maxSize must be at least 1');
29
+ }
30
+ this.capacity = maxSize;
31
+ }
32
+
33
+ get size(): number {
34
+ return this._size;
35
+ }
36
+
37
+ private moveToHead(node: LRUNode<K, V>): void {
38
+ if (node === this.head) return;
39
+ this.removeNode(node);
40
+ this.addToHead(node);
41
+ }
42
+
43
+ private addToHead(node: LRUNode<K, V>): void {
44
+ node.prev = null;
45
+ node.next = this.head;
46
+ if (this.head) {
47
+ this.head.prev = node;
48
+ }
49
+ this.head = node;
50
+ if (!this.tail) {
51
+ this.tail = node;
52
+ }
53
+ }
54
+
55
+ private removeNode(node: LRUNode<K, V>): void {
56
+ if (node.prev) {
57
+ node.prev.next = node.next;
58
+ } else {
59
+ this.head = node.next;
60
+ }
61
+ if (node.next) {
62
+ node.next.prev = node.prev;
63
+ } else {
64
+ this.tail = node.prev;
65
+ }
66
+ node.prev = null;
67
+ node.next = null;
68
+ }
69
+
70
+ get(key: K): V | undefined {
71
+ const node = this.map.get(key);
72
+ if (!node) return undefined;
73
+ this.moveToHead(node);
74
+ return node.value;
75
+ }
76
+
77
+ set(key: K, value: V): void {
78
+ const existing = this.map.get(key);
79
+ if (existing) {
80
+ existing.value = value;
81
+ this.moveToHead(existing);
82
+ return;
83
+ }
84
+
85
+ const newNode: LRUNode<K, V> = {
86
+ key,
87
+ value,
88
+ prev: null,
89
+ next: null,
90
+ };
91
+ this.map.set(key, newNode);
92
+ this.addToHead(newNode);
93
+ this._size++;
94
+
95
+ if (this._size > this.capacity) {
96
+ const removed = this.tail;
97
+ if (!removed) return;
98
+ this.removeNode(removed);
99
+ this.map.delete(removed.key);
100
+ this._size--;
101
+ }
102
+ }
103
+
104
+ has(key: K): boolean {
105
+ return this.map.has(key);
106
+ }
107
+
108
+ delete(key: K): boolean {
109
+ const node = this.map.get(key);
110
+ if (!node) return false;
111
+ this.removeNode(node);
112
+ this.map.delete(key);
113
+ this._size--;
114
+ return true;
115
+ }
116
+
117
+ clear(): void {
118
+ this.map.clear();
119
+ this.head = null;
120
+ this.tail = null;
121
+ this._size = 0;
122
+ }
123
+
124
+ forEach(callback: (value: V, key: K) => void): void {
125
+ let current = this.head;
126
+ while (current) {
127
+ callback(current.value, current.key);
128
+ current = current.next;
129
+ }
130
+ }
131
+ }
132
+
133
+ /**
134
+ * 缓存条目
135
+ */
136
+ export interface CacheEntry<V> {
137
+ value: V;
138
+ expiry: number;
139
+ }
140
+
141
+ /**
142
+ * 带过期时间的缓存
143
+ *
144
+ * @note 此实现采用惰性删除策略:过期条目仅在 get() / has() 访问时被移除,
145
+ * 不会主动扫描清理。如果存在大量写入后不再访问的过期条目,
146
+ * 它们将一直占用内存直到调用 cleanup() 或 clear()。
147
+ * 对于写入密集但读取稀疏的场景,建议定期调用 cleanup() 释放内存。
148
+ */
149
+ export class ExpiringCache<K, V> {
150
+ private ttl: number;
151
+ private map: Map<K, CacheEntry<V>> = new Map();
152
+
153
+ constructor(ttlMs: number) {
154
+ if (ttlMs < 1) {
155
+ throw new Error('ExpiringCache TTL must be at least 1ms');
156
+ }
157
+ this.ttl = ttlMs;
158
+ }
159
+
160
+ get size(): number {
161
+ return this.map.size;
162
+ }
163
+
164
+ private isExpired(entry: CacheEntry<V>): boolean {
165
+ return Date.now() > entry.expiry;
166
+ }
167
+
168
+ get(key: K): V | undefined {
169
+ const entry = this.map.get(key);
170
+ if (!entry) return undefined;
171
+ if (this.isExpired(entry)) {
172
+ this.map.delete(key);
173
+ return undefined;
174
+ }
175
+ return entry.value;
176
+ }
177
+
178
+ set(key: K, value: V): void {
179
+ const entry: CacheEntry<V> = {
180
+ value,
181
+ expiry: Date.now() + this.ttl,
182
+ };
183
+ this.map.set(key, entry);
184
+ }
185
+
186
+ has(key: K): boolean {
187
+ const entry = this.map.get(key);
188
+ if (!entry) return false;
189
+ if (this.isExpired(entry)) {
190
+ this.map.delete(key);
191
+ return false;
192
+ }
193
+ return true;
194
+ }
195
+
196
+ delete(key: K): boolean {
197
+ return this.map.delete(key);
198
+ }
199
+
200
+ clear(): void {
201
+ this.map.clear();
202
+ }
203
+
204
+ /**
205
+ * 清理所有过期条目
206
+ */
207
+ cleanup(): number {
208
+ let cleaned = 0;
209
+ this.map.forEach((entry, key) => {
210
+ if (this.isExpired(entry)) {
211
+ this.map.delete(key);
212
+ cleaned++;
213
+ }
214
+ });
215
+ return cleaned;
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Memoize 函数返回类型
221
+ */
222
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
223
+ export interface MemoizedFn<T extends (...args: any[]) => any> {
224
+ (...args: Parameters<T>): ReturnType<T>;
225
+ clear: () => void;
226
+ }
227
+
228
+ /**
229
+ * 函数记忆化
230
+ *
231
+ * 当指定 maxSize 时,采用 FIFO(先进先出)淘汰策略:
232
+ * 达到容量上限时删除最早插入的缓存条目。
233
+ */
234
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
235
+ export function memoize<T extends (...args: any[]) => any>(
236
+ fn: T,
237
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
238
+ options?: { resolver?: (...args: any[]) => string; maxSize?: number },
239
+ externalCache?: Map<string, ReturnType<T>>,
240
+ ): MemoizedFn<T> {
241
+ const internalCache = externalCache ?? new Map<string, ReturnType<T>>();
242
+ const maxSize = options?.maxSize;
243
+
244
+ const memoized = ((...args: Parameters<T>): ReturnType<T> => {
245
+ let key: string | undefined;
246
+ try {
247
+ key = options?.resolver
248
+ ? options.resolver(...args)
249
+ : JSON.stringify(args.length === 1 ? args[0] : args);
250
+ } catch {
251
+ key = undefined;
252
+ }
253
+ if (!key) return fn(...args);
254
+ if (internalCache.has(key)) {
255
+ return internalCache.get(key)!;
256
+ }
257
+ const result = fn(...args);
258
+ internalCache.set(key, result);
259
+ if (maxSize && internalCache.size > maxSize) {
260
+ const firstKey = internalCache.keys().next().value;
261
+ if (firstKey !== undefined) internalCache.delete(firstKey);
262
+ }
263
+ return result;
264
+ }) as MemoizedFn<T>;
265
+
266
+ memoized.clear = () => {
267
+ internalCache.clear();
268
+ };
269
+
270
+ return memoized;
271
+ }