xxf_react 0.6.6 → 0.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -48
- package/dist/foundation/Copy.d.ts +8 -0
- package/dist/foundation/Copy.d.ts.map +1 -0
- package/dist/foundation/Copy.js +21 -0
- package/dist/foundation/index.d.ts +2 -0
- package/dist/foundation/index.d.ts.map +1 -1
- package/dist/foundation/index.js +2 -0
- package/dist/http/api/ApiBuilder.d.ts +115 -0
- package/dist/http/api/ApiBuilder.d.ts.map +1 -0
- package/dist/http/api/ApiBuilder.js +128 -0
- package/dist/http/api/index.d.ts +2 -0
- package/dist/http/api/index.d.ts.map +1 -0
- package/dist/http/api/index.js +1 -0
- package/dist/http/cache/DiskLruCache.d.ts +66 -0
- package/dist/http/cache/DiskLruCache.d.ts.map +1 -0
- package/dist/http/cache/DiskLruCache.js +254 -0
- package/dist/http/cache/HttpCache.d.ts +59 -0
- package/dist/http/cache/HttpCache.d.ts.map +1 -0
- package/dist/http/cache/HttpCache.js +215 -0
- package/dist/http/cache/index.d.ts +3 -0
- package/dist/http/cache/index.d.ts.map +1 -0
- package/dist/http/cache/index.js +2 -0
- package/dist/http/client/ApiStream.d.ts +80 -0
- package/dist/http/client/ApiStream.d.ts.map +1 -0
- package/dist/http/client/ApiStream.js +190 -0
- package/dist/http/client/HttpClient.d.ts +88 -0
- package/dist/http/client/HttpClient.d.ts.map +1 -0
- package/dist/http/client/HttpClient.js +381 -0
- package/dist/http/client/index.d.ts +3 -0
- package/dist/http/client/index.d.ts.map +1 -0
- package/dist/http/client/index.js +2 -0
- package/dist/http/index.d.ts +42 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +45 -0
- package/dist/http/models/ApiTypes.d.ts +54 -0
- package/dist/http/models/ApiTypes.d.ts.map +1 -0
- package/dist/http/models/ApiTypes.js +4 -0
- package/dist/http/models/CacheConfig.d.ts +58 -0
- package/dist/http/models/CacheConfig.d.ts.map +1 -0
- package/dist/http/models/CacheConfig.js +4 -0
- package/dist/http/models/CacheEntry.d.ts +28 -0
- package/dist/http/models/CacheEntry.d.ts.map +1 -0
- package/dist/http/models/CacheEntry.js +1 -0
- package/dist/http/models/CacheInterceptor.d.ts +112 -0
- package/dist/http/models/CacheInterceptor.d.ts.map +1 -0
- package/dist/http/models/CacheInterceptor.js +6 -0
- package/dist/http/models/CacheMode.d.ts +45 -0
- package/dist/http/models/CacheMode.d.ts.map +1 -0
- package/dist/http/models/CacheMode.js +45 -0
- package/dist/http/models/DiskCacheConfig.d.ts +56 -0
- package/dist/http/models/DiskCacheConfig.d.ts.map +1 -0
- package/dist/http/models/DiskCacheConfig.js +4 -0
- package/dist/http/models/HttpClientConfig.d.ts +77 -0
- package/dist/http/models/HttpClientConfig.d.ts.map +1 -0
- package/dist/http/models/HttpClientConfig.js +4 -0
- package/dist/http/models/LruMetadata.d.ts +28 -0
- package/dist/http/models/LruMetadata.d.ts.map +1 -0
- package/dist/http/models/LruMetadata.js +6 -0
- package/dist/http/models/RequestConfig.d.ts +67 -0
- package/dist/http/models/RequestConfig.d.ts.map +1 -0
- package/dist/http/models/RequestConfig.js +4 -0
- package/dist/http/models/index.d.ts +24 -0
- package/dist/http/models/index.d.ts.map +1 -0
- package/dist/http/models/index.js +16 -0
- package/dist/http/types.d.ts +13 -0
- package/dist/http/types.d.ts.map +1 -0
- package/dist/http/types.js +14 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/package.json +14 -4
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 基于 IndexedDB 的 LRU 磁盘缓存
|
|
3
|
+
*
|
|
4
|
+
* 特点:
|
|
5
|
+
* - 使用 idb-keyval 简化 IndexedDB 操作
|
|
6
|
+
* - 维护 LRU 元数据实现淘汰策略
|
|
7
|
+
* - 支持 TTL 过期
|
|
8
|
+
* - 批量清理优化性能
|
|
9
|
+
* - 防污染:返回数据的深拷贝
|
|
10
|
+
*/
|
|
11
|
+
import { get, set, del, clear as clearStore, createStore } from 'idb-keyval';
|
|
12
|
+
const META_KEY = '__lru_metadata__';
|
|
13
|
+
export class DiskLruCache {
|
|
14
|
+
constructor(config) {
|
|
15
|
+
this.metadata = null;
|
|
16
|
+
this.metadataDirty = false;
|
|
17
|
+
this.flushTimer = null;
|
|
18
|
+
this.evicting = false; // 防止并发淘汰
|
|
19
|
+
this.config = {
|
|
20
|
+
cleanupThreshold: 0.1,
|
|
21
|
+
...config,
|
|
22
|
+
};
|
|
23
|
+
this.store = createStore(config.dbName, config.storeName);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 加载或初始化 LRU 元数据(失败返回空元数据)
|
|
27
|
+
*/
|
|
28
|
+
async loadMetadata() {
|
|
29
|
+
if (this.metadata)
|
|
30
|
+
return this.metadata;
|
|
31
|
+
try {
|
|
32
|
+
const stored = await get(META_KEY, this.store);
|
|
33
|
+
if (stored) {
|
|
34
|
+
this.metadata = stored;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
this.metadata = { entries: {}, count: 0 };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// 加载失败,使用空元数据
|
|
42
|
+
this.metadata = { entries: {}, count: 0 };
|
|
43
|
+
}
|
|
44
|
+
return this.metadata;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 保存元数据到磁盘 (批量优化,延迟写入)
|
|
48
|
+
*/
|
|
49
|
+
scheduleFlushMetadata() {
|
|
50
|
+
if (this.flushTimer)
|
|
51
|
+
return;
|
|
52
|
+
this.metadataDirty = true;
|
|
53
|
+
this.flushTimer = setTimeout(() => {
|
|
54
|
+
this.flushTimer = null;
|
|
55
|
+
if (this.metadataDirty && this.metadata) {
|
|
56
|
+
set(META_KEY, this.metadata, this.store)
|
|
57
|
+
.then(() => {
|
|
58
|
+
this.metadataDirty = false;
|
|
59
|
+
})
|
|
60
|
+
.catch(() => {
|
|
61
|
+
// 静默处理写入错误,下次会重试
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}, 1000); // 1 秒后批量写入
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 立即刷新元数据到磁盘(失败静默忽略)
|
|
68
|
+
*/
|
|
69
|
+
async flush() {
|
|
70
|
+
if (this.flushTimer) {
|
|
71
|
+
clearTimeout(this.flushTimer);
|
|
72
|
+
this.flushTimer = null;
|
|
73
|
+
}
|
|
74
|
+
if (this.metadataDirty && this.metadata) {
|
|
75
|
+
try {
|
|
76
|
+
await set(META_KEY, this.metadata, this.store);
|
|
77
|
+
this.metadataDirty = false;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// 刷新失败静默忽略,下次会重试
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 获取缓存(失败返回 null,不影响业务)
|
|
86
|
+
* @returns 缓存条目的深拷贝
|
|
87
|
+
*/
|
|
88
|
+
async get(key) {
|
|
89
|
+
try {
|
|
90
|
+
const meta = await this.loadMetadata();
|
|
91
|
+
// 检查是否存在
|
|
92
|
+
if (!meta.entries[key]) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const entry = await get(key, this.store);
|
|
96
|
+
if (!entry) {
|
|
97
|
+
// 数据丢失,清理元数据
|
|
98
|
+
delete meta.entries[key];
|
|
99
|
+
meta.count = Math.max(0, meta.count - 1);
|
|
100
|
+
this.scheduleFlushMetadata();
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
// 检查是否过期
|
|
104
|
+
if (entry.ttl > 0 && Date.now() - entry.timestamp > entry.ttl) {
|
|
105
|
+
await this.delete(key);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
// 更新访问时间
|
|
109
|
+
meta.entries[key] = { accessTime: Date.now() };
|
|
110
|
+
this.scheduleFlushMetadata();
|
|
111
|
+
return entry;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// IndexedDB 操作失败,返回 null 不影响业务
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 设置缓存(失败静默忽略,不影响业务)
|
|
120
|
+
*/
|
|
121
|
+
async set(key, data, ttl = 0) {
|
|
122
|
+
try {
|
|
123
|
+
const meta = await this.loadMetadata();
|
|
124
|
+
const entry = {
|
|
125
|
+
data,
|
|
126
|
+
timestamp: Date.now(),
|
|
127
|
+
ttl,
|
|
128
|
+
key,
|
|
129
|
+
};
|
|
130
|
+
// 检查是否需要淘汰
|
|
131
|
+
if (!meta.entries[key] && meta.count >= this.config.maxSize) {
|
|
132
|
+
await this.evict();
|
|
133
|
+
}
|
|
134
|
+
await set(key, entry, this.store);
|
|
135
|
+
// 更新元数据
|
|
136
|
+
if (!meta.entries[key]) {
|
|
137
|
+
meta.count++;
|
|
138
|
+
}
|
|
139
|
+
meta.entries[key] = { accessTime: Date.now() };
|
|
140
|
+
this.scheduleFlushMetadata();
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// IndexedDB 操作失败,静默忽略不影响业务
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* 删除缓存(失败静默忽略)
|
|
148
|
+
*/
|
|
149
|
+
async delete(key) {
|
|
150
|
+
try {
|
|
151
|
+
const meta = await this.loadMetadata();
|
|
152
|
+
if (meta.entries[key]) {
|
|
153
|
+
await del(key, this.store);
|
|
154
|
+
delete meta.entries[key];
|
|
155
|
+
meta.count = Math.max(0, meta.count - 1);
|
|
156
|
+
this.scheduleFlushMetadata();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// 删除失败静默忽略
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 检查缓存是否存在(不更新访问时间,失败返回 false)
|
|
165
|
+
*/
|
|
166
|
+
async has(key) {
|
|
167
|
+
try {
|
|
168
|
+
const meta = await this.loadMetadata();
|
|
169
|
+
return !!meta.entries[key];
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* 淘汰最旧的缓存条目(失败静默忽略)
|
|
177
|
+
*/
|
|
178
|
+
async evict() {
|
|
179
|
+
// 防止并发淘汰
|
|
180
|
+
if (this.evicting)
|
|
181
|
+
return;
|
|
182
|
+
this.evicting = true;
|
|
183
|
+
try {
|
|
184
|
+
const meta = await this.loadMetadata();
|
|
185
|
+
// 计算需要淘汰的数量
|
|
186
|
+
const evictCount = Math.ceil(this.config.maxSize * this.config.cleanupThreshold);
|
|
187
|
+
// 按访问时间排序
|
|
188
|
+
const sorted = Object.entries(meta.entries).sort((a, b) => a[1].accessTime - b[1].accessTime);
|
|
189
|
+
// 淘汰最旧的
|
|
190
|
+
const toEvict = sorted.slice(0, evictCount);
|
|
191
|
+
for (const [key] of toEvict) {
|
|
192
|
+
try {
|
|
193
|
+
await del(key, this.store);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// 单个删除失败继续处理其他
|
|
197
|
+
}
|
|
198
|
+
delete meta.entries[key];
|
|
199
|
+
meta.count = Math.max(0, meta.count - 1);
|
|
200
|
+
}
|
|
201
|
+
this.scheduleFlushMetadata();
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// 淘汰失败静默忽略
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
this.evicting = false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* 清空所有缓存(失败静默忽略)
|
|
212
|
+
*/
|
|
213
|
+
async clear() {
|
|
214
|
+
// 取消待处理的元数据写入
|
|
215
|
+
if (this.flushTimer) {
|
|
216
|
+
clearTimeout(this.flushTimer);
|
|
217
|
+
this.flushTimer = null;
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
// 使用 idb-keyval 的 clear 方法一次性清空整个 store
|
|
221
|
+
await clearStore(this.store);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// 清空失败静默忽略
|
|
225
|
+
}
|
|
226
|
+
// 重置内存中的元数据
|
|
227
|
+
this.metadata = { entries: {}, count: 0 };
|
|
228
|
+
this.metadataDirty = false;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* 获取缓存数量(失败返回 0)
|
|
232
|
+
*/
|
|
233
|
+
async size() {
|
|
234
|
+
try {
|
|
235
|
+
const meta = await this.loadMetadata();
|
|
236
|
+
return meta.count;
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* 获取所有缓存 key(失败返回空数组)
|
|
244
|
+
*/
|
|
245
|
+
async keys() {
|
|
246
|
+
try {
|
|
247
|
+
const meta = await this.loadMetadata();
|
|
248
|
+
return Object.keys(meta.entries);
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return [];
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP 缓存(内存 + 磁盘双层)
|
|
3
|
+
*
|
|
4
|
+
* 策略:
|
|
5
|
+
* - 读取:先内存 -> 再磁盘 -> 若磁盘命中则回填内存
|
|
6
|
+
* - 写入:同时写入内存和磁盘
|
|
7
|
+
* - 容错:缓存操作失败不影响正常业务
|
|
8
|
+
* - 防污染:返回数据的深拷贝,防止上层修改影响缓存
|
|
9
|
+
*/
|
|
10
|
+
import { DiskLruCache } from './DiskLruCache';
|
|
11
|
+
import type { CacheEntry, HttpCacheConfig } from '../types';
|
|
12
|
+
export declare class HttpCache {
|
|
13
|
+
private memoryCache;
|
|
14
|
+
private diskCache;
|
|
15
|
+
constructor(config: HttpCacheConfig);
|
|
16
|
+
/**
|
|
17
|
+
* 获取缓存
|
|
18
|
+
* @param key 缓存 key
|
|
19
|
+
* @returns 缓存条目的深拷贝,未命中或失败返回 null
|
|
20
|
+
*/
|
|
21
|
+
get<T>(key: string): Promise<CacheEntry<T> | null>;
|
|
22
|
+
/**
|
|
23
|
+
* 设置缓存(失败静默忽略,不影响业务)
|
|
24
|
+
* @param key 缓存 key
|
|
25
|
+
* @param data 数据
|
|
26
|
+
* @param ttl 过期时间 (ms)
|
|
27
|
+
*/
|
|
28
|
+
set<T>(key: string, data: T, ttl?: number): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* 删除缓存(失败静默忽略)
|
|
31
|
+
*/
|
|
32
|
+
delete(key: string): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* 清空所有缓存(失败静默忽略)
|
|
35
|
+
*/
|
|
36
|
+
clear(): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* 仅获取内存缓存(同步,失败返回 null)
|
|
39
|
+
* @returns 缓存条目的深拷贝
|
|
40
|
+
*/
|
|
41
|
+
getFromMemory<T>(key: string): CacheEntry<T> | null;
|
|
42
|
+
/**
|
|
43
|
+
* 仅设置内存缓存(同步,失败静默忽略)
|
|
44
|
+
*/
|
|
45
|
+
setToMemory<T>(key: string, data: T, ttl?: number): void;
|
|
46
|
+
/**
|
|
47
|
+
* 检查缓存是否存在(会检查过期,失败返回 false)
|
|
48
|
+
*/
|
|
49
|
+
has(key: string): Promise<boolean>;
|
|
50
|
+
/**
|
|
51
|
+
* 获取磁盘缓存实例(用于高级操作)
|
|
52
|
+
*/
|
|
53
|
+
getDiskCache(): DiskLruCache;
|
|
54
|
+
/**
|
|
55
|
+
* 刷新磁盘缓存元数据(失败静默忽略)
|
|
56
|
+
*/
|
|
57
|
+
flush(): Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=HttpCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HttpCache.d.ts","sourceRoot":"","sources":["../../../src/http/cache/HttpCache.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAG3D,qBAAa,SAAS;IAClB,OAAO,CAAC,WAAW,CAAiB;IACpC,OAAO,CAAC,SAAS,CAAc;gBAEnB,MAAM,EAAE,eAAe;IAcnC;;;;OAIG;IACG,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAwCxD;;;;;OAKG;IACG,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,GAAE,MAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBlE;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcxC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAc5B;;;OAGG;IACH,aAAa,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI;IAgBnD;;OAEG;IACH,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,GAAE,MAAU,GAAG,IAAI;IAc3D;;OAEG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAyBxC;;OAEG;IACH,YAAY,IAAI,YAAY;IAI5B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAO/B"}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP 缓存(内存 + 磁盘双层)
|
|
3
|
+
*
|
|
4
|
+
* 策略:
|
|
5
|
+
* - 读取:先内存 -> 再磁盘 -> 若磁盘命中则回填内存
|
|
6
|
+
* - 写入:同时写入内存和磁盘
|
|
7
|
+
* - 容错:缓存操作失败不影响正常业务
|
|
8
|
+
* - 防污染:返回数据的深拷贝,防止上层修改影响缓存
|
|
9
|
+
*/
|
|
10
|
+
import { lru } from 'tiny-lru';
|
|
11
|
+
import { DiskLruCache } from './DiskLruCache';
|
|
12
|
+
import { copy } from '../../foundation';
|
|
13
|
+
export class HttpCache {
|
|
14
|
+
constructor(config) {
|
|
15
|
+
// 初始化内存缓存
|
|
16
|
+
// 注意:不使用 tiny-lru 的 ttl 参数,因为每个 entry 有自己的 ttl
|
|
17
|
+
// tiny-lru 的 ttl 是全局的,会导致冲突
|
|
18
|
+
this.memoryCache = lru(config.memoryMaxSize);
|
|
19
|
+
// 初始化磁盘缓存
|
|
20
|
+
this.diskCache = new DiskLruCache({
|
|
21
|
+
dbName: config.dbName,
|
|
22
|
+
storeName: 'http-cache',
|
|
23
|
+
maxSize: config.diskMaxSize,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 获取缓存
|
|
28
|
+
* @param key 缓存 key
|
|
29
|
+
* @returns 缓存条目的深拷贝,未命中或失败返回 null
|
|
30
|
+
*/
|
|
31
|
+
async get(key) {
|
|
32
|
+
// 1. 先查内存(内存操作用 try-catch 保护)
|
|
33
|
+
try {
|
|
34
|
+
const memEntry = this.memoryCache.get(key);
|
|
35
|
+
if (memEntry) {
|
|
36
|
+
// 检查是否过期
|
|
37
|
+
if (memEntry.ttl > 0 &&
|
|
38
|
+
Date.now() - memEntry.timestamp > memEntry.ttl) {
|
|
39
|
+
this.memoryCache.delete(key);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// 返回深拷贝,防止上层修改污染缓存
|
|
43
|
+
return copy(memEntry);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// 内存缓存读取失败,继续尝试磁盘
|
|
49
|
+
}
|
|
50
|
+
// 2. 再查磁盘(磁盘操作失败返回 null,不影响业务)
|
|
51
|
+
try {
|
|
52
|
+
const diskEntry = await this.diskCache.get(key);
|
|
53
|
+
if (diskEntry) {
|
|
54
|
+
// 回填内存缓存(失败也忽略)
|
|
55
|
+
try {
|
|
56
|
+
this.memoryCache.set(key, diskEntry);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// 回填失败忽略
|
|
60
|
+
}
|
|
61
|
+
// 返回深拷贝,防止上层修改污染缓存
|
|
62
|
+
return copy(diskEntry);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// 磁盘缓存读取失败,返回 null
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 设置缓存(失败静默忽略,不影响业务)
|
|
72
|
+
* @param key 缓存 key
|
|
73
|
+
* @param data 数据
|
|
74
|
+
* @param ttl 过期时间 (ms)
|
|
75
|
+
*/
|
|
76
|
+
async set(key, data, ttl = 0) {
|
|
77
|
+
const entry = {
|
|
78
|
+
data,
|
|
79
|
+
timestamp: Date.now(),
|
|
80
|
+
ttl,
|
|
81
|
+
key,
|
|
82
|
+
};
|
|
83
|
+
// 写入内存(失败忽略)
|
|
84
|
+
try {
|
|
85
|
+
this.memoryCache.set(key, entry);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// 内存写入失败忽略
|
|
89
|
+
}
|
|
90
|
+
// 写入磁盘(失败忽略)
|
|
91
|
+
try {
|
|
92
|
+
await this.diskCache.set(key, data, ttl);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// 磁盘写入失败忽略,不影响业务
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 删除缓存(失败静默忽略)
|
|
100
|
+
*/
|
|
101
|
+
async delete(key) {
|
|
102
|
+
try {
|
|
103
|
+
this.memoryCache.delete(key);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// 忽略
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
await this.diskCache.delete(key);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// 忽略
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 清空所有缓存(失败静默忽略)
|
|
117
|
+
*/
|
|
118
|
+
async clear() {
|
|
119
|
+
try {
|
|
120
|
+
this.memoryCache.clear();
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// 忽略
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
await this.diskCache.clear();
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// 忽略
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 仅获取内存缓存(同步,失败返回 null)
|
|
134
|
+
* @returns 缓存条目的深拷贝
|
|
135
|
+
*/
|
|
136
|
+
getFromMemory(key) {
|
|
137
|
+
try {
|
|
138
|
+
const entry = this.memoryCache.get(key);
|
|
139
|
+
if (!entry)
|
|
140
|
+
return null;
|
|
141
|
+
if (entry.ttl > 0 && Date.now() - entry.timestamp > entry.ttl) {
|
|
142
|
+
this.memoryCache.delete(key);
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
// 返回深拷贝,防止上层修改污染缓存
|
|
146
|
+
return copy(entry);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 仅设置内存缓存(同步,失败静默忽略)
|
|
154
|
+
*/
|
|
155
|
+
setToMemory(key, data, ttl = 0) {
|
|
156
|
+
try {
|
|
157
|
+
const entry = {
|
|
158
|
+
data,
|
|
159
|
+
timestamp: Date.now(),
|
|
160
|
+
ttl,
|
|
161
|
+
key,
|
|
162
|
+
};
|
|
163
|
+
this.memoryCache.set(key, entry);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// 忽略
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* 检查缓存是否存在(会检查过期,失败返回 false)
|
|
171
|
+
*/
|
|
172
|
+
async has(key) {
|
|
173
|
+
// 先查内存
|
|
174
|
+
try {
|
|
175
|
+
const memEntry = this.memoryCache.get(key);
|
|
176
|
+
if (memEntry) {
|
|
177
|
+
// 检查是否过期
|
|
178
|
+
if (memEntry.ttl > 0 && Date.now() - memEntry.timestamp > memEntry.ttl) {
|
|
179
|
+
this.memoryCache.delete(key);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
// 内存检查失败,继续检查磁盘
|
|
188
|
+
}
|
|
189
|
+
// 再查磁盘
|
|
190
|
+
try {
|
|
191
|
+
const diskEntry = await this.diskCache.get(key);
|
|
192
|
+
return diskEntry !== null;
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 获取磁盘缓存实例(用于高级操作)
|
|
200
|
+
*/
|
|
201
|
+
getDiskCache() {
|
|
202
|
+
return this.diskCache;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* 刷新磁盘缓存元数据(失败静默忽略)
|
|
206
|
+
*/
|
|
207
|
+
async flush() {
|
|
208
|
+
try {
|
|
209
|
+
await this.diskCache.flush();
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// 忽略
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/http/cache/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ApiStream - AsyncGenerator 流式响应
|
|
3
|
+
*
|
|
4
|
+
* 支持三种消费方式:
|
|
5
|
+
* 1. await - 直接获取最后一个结果
|
|
6
|
+
* 2. for await - 迭代所有结果
|
|
7
|
+
* 3. subscribe - 安全消费(框架 try-catch)
|
|
8
|
+
*/
|
|
9
|
+
import type { ApiResult, ApiCallback } from '../types';
|
|
10
|
+
export declare class ApiStream<T> implements AsyncIterable<ApiResult<T>>, PromiseLike<T> {
|
|
11
|
+
private generator;
|
|
12
|
+
private consumed;
|
|
13
|
+
constructor(generator: () => AsyncGenerator<ApiResult<T>>);
|
|
14
|
+
/**
|
|
15
|
+
* 实现 AsyncIterable,支持 for await
|
|
16
|
+
*
|
|
17
|
+
* 注意:每次迭代都会创建新的请求。如果需要缓存结果,请使用 toArray()
|
|
18
|
+
*/
|
|
19
|
+
[Symbol.asyncIterator](): AsyncGenerator<ApiResult<T>>;
|
|
20
|
+
/**
|
|
21
|
+
* 实现 PromiseLike,支持直接 await(返回最后一个结果的 data)
|
|
22
|
+
*/
|
|
23
|
+
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
24
|
+
/**
|
|
25
|
+
* 实现 catch
|
|
26
|
+
*/
|
|
27
|
+
catch<TResult = never>(onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null): Promise<T | TResult>;
|
|
28
|
+
/**
|
|
29
|
+
* 实现 finally
|
|
30
|
+
*/
|
|
31
|
+
finally(onfinally?: (() => void) | null): Promise<T>;
|
|
32
|
+
/**
|
|
33
|
+
* 获取第一个结果
|
|
34
|
+
* @throws 如果没有结果会抛出错误
|
|
35
|
+
*/
|
|
36
|
+
first(): Promise<ApiResult<T>>;
|
|
37
|
+
/**
|
|
38
|
+
* 获取第一个结果,没有则返回 null
|
|
39
|
+
*/
|
|
40
|
+
firstOrNull(): Promise<ApiResult<T> | null>;
|
|
41
|
+
/**
|
|
42
|
+
* 获取最后一个结果
|
|
43
|
+
* @throws 如果没有结果会抛出错误
|
|
44
|
+
*/
|
|
45
|
+
last(): Promise<ApiResult<T>>;
|
|
46
|
+
/**
|
|
47
|
+
* 获取最后一个结果,没有则返回 null
|
|
48
|
+
*/
|
|
49
|
+
lastOrNull(): Promise<ApiResult<T> | null>;
|
|
50
|
+
/**
|
|
51
|
+
* 获取所有结果
|
|
52
|
+
*/
|
|
53
|
+
toArray(): Promise<ApiResult<T>[]>;
|
|
54
|
+
/**
|
|
55
|
+
* 查找满足条件的第一个结果
|
|
56
|
+
*/
|
|
57
|
+
find(predicate: (result: ApiResult<T>) => boolean): Promise<ApiResult<T> | undefined>;
|
|
58
|
+
/**
|
|
59
|
+
* 安全消费(框架 try-catch 隔离错误)
|
|
60
|
+
* onData 抛错不会影响后续 yield
|
|
61
|
+
*/
|
|
62
|
+
subscribe(callback: ApiCallback<T>): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* 映射结果
|
|
65
|
+
*/
|
|
66
|
+
map<U>(fn: (data: T) => U): ApiStream<U>;
|
|
67
|
+
/**
|
|
68
|
+
* 过滤结果
|
|
69
|
+
*/
|
|
70
|
+
filter(predicate: (result: ApiResult<T>) => boolean): ApiStream<T>;
|
|
71
|
+
/**
|
|
72
|
+
* 只获取缓存结果
|
|
73
|
+
*/
|
|
74
|
+
cacheOnly(): ApiStream<T>;
|
|
75
|
+
/**
|
|
76
|
+
* 只获取网络结果
|
|
77
|
+
*/
|
|
78
|
+
remoteOnly(): ApiStream<T>;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=ApiStream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApiStream.d.ts","sourceRoot":"","sources":["../../../src/http/client/ApiStream.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAEtD,qBAAa,SAAS,CAAC,CAAC,CAAE,YAAW,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC;IAC5E,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,QAAQ,CAAQ;gBAEZ,SAAS,EAAE,MAAM,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAIzD;;;;OAIG;IACH,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAqBtD;;OAEG;IACH,IAAI,CAAC,QAAQ,GAAG,CAAC,EAAE,QAAQ,GAAG,KAAK,EAC/B,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,EACrE,UAAU,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,GAC5E,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAM/B;;OAEG;IACH,KAAK,CAAC,OAAO,GAAG,KAAK,EACjB,UAAU,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,GAC1E,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC;IAIvB;;OAEG;IACH,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC;IAapD;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAQpC;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAOjD;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAQnC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAQhD;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;IAQxC;;OAEG;IACG,IAAI,CACN,SAAS,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,OAAO,GAC7C,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IASpC;;;OAGG;IACG,SAAS,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBxD;;OAEG;IACH,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;IAYxC;;OAEG;IACH,MAAM,CAAC,SAAS,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC;IAWlE;;OAEG;IACH,SAAS,IAAI,SAAS,CAAC,CAAC,CAAC;IAIzB;;OAEG;IACH,UAAU,IAAI,SAAS,CAAC,CAAC,CAAC;CAG7B"}
|