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.
Files changed (71) hide show
  1. package/README.md +14 -48
  2. package/dist/foundation/Copy.d.ts +8 -0
  3. package/dist/foundation/Copy.d.ts.map +1 -0
  4. package/dist/foundation/Copy.js +21 -0
  5. package/dist/foundation/index.d.ts +2 -0
  6. package/dist/foundation/index.d.ts.map +1 -1
  7. package/dist/foundation/index.js +2 -0
  8. package/dist/http/api/ApiBuilder.d.ts +115 -0
  9. package/dist/http/api/ApiBuilder.d.ts.map +1 -0
  10. package/dist/http/api/ApiBuilder.js +128 -0
  11. package/dist/http/api/index.d.ts +2 -0
  12. package/dist/http/api/index.d.ts.map +1 -0
  13. package/dist/http/api/index.js +1 -0
  14. package/dist/http/cache/DiskLruCache.d.ts +66 -0
  15. package/dist/http/cache/DiskLruCache.d.ts.map +1 -0
  16. package/dist/http/cache/DiskLruCache.js +254 -0
  17. package/dist/http/cache/HttpCache.d.ts +59 -0
  18. package/dist/http/cache/HttpCache.d.ts.map +1 -0
  19. package/dist/http/cache/HttpCache.js +215 -0
  20. package/dist/http/cache/index.d.ts +3 -0
  21. package/dist/http/cache/index.d.ts.map +1 -0
  22. package/dist/http/cache/index.js +2 -0
  23. package/dist/http/client/ApiStream.d.ts +80 -0
  24. package/dist/http/client/ApiStream.d.ts.map +1 -0
  25. package/dist/http/client/ApiStream.js +190 -0
  26. package/dist/http/client/HttpClient.d.ts +88 -0
  27. package/dist/http/client/HttpClient.d.ts.map +1 -0
  28. package/dist/http/client/HttpClient.js +381 -0
  29. package/dist/http/client/index.d.ts +3 -0
  30. package/dist/http/client/index.d.ts.map +1 -0
  31. package/dist/http/client/index.js +2 -0
  32. package/dist/http/index.d.ts +42 -0
  33. package/dist/http/index.d.ts.map +1 -0
  34. package/dist/http/index.js +45 -0
  35. package/dist/http/models/ApiTypes.d.ts +54 -0
  36. package/dist/http/models/ApiTypes.d.ts.map +1 -0
  37. package/dist/http/models/ApiTypes.js +4 -0
  38. package/dist/http/models/CacheConfig.d.ts +58 -0
  39. package/dist/http/models/CacheConfig.d.ts.map +1 -0
  40. package/dist/http/models/CacheConfig.js +4 -0
  41. package/dist/http/models/CacheEntry.d.ts +28 -0
  42. package/dist/http/models/CacheEntry.d.ts.map +1 -0
  43. package/dist/http/models/CacheEntry.js +1 -0
  44. package/dist/http/models/CacheInterceptor.d.ts +112 -0
  45. package/dist/http/models/CacheInterceptor.d.ts.map +1 -0
  46. package/dist/http/models/CacheInterceptor.js +6 -0
  47. package/dist/http/models/CacheMode.d.ts +45 -0
  48. package/dist/http/models/CacheMode.d.ts.map +1 -0
  49. package/dist/http/models/CacheMode.js +45 -0
  50. package/dist/http/models/DiskCacheConfig.d.ts +56 -0
  51. package/dist/http/models/DiskCacheConfig.d.ts.map +1 -0
  52. package/dist/http/models/DiskCacheConfig.js +4 -0
  53. package/dist/http/models/HttpClientConfig.d.ts +77 -0
  54. package/dist/http/models/HttpClientConfig.d.ts.map +1 -0
  55. package/dist/http/models/HttpClientConfig.js +4 -0
  56. package/dist/http/models/LruMetadata.d.ts +28 -0
  57. package/dist/http/models/LruMetadata.d.ts.map +1 -0
  58. package/dist/http/models/LruMetadata.js +6 -0
  59. package/dist/http/models/RequestConfig.d.ts +67 -0
  60. package/dist/http/models/RequestConfig.d.ts.map +1 -0
  61. package/dist/http/models/RequestConfig.js +4 -0
  62. package/dist/http/models/index.d.ts +24 -0
  63. package/dist/http/models/index.d.ts.map +1 -0
  64. package/dist/http/models/index.js +16 -0
  65. package/dist/http/types.d.ts +13 -0
  66. package/dist/http/types.d.ts.map +1 -0
  67. package/dist/http/types.js +14 -0
  68. package/dist/index.d.ts +1 -0
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +3 -0
  71. package/package.json +14 -4
@@ -0,0 +1,190 @@
1
+ /**
2
+ * ApiStream - AsyncGenerator 流式响应
3
+ *
4
+ * 支持三种消费方式:
5
+ * 1. await - 直接获取最后一个结果
6
+ * 2. for await - 迭代所有结果
7
+ * 3. subscribe - 安全消费(框架 try-catch)
8
+ */
9
+ export class ApiStream {
10
+ constructor(generator) {
11
+ this.consumed = false;
12
+ this.generator = generator;
13
+ }
14
+ /**
15
+ * 实现 AsyncIterable,支持 for await
16
+ *
17
+ * 注意:每次迭代都会创建新的请求。如果需要缓存结果,请使用 toArray()
18
+ */
19
+ [Symbol.asyncIterator]() {
20
+ var _a;
21
+ if (this.consumed) {
22
+ // 开发环境下警告重复消费
23
+ try {
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
+ if (typeof globalThis.process !== 'undefined' &&
26
+ ((_a = globalThis.process.env) === null || _a === void 0 ? void 0 : _a.NODE_ENV) !== 'production') {
27
+ console.warn('[ApiStream] Stream is being consumed multiple times. ' +
28
+ 'Each iteration creates a new request. ' +
29
+ 'Use toArray() if you need to reuse results.');
30
+ }
31
+ }
32
+ catch {
33
+ // 忽略环境检测错误
34
+ }
35
+ }
36
+ this.consumed = true;
37
+ return this.generator();
38
+ }
39
+ /**
40
+ * 实现 PromiseLike,支持直接 await(返回最后一个结果的 data)
41
+ */
42
+ then(onfulfilled, onrejected) {
43
+ return this.last()
44
+ .then((result) => result.data)
45
+ .then(onfulfilled, onrejected);
46
+ }
47
+ /**
48
+ * 实现 catch
49
+ */
50
+ catch(onrejected) {
51
+ return this.then(undefined, onrejected);
52
+ }
53
+ /**
54
+ * 实现 finally
55
+ */
56
+ finally(onfinally) {
57
+ return this.then((value) => {
58
+ onfinally === null || onfinally === void 0 ? void 0 : onfinally();
59
+ return value;
60
+ }, (reason) => {
61
+ onfinally === null || onfinally === void 0 ? void 0 : onfinally();
62
+ throw reason;
63
+ });
64
+ }
65
+ /**
66
+ * 获取第一个结果
67
+ * @throws 如果没有结果会抛出错误
68
+ */
69
+ async first() {
70
+ const result = await this.firstOrNull();
71
+ if (!result) {
72
+ throw new Error('No result available');
73
+ }
74
+ return result;
75
+ }
76
+ /**
77
+ * 获取第一个结果,没有则返回 null
78
+ */
79
+ async firstOrNull() {
80
+ for await (const result of this) {
81
+ return result;
82
+ }
83
+ return null;
84
+ }
85
+ /**
86
+ * 获取最后一个结果
87
+ * @throws 如果没有结果会抛出错误
88
+ */
89
+ async last() {
90
+ const result = await this.lastOrNull();
91
+ if (!result) {
92
+ throw new Error('No result available');
93
+ }
94
+ return result;
95
+ }
96
+ /**
97
+ * 获取最后一个结果,没有则返回 null
98
+ */
99
+ async lastOrNull() {
100
+ let lastResult = null;
101
+ for await (const result of this) {
102
+ lastResult = result;
103
+ }
104
+ return lastResult;
105
+ }
106
+ /**
107
+ * 获取所有结果
108
+ */
109
+ async toArray() {
110
+ const results = [];
111
+ for await (const result of this) {
112
+ results.push(result);
113
+ }
114
+ return results;
115
+ }
116
+ /**
117
+ * 查找满足条件的第一个结果
118
+ */
119
+ async find(predicate) {
120
+ for await (const result of this) {
121
+ if (predicate(result)) {
122
+ return result;
123
+ }
124
+ }
125
+ return undefined;
126
+ }
127
+ /**
128
+ * 安全消费(框架 try-catch 隔离错误)
129
+ * onData 抛错不会影响后续 yield
130
+ */
131
+ async subscribe(callback) {
132
+ var _a, _b, _c, _d;
133
+ try {
134
+ for await (const result of this) {
135
+ try {
136
+ callback.onData(result.data, result.fromCache);
137
+ }
138
+ catch (error) {
139
+ // 捕获下游错误,不影响后续 yield
140
+ (_a = callback.onError) === null || _a === void 0 ? void 0 : _a.call(callback, error);
141
+ }
142
+ }
143
+ (_b = callback.onComplete) === null || _b === void 0 ? void 0 : _b.call(callback);
144
+ }
145
+ catch (error) {
146
+ // 网络错误等
147
+ (_c = callback.onError) === null || _c === void 0 ? void 0 : _c.call(callback, error);
148
+ (_d = callback.onComplete) === null || _d === void 0 ? void 0 : _d.call(callback);
149
+ }
150
+ }
151
+ /**
152
+ * 映射结果
153
+ */
154
+ map(fn) {
155
+ const self = this;
156
+ return new ApiStream(async function* () {
157
+ for await (const result of self) {
158
+ yield {
159
+ data: fn(result.data),
160
+ fromCache: result.fromCache,
161
+ };
162
+ }
163
+ });
164
+ }
165
+ /**
166
+ * 过滤结果
167
+ */
168
+ filter(predicate) {
169
+ const self = this;
170
+ return new ApiStream(async function* () {
171
+ for await (const result of self) {
172
+ if (predicate(result)) {
173
+ yield result;
174
+ }
175
+ }
176
+ });
177
+ }
178
+ /**
179
+ * 只获取缓存结果
180
+ */
181
+ cacheOnly() {
182
+ return this.filter((r) => r.fromCache);
183
+ }
184
+ /**
185
+ * 只获取网络结果
186
+ */
187
+ remoteOnly() {
188
+ return this.filter((r) => !r.fromCache);
189
+ }
190
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * HTTP 客户端
3
+ *
4
+ * 基于 ky 封装,支持:
5
+ * - 多种缓存模式
6
+ * - 自动重试
7
+ * - 超时处理
8
+ */
9
+ import { type KyInstance, type Options as KyOptions } from 'ky';
10
+ import { HttpCache } from '../cache/HttpCache';
11
+ import { ApiStream } from './ApiStream';
12
+ import { type HttpClientConfig, type RequestConfig, type ApiOptions } from '../types';
13
+ export declare class HttpClient {
14
+ private client;
15
+ private cache;
16
+ private config;
17
+ constructor(config: HttpClientConfig);
18
+ /**
19
+ * 获取 ky 实例(用于扩展 hooks 等)
20
+ */
21
+ getKyInstance(): KyInstance;
22
+ /**
23
+ * 设置 ky 实例
24
+ */
25
+ setKyInstance(instance: KyInstance): void;
26
+ /**
27
+ * 扩展 ky 实例
28
+ */
29
+ extendKy(options: KyOptions): void;
30
+ /**
31
+ * 执行请求,返回 ApiStream
32
+ */
33
+ request<T>(requestConfig: RequestConfig, options?: ApiOptions): ApiStream<T>;
34
+ /**
35
+ * 根据缓存策略执行请求
36
+ */
37
+ private executeWithCacheStrategy;
38
+ /**
39
+ * 判断响应是否应该缓存
40
+ *
41
+ * 调用全局缓存拦截器,根据响应内容决定是否缓存。
42
+ * 如果没有配置拦截器,默认允许缓存。
43
+ *
44
+ * @param result 请求响应结果
45
+ * @param cacheKey 缓存 key
46
+ * @param request 请求信息
47
+ * @param defaultTtl 默认 TTL
48
+ * @returns 是否缓存及最终 TTL
49
+ */
50
+ private shouldCacheResponse;
51
+ /**
52
+ * 条件缓存:根据拦截器结果决定是否写入缓存
53
+ */
54
+ private conditionalCache;
55
+ /**
56
+ * OnlyRemote: 只从服务器拿
57
+ */
58
+ private strategyOnlyRemote;
59
+ /**
60
+ * OnlyCache: 只从缓存拿
61
+ */
62
+ private strategyOnlyCache;
63
+ /**
64
+ * IfCache: 有缓存返回缓存,否则请求网络
65
+ */
66
+ private strategyIfCache;
67
+ /**
68
+ * FirstRemote: 先从服务器获取,没网络则读缓存
69
+ */
70
+ private strategyFirstRemote;
71
+ /**
72
+ * FirstCache: 先从本地缓存拿,然后从服务器拿,可能 yield 两次
73
+ */
74
+ private strategyFirstCache;
75
+ /**
76
+ * LastCache: 读上次缓存,没有则返回网络并同步缓存;有缓存也会后台同步网络但不 yield
77
+ */
78
+ private strategyLastCache;
79
+ /**
80
+ * 获取缓存实例(用于高级操作)
81
+ */
82
+ getCache(): HttpCache;
83
+ /**
84
+ * 清空所有缓存
85
+ */
86
+ clearCache(): Promise<void>;
87
+ }
88
+ //# sourceMappingURL=HttpClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HttpClient.d.ts","sourceRoot":"","sources":["../../../src/http/client/HttpClient.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAW,EAAE,KAAK,UAAU,EAAE,KAAK,OAAO,IAAI,SAAS,EAAE,MAAM,IAAI,CAAA;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAEH,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,UAAU,EAMlB,MAAM,UAAU,CAAA;AAyFjB,qBAAa,UAAU;IACnB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,MAAM,CAA4B;gBAE9B,MAAM,EAAE,gBAAgB;IA8BpC;;OAEG;IACH,aAAa,IAAI,UAAU;IAI3B;;OAEG;IACH,aAAa,CAAC,QAAQ,EAAE,UAAU,GAAG,IAAI;IAIzC;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI;IAIlC;;OAEG;IACH,OAAO,CAAC,CAAC,EAAE,aAAa,EAAE,aAAa,EAAE,OAAO,GAAE,UAAe,GAAG,SAAS,CAAC,CAAC,CAAC;IAmFhF;;OAEG;YACY,wBAAwB;IAqCvC;;;;;;;;;;;OAWG;YACW,mBAAmB;IAwCjC;;OAEG;YACW,gBAAgB;IAY9B;;OAEG;YACY,kBAAkB;IAWjC;;OAEG;YACY,iBAAiB;IAQhC;;OAEG;YACY,eAAe;IAiB9B;;OAEG;YACY,mBAAmB;IAqBlC;;OAEG;YACY,kBAAkB;IA0BjC;;OAEG;YACY,iBAAiB;IA0BhC;;OAEG;IACH,QAAQ,IAAI,SAAS;IAIrB;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAGpC"}
@@ -0,0 +1,381 @@
1
+ /**
2
+ * HTTP 客户端
3
+ *
4
+ * 基于 ky 封装,支持:
5
+ * - 多种缓存模式
6
+ * - 自动重试
7
+ * - 超时处理
8
+ */
9
+ import ky from 'ky';
10
+ import { HttpCache } from '../cache/HttpCache';
11
+ import { ApiStream } from './ApiStream';
12
+ import { CacheMode, } from '../types';
13
+ /**
14
+ * 对象 key 排序(用于生成稳定的缓存 key)
15
+ * 支持嵌套对象递归排序
16
+ */
17
+ function sortKeys(obj) {
18
+ const sorted = {};
19
+ for (const key of Object.keys(obj).sort()) {
20
+ const value = obj[key];
21
+ // 递归处理嵌套对象(排除 null 和数组)
22
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
23
+ sorted[key] = sortKeys(value);
24
+ }
25
+ else {
26
+ sorted[key] = value;
27
+ }
28
+ }
29
+ return sorted;
30
+ }
31
+ /**
32
+ * 简单 hash 函数
33
+ */
34
+ function simpleHash(str) {
35
+ let hash = 0;
36
+ for (let i = 0; i < str.length; i++) {
37
+ const char = str.charCodeAt(i);
38
+ hash = (hash << 5) - hash + char;
39
+ hash = hash & hash; // Convert to 32bit integer
40
+ }
41
+ return Math.abs(hash).toString(36);
42
+ }
43
+ /**
44
+ * 过滤对象中的 undefined 和 null 值
45
+ */
46
+ function filterUndefined(obj) {
47
+ if (!obj)
48
+ return undefined;
49
+ const result = {};
50
+ for (const [key, value] of Object.entries(obj)) {
51
+ if (value !== undefined && value !== null) {
52
+ result[key] = String(value);
53
+ }
54
+ }
55
+ return Object.keys(result).length > 0 ? result : undefined;
56
+ }
57
+ /**
58
+ * 默认缓存 key 生成
59
+ */
60
+ function defaultGenerateCacheKey(request, keyHeaders) {
61
+ const parts = [request.method, request.path];
62
+ if (request.query && Object.keys(request.query).length > 0) {
63
+ parts.push(JSON.stringify(sortKeys(request.query)));
64
+ }
65
+ if (request.body) {
66
+ parts.push(simpleHash(JSON.stringify(request.body)));
67
+ }
68
+ if ((keyHeaders === null || keyHeaders === void 0 ? void 0 : keyHeaders.length) && request.headers) {
69
+ const headerParts = keyHeaders
70
+ .filter((k) => request.headers[k])
71
+ .map((k) => `${k}:${request.headers[k]}`)
72
+ .join('|');
73
+ if (headerParts) {
74
+ parts.push(headerParts);
75
+ }
76
+ }
77
+ return parts.join(':');
78
+ }
79
+ export class HttpClient {
80
+ constructor(config) {
81
+ this.config = {
82
+ headers: {},
83
+ timeout: 30000,
84
+ retry: 2,
85
+ memoryCacheMaxSize: 100,
86
+ diskCacheMaxSize: 500,
87
+ dbName: 'xxf-http-cache',
88
+ defaultKeyHeaders: ['Authorization'],
89
+ defaultCache: undefined, // 默认不缓存 (OnlyRemote)
90
+ cacheInterceptor: undefined, // 默认无拦截器
91
+ ...config,
92
+ };
93
+ // 初始化 ky 实例
94
+ this.client = ky.create({
95
+ prefixUrl: this.config.baseUrl,
96
+ timeout: this.config.timeout,
97
+ retry: this.config.retry,
98
+ headers: this.config.headers,
99
+ });
100
+ // 初始化双层缓存
101
+ this.cache = new HttpCache({
102
+ memoryMaxSize: this.config.memoryCacheMaxSize,
103
+ diskMaxSize: this.config.diskCacheMaxSize,
104
+ dbName: this.config.dbName,
105
+ });
106
+ }
107
+ /**
108
+ * 获取 ky 实例(用于扩展 hooks 等)
109
+ */
110
+ getKyInstance() {
111
+ return this.client;
112
+ }
113
+ /**
114
+ * 设置 ky 实例
115
+ */
116
+ setKyInstance(instance) {
117
+ this.client = instance;
118
+ }
119
+ /**
120
+ * 扩展 ky 实例
121
+ */
122
+ extendKy(options) {
123
+ this.client = this.client.extend(options);
124
+ }
125
+ /**
126
+ * 执行请求,返回 ApiStream
127
+ */
128
+ request(requestConfig, options = {}) {
129
+ const self = this;
130
+ return new ApiStream(async function* () {
131
+ const { method, path, headers: configHeaders, timeout, cache: cacheConfig, retry } = requestConfig;
132
+ const { pathParams, query, body, cache: overrideCache, headers: optionHeaders } = options;
133
+ // 替换路径参数(自动 URL 编码)
134
+ let url = path;
135
+ if (pathParams) {
136
+ for (const [key, value] of Object.entries(pathParams)) {
137
+ url = url.replace(`{${key}}`, encodeURIComponent(String(value)));
138
+ }
139
+ }
140
+ // 合并 headers
141
+ const mergedHeaders = {
142
+ ...self.config.headers,
143
+ ...configHeaders,
144
+ ...optionHeaders,
145
+ };
146
+ // 合并缓存配置(优先级:调用时覆盖 > 接口配置 > 全局默认 > 硬编码默认)
147
+ const finalCacheConfig = {
148
+ mode: CacheMode.OnlyRemote,
149
+ ttl: 5 * 60 * 1000, // 默认 5 分钟
150
+ keyHeaders: self.config.defaultKeyHeaders,
151
+ ...self.config.defaultCache, // 全局默认缓存配置
152
+ ...cacheConfig, // 接口级配置
153
+ ...overrideCache, // 调用时覆盖
154
+ };
155
+ // 生成缓存 key
156
+ const cacheKeyRequest = {
157
+ method,
158
+ path: url,
159
+ query: query,
160
+ body,
161
+ headers: mergedHeaders,
162
+ pathParams,
163
+ };
164
+ const cacheKey = finalCacheConfig.cacheKey
165
+ ? finalCacheConfig.cacheKey(cacheKeyRequest)
166
+ : defaultGenerateCacheKey(cacheKeyRequest, finalCacheConfig.keyHeaders);
167
+ // 过滤 query 参数中的 undefined/null
168
+ const filteredQuery = filterUndefined(query);
169
+ // 请求函数(返回完整响应信息,供缓存拦截器使用)
170
+ const doRequest = async () => {
171
+ const kyOptions = {
172
+ method: method,
173
+ headers: mergedHeaders,
174
+ timeout,
175
+ retry,
176
+ searchParams: filteredQuery,
177
+ };
178
+ if (body && ['POST', 'PUT', 'PATCH'].includes(method)) {
179
+ kyOptions.json = body;
180
+ }
181
+ const response = await self.client(url, kyOptions);
182
+ const data = await response.json();
183
+ return {
184
+ data,
185
+ status: response.status,
186
+ headers: response.headers,
187
+ };
188
+ };
189
+ // 根据缓存模式执行
190
+ yield* self.executeWithCacheStrategy(finalCacheConfig.mode, cacheKey, finalCacheConfig.ttl, doRequest, cacheKeyRequest);
191
+ });
192
+ }
193
+ /**
194
+ * 根据缓存策略执行请求
195
+ */
196
+ async *executeWithCacheStrategy(mode, cacheKey, ttl, fetchFn, request) {
197
+ switch (mode) {
198
+ case CacheMode.OnlyRemote:
199
+ yield* this.strategyOnlyRemote(cacheKey, ttl, fetchFn, request);
200
+ break;
201
+ case CacheMode.OnlyCache:
202
+ yield* this.strategyOnlyCache(cacheKey);
203
+ break;
204
+ case CacheMode.IfCache:
205
+ yield* this.strategyIfCache(cacheKey, ttl, fetchFn, request);
206
+ break;
207
+ case CacheMode.FirstRemote:
208
+ yield* this.strategyFirstRemote(cacheKey, ttl, fetchFn, request);
209
+ break;
210
+ case CacheMode.FirstCache:
211
+ yield* this.strategyFirstCache(cacheKey, ttl, fetchFn, request);
212
+ break;
213
+ case CacheMode.LastCache:
214
+ yield* this.strategyLastCache(cacheKey, ttl, fetchFn, request);
215
+ break;
216
+ default:
217
+ yield* this.strategyOnlyRemote(cacheKey, ttl, fetchFn, request);
218
+ }
219
+ }
220
+ /**
221
+ * 判断响应是否应该缓存
222
+ *
223
+ * 调用全局缓存拦截器,根据响应内容决定是否缓存。
224
+ * 如果没有配置拦截器,默认允许缓存。
225
+ *
226
+ * @param result 请求响应结果
227
+ * @param cacheKey 缓存 key
228
+ * @param request 请求信息
229
+ * @param defaultTtl 默认 TTL
230
+ * @returns 是否缓存及最终 TTL
231
+ */
232
+ async shouldCacheResponse(result, cacheKey, request, defaultTtl) {
233
+ var _a;
234
+ // 没有配置拦截器,默认允许缓存
235
+ if (!this.config.cacheInterceptor) {
236
+ return { shouldCache: true, ttl: defaultTtl };
237
+ }
238
+ // 构建拦截器上下文
239
+ const context = {
240
+ data: result.data,
241
+ status: result.status,
242
+ headers: result.headers,
243
+ request,
244
+ cacheKey,
245
+ };
246
+ try {
247
+ // 调用拦截器(支持同步和异步)
248
+ const interceptorResult = await this.config.cacheInterceptor(context);
249
+ // 处理返回结果
250
+ if (typeof interceptorResult === 'boolean') {
251
+ return { shouldCache: interceptorResult, ttl: defaultTtl };
252
+ }
253
+ return {
254
+ shouldCache: interceptorResult.shouldCache,
255
+ ttl: (_a = interceptorResult.ttl) !== null && _a !== void 0 ? _a : defaultTtl,
256
+ };
257
+ }
258
+ catch (error) {
259
+ // 拦截器执行出错,保守起见不缓存,但不影响正常响应
260
+ console.warn('[HttpClient] cacheInterceptor error:', error);
261
+ return { shouldCache: false, ttl: defaultTtl };
262
+ }
263
+ }
264
+ /**
265
+ * 条件缓存:根据拦截器结果决定是否写入缓存
266
+ */
267
+ async conditionalCache(result, cacheKey, request, defaultTtl) {
268
+ const { shouldCache, ttl } = await this.shouldCacheResponse(result, cacheKey, request, defaultTtl);
269
+ if (shouldCache) {
270
+ await this.cache.set(cacheKey, result.data, ttl);
271
+ }
272
+ }
273
+ /**
274
+ * OnlyRemote: 只从服务器拿
275
+ */
276
+ async *strategyOnlyRemote(cacheKey, ttl, fetchFn, request) {
277
+ const result = await fetchFn();
278
+ await this.conditionalCache(result, cacheKey, request, ttl);
279
+ yield { data: result.data, fromCache: false };
280
+ }
281
+ /**
282
+ * OnlyCache: 只从缓存拿
283
+ */
284
+ async *strategyOnlyCache(cacheKey) {
285
+ const cached = await this.cache.get(cacheKey);
286
+ if (cached) {
287
+ yield { data: cached.data, fromCache: true };
288
+ }
289
+ // 没有缓存就不 yield(类似 Observable.empty())
290
+ }
291
+ /**
292
+ * IfCache: 有缓存返回缓存,否则请求网络
293
+ */
294
+ async *strategyIfCache(cacheKey, ttl, fetchFn, request) {
295
+ const cached = await this.cache.get(cacheKey);
296
+ if (cached) {
297
+ yield { data: cached.data, fromCache: true };
298
+ return;
299
+ }
300
+ const result = await fetchFn();
301
+ await this.conditionalCache(result, cacheKey, request, ttl);
302
+ yield { data: result.data, fromCache: false };
303
+ }
304
+ /**
305
+ * FirstRemote: 先从服务器获取,没网络则读缓存
306
+ */
307
+ async *strategyFirstRemote(cacheKey, ttl, fetchFn, request) {
308
+ try {
309
+ const result = await fetchFn();
310
+ await this.conditionalCache(result, cacheKey, request, ttl);
311
+ yield { data: result.data, fromCache: false };
312
+ }
313
+ catch (error) {
314
+ // 网络错误时尝试读取缓存
315
+ const cached = await this.cache.get(cacheKey);
316
+ if (cached) {
317
+ yield { data: cached.data, fromCache: true };
318
+ }
319
+ else {
320
+ throw error;
321
+ }
322
+ }
323
+ }
324
+ /**
325
+ * FirstCache: 先从本地缓存拿,然后从服务器拿,可能 yield 两次
326
+ */
327
+ async *strategyFirstCache(cacheKey, ttl, fetchFn, request) {
328
+ // 先尝试读取缓存
329
+ const cached = await this.cache.get(cacheKey);
330
+ if (cached) {
331
+ yield { data: cached.data, fromCache: true };
332
+ }
333
+ // 请求网络
334
+ try {
335
+ const result = await fetchFn();
336
+ await this.conditionalCache(result, cacheKey, request, ttl);
337
+ yield { data: result.data, fromCache: false };
338
+ }
339
+ catch (error) {
340
+ // 如果已有缓存,网络错误可以忽略
341
+ if (!cached) {
342
+ throw error;
343
+ }
344
+ // 有缓存时,网络错误静默处理
345
+ }
346
+ }
347
+ /**
348
+ * LastCache: 读上次缓存,没有则返回网络并同步缓存;有缓存也会后台同步网络但不 yield
349
+ */
350
+ async *strategyLastCache(cacheKey, ttl, fetchFn, request) {
351
+ const cached = await this.cache.get(cacheKey);
352
+ if (cached) {
353
+ // 有缓存,返回缓存,后台同步网络(不 yield)
354
+ yield { data: cached.data, fromCache: true };
355
+ // 后台静默更新(也会经过拦截器判断)
356
+ fetchFn()
357
+ .then((result) => this.conditionalCache(result, cacheKey, request, ttl))
358
+ .catch(() => {
359
+ /* 静默忽略错误 */
360
+ });
361
+ }
362
+ else {
363
+ // 无缓存,从网络获取
364
+ const result = await fetchFn();
365
+ await this.conditionalCache(result, cacheKey, request, ttl);
366
+ yield { data: result.data, fromCache: false };
367
+ }
368
+ }
369
+ /**
370
+ * 获取缓存实例(用于高级操作)
371
+ */
372
+ getCache() {
373
+ return this.cache;
374
+ }
375
+ /**
376
+ * 清空所有缓存
377
+ */
378
+ async clearCache() {
379
+ await this.cache.clear();
380
+ }
381
+ }
@@ -0,0 +1,3 @@
1
+ export { HttpClient } from './HttpClient';
2
+ export { ApiStream } from './ApiStream';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/http/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,2 @@
1
+ export { HttpClient } from './HttpClient';
2
+ export { ApiStream } from './ApiStream';