@yh-ui/request 0.1.21
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/LICENSE +21 -0
- package/README.md +274 -0
- package/dist/adapters/fetch.cjs +157 -0
- package/dist/adapters/fetch.d.ts +25 -0
- package/dist/adapters/fetch.mjs +148 -0
- package/dist/adapters/index.cjs +27 -0
- package/dist/adapters/index.d.ts +5 -0
- package/dist/adapters/index.mjs +2 -0
- package/dist/adapters/platform.cjs +394 -0
- package/dist/adapters/platform.d.ts +72 -0
- package/dist/adapters/platform.mjs +369 -0
- package/dist/cache/index.cjs +56 -0
- package/dist/cache/index.d.ts +21 -0
- package/dist/cache/index.mjs +14 -0
- package/dist/cache/indexedDB.cjs +188 -0
- package/dist/cache/indexedDB.d.ts +58 -0
- package/dist/cache/indexedDB.mjs +176 -0
- package/dist/cache/localStorage.cjs +158 -0
- package/dist/cache/localStorage.d.ts +58 -0
- package/dist/cache/localStorage.mjs +153 -0
- package/dist/cache/memory.cjs +112 -0
- package/dist/cache/memory.d.ts +71 -0
- package/dist/cache/memory.mjs +103 -0
- package/dist/graphql.cjs +255 -0
- package/dist/graphql.d.ts +192 -0
- package/dist/graphql.mjs +235 -0
- package/dist/http-cache.cjs +248 -0
- package/dist/http-cache.d.ts +156 -0
- package/dist/http-cache.mjs +233 -0
- package/dist/index.cjs +181 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.mjs +16 -0
- package/dist/interceptors/debug.cjs +139 -0
- package/dist/interceptors/debug.d.ts +92 -0
- package/dist/interceptors/debug.mjs +130 -0
- package/dist/interceptors/index.cjs +38 -0
- package/dist/interceptors/index.d.ts +6 -0
- package/dist/interceptors/index.mjs +3 -0
- package/dist/interceptors/progress.cjs +185 -0
- package/dist/interceptors/progress.d.ts +97 -0
- package/dist/interceptors/progress.mjs +177 -0
- package/dist/interceptors/security.cjs +154 -0
- package/dist/interceptors/security.d.ts +83 -0
- package/dist/interceptors/security.mjs +134 -0
- package/dist/plugin.cjs +166 -0
- package/dist/plugin.d.ts +106 -0
- package/dist/plugin.mjs +163 -0
- package/dist/request.cjs +396 -0
- package/dist/request.d.ts +111 -0
- package/dist/request.mjs +339 -0
- package/dist/types.cjs +13 -0
- package/dist/types.d.ts +157 -0
- package/dist/types.mjs +7 -0
- package/dist/useAIStream.cjs +125 -0
- package/dist/useAIStream.d.ts +89 -0
- package/dist/useAIStream.mjs +108 -0
- package/dist/useLoadMore.cjs +136 -0
- package/dist/useLoadMore.d.ts +84 -0
- package/dist/useLoadMore.mjs +134 -0
- package/dist/usePagination.cjs +141 -0
- package/dist/usePagination.d.ts +89 -0
- package/dist/usePagination.mjs +132 -0
- package/dist/useQueue.cjs +243 -0
- package/dist/useQueue.d.ts +118 -0
- package/dist/useQueue.mjs +239 -0
- package/dist/useRequest.cjs +325 -0
- package/dist/useRequest.d.ts +126 -0
- package/dist/useRequest.mjs +329 -0
- package/dist/useRequestQueue.cjs +36 -0
- package/dist/useRequestQueue.d.ts +52 -0
- package/dist/useRequestQueue.mjs +27 -0
- package/dist/useSSE.cjs +241 -0
- package/dist/useSSE.d.ts +74 -0
- package/dist/useSSE.mjs +226 -0
- package/dist/websocket.cjs +325 -0
- package/dist/websocket.d.ts +163 -0
- package/dist/websocket.mjs +316 -0
- package/package.json +61 -0
package/dist/graphql.mjs
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
export class GraphQLBuilder {
|
|
2
|
+
operationType = "query";
|
|
3
|
+
operationName = "";
|
|
4
|
+
variables = {};
|
|
5
|
+
fields = [];
|
|
6
|
+
fragments = [];
|
|
7
|
+
variablesDefinitions = [];
|
|
8
|
+
/**
|
|
9
|
+
* 设置操作类型
|
|
10
|
+
*/
|
|
11
|
+
operation(type) {
|
|
12
|
+
this.operationType = type;
|
|
13
|
+
return this;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 设置操作名称
|
|
17
|
+
*/
|
|
18
|
+
name(name) {
|
|
19
|
+
this.operationName = name;
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 添加变量定义
|
|
24
|
+
*/
|
|
25
|
+
variable(name, type, defaultValue) {
|
|
26
|
+
const defaultStr = defaultValue !== void 0 ? ` = ${JSON.stringify(defaultValue)}` : "";
|
|
27
|
+
this.variablesDefinitions.push(`$${name}: ${type}${defaultStr}`);
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 设置变量
|
|
32
|
+
*/
|
|
33
|
+
variables_(vars) {
|
|
34
|
+
this.variables = vars;
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 添加字段
|
|
39
|
+
*/
|
|
40
|
+
field(field) {
|
|
41
|
+
this.fields.push(field);
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 添加多个字段
|
|
46
|
+
*/
|
|
47
|
+
addFields(fieldsInput) {
|
|
48
|
+
this.fields.push(...fieldsInput);
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 添加内联片段
|
|
53
|
+
*/
|
|
54
|
+
inlineFragment(type, fieldsInput) {
|
|
55
|
+
this.fields.push(`... on ${type} { ${fieldsInput.join(" ")} }`);
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 添加片段
|
|
60
|
+
*/
|
|
61
|
+
fragment(name) {
|
|
62
|
+
this.fragments.push(`...${name}`);
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 构建 GraphQL 查询字符串
|
|
67
|
+
*/
|
|
68
|
+
build() {
|
|
69
|
+
const parts = [];
|
|
70
|
+
const operationHeader = this.operationName ? `${this.operationType} ${this.operationName}` : this.operationType;
|
|
71
|
+
const variablesStr = this.variablesDefinitions.length > 0 ? `(${this.variablesDefinitions.join(", ")})` : "";
|
|
72
|
+
const fieldsStr = this.fields.join("\n");
|
|
73
|
+
parts.push(`${operationHeader}${variablesStr} {
|
|
74
|
+
${fieldsStr}
|
|
75
|
+
}`);
|
|
76
|
+
return parts.join("\n");
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 转换为请求配置
|
|
80
|
+
*/
|
|
81
|
+
toRequestOptions(options = {}) {
|
|
82
|
+
return {
|
|
83
|
+
...options,
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: {
|
|
86
|
+
"Content-Type": "application/json",
|
|
87
|
+
...options.headers
|
|
88
|
+
},
|
|
89
|
+
data: {
|
|
90
|
+
query: this.build(),
|
|
91
|
+
variables: Object.keys(this.variables).length > 0 ? this.variables : void 0,
|
|
92
|
+
operationName: this.operationName || void 0
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 重置构建器
|
|
98
|
+
*/
|
|
99
|
+
reset() {
|
|
100
|
+
this.operationType = "query";
|
|
101
|
+
this.operationName = "";
|
|
102
|
+
this.variables = {};
|
|
103
|
+
this.fields = [];
|
|
104
|
+
this.fragments = [];
|
|
105
|
+
this.variablesDefinitions = [];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export class GraphQLClient {
|
|
109
|
+
endpoint;
|
|
110
|
+
defaultHeaders = {};
|
|
111
|
+
defaultOptions = {};
|
|
112
|
+
/**
|
|
113
|
+
* 创建 GraphQL 客户端
|
|
114
|
+
*/
|
|
115
|
+
constructor(endpoint, options = {}) {
|
|
116
|
+
this.endpoint = endpoint;
|
|
117
|
+
this.defaultHeaders = options.headers || {};
|
|
118
|
+
this.defaultOptions = {
|
|
119
|
+
credentials: options.credentials || "same-origin"
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 发送查询
|
|
124
|
+
*/
|
|
125
|
+
async query(query, variables, options = {}) {
|
|
126
|
+
const queryStr = typeof query === "string" ? query : query.build();
|
|
127
|
+
return this.request({
|
|
128
|
+
...this.defaultOptions,
|
|
129
|
+
...options,
|
|
130
|
+
method: "POST",
|
|
131
|
+
url: this.endpoint,
|
|
132
|
+
headers: {
|
|
133
|
+
...this.defaultHeaders,
|
|
134
|
+
"Content-Type": "application/json",
|
|
135
|
+
...options.headers
|
|
136
|
+
},
|
|
137
|
+
data: {
|
|
138
|
+
query: queryStr,
|
|
139
|
+
variables,
|
|
140
|
+
operationName: typeof query !== "string" ? query["operationName"] : void 0
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 发送 mutation
|
|
146
|
+
*/
|
|
147
|
+
async mutate(mutation, variables, options = {}) {
|
|
148
|
+
return this.query(mutation, variables, options);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 发送原始请求
|
|
152
|
+
*/
|
|
153
|
+
async request(options) {
|
|
154
|
+
const { request } = await import("./request.mjs");
|
|
155
|
+
return request.request({
|
|
156
|
+
...options,
|
|
157
|
+
url: options.url || this.endpoint
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* 创建查询构建器
|
|
162
|
+
*/
|
|
163
|
+
createQuery() {
|
|
164
|
+
return new GraphQLBuilder().operation("query");
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* 创建 Mutation 构建器
|
|
168
|
+
*/
|
|
169
|
+
createMutation() {
|
|
170
|
+
return new GraphQLBuilder().operation("mutation");
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* 设置默认请求头
|
|
174
|
+
*/
|
|
175
|
+
setHeader(key, value) {
|
|
176
|
+
this.defaultHeaders[key] = value;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 设置认证 Token
|
|
180
|
+
*/
|
|
181
|
+
setAuthToken(token) {
|
|
182
|
+
this.defaultHeaders["Authorization"] = `Bearer ${token}`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
export function createGraphQLBuilder() {
|
|
186
|
+
return new GraphQLBuilder();
|
|
187
|
+
}
|
|
188
|
+
export function createGraphQLClient(endpoint, options) {
|
|
189
|
+
return new GraphQLClient(endpoint, options);
|
|
190
|
+
}
|
|
191
|
+
export function gql(strings, ...values) {
|
|
192
|
+
let result = "";
|
|
193
|
+
strings.forEach((str, i) => {
|
|
194
|
+
result += str;
|
|
195
|
+
if (i < values.length) {
|
|
196
|
+
const value = values[i];
|
|
197
|
+
if (typeof value === "string") {
|
|
198
|
+
result += value;
|
|
199
|
+
} else if (value !== void 0) {
|
|
200
|
+
result += JSON.stringify(value);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
export function createPaginatedQuery(queryName, itemType, pageSize = 10) {
|
|
207
|
+
const query = `
|
|
208
|
+
query ${queryName}($first: Int!, $after: String) {
|
|
209
|
+
${queryName}(first: $first, after: $after) {
|
|
210
|
+
pageInfo {
|
|
211
|
+
hasNextPage
|
|
212
|
+
endCursor
|
|
213
|
+
}
|
|
214
|
+
edges {
|
|
215
|
+
node {
|
|
216
|
+
${itemType}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
`;
|
|
222
|
+
const getVariables = (cursor) => ({
|
|
223
|
+
first: pageSize,
|
|
224
|
+
...cursor ? { after: cursor } : {}
|
|
225
|
+
});
|
|
226
|
+
return { query, getVariables };
|
|
227
|
+
}
|
|
228
|
+
export function parseGraphQLResponse(response) {
|
|
229
|
+
if (response.errors && response.errors.length > 0) {
|
|
230
|
+
const error = new Error(response.errors.map((e) => e.message).join(", "));
|
|
231
|
+
error.code = "GRAPHQL_ERROR";
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
return response.data;
|
|
235
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.HttpCache = void 0;
|
|
7
|
+
exports.createConditionalRequestInterceptor = createConditionalRequestInterceptor;
|
|
8
|
+
exports.createHttpCacheInterceptor = createHttpCacheInterceptor;
|
|
9
|
+
exports.httpCache = void 0;
|
|
10
|
+
exports.isResponseCacheable = isResponseCacheable;
|
|
11
|
+
exports.parseCacheControl = parseCacheControl;
|
|
12
|
+
class HttpCache {
|
|
13
|
+
cache = /* @__PURE__ */new Map();
|
|
14
|
+
options;
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.options = {
|
|
17
|
+
enabled: options.enabled ?? true,
|
|
18
|
+
etagHeader: options.etagHeader ?? "ETag",
|
|
19
|
+
lastModifiedHeader: options.lastModifiedHeader ?? "Last-Modified",
|
|
20
|
+
maxAge: options.maxAge ?? 5 * 60 * 1e3,
|
|
21
|
+
// 5 分钟
|
|
22
|
+
staleWhileRevalidate: options.staleWhileRevalidate ?? false,
|
|
23
|
+
staleTime: options.staleTime ?? 60 * 1e3
|
|
24
|
+
// 1 分钟
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 从响应中提取缓存信息
|
|
29
|
+
*/
|
|
30
|
+
extractCacheInfo(response) {
|
|
31
|
+
const etag = response.headers.get(this.options.etagHeader) || void 0;
|
|
32
|
+
const lastModified = response.headers.get(this.options.lastModifiedHeader) || void 0;
|
|
33
|
+
return {
|
|
34
|
+
etag,
|
|
35
|
+
lastModified
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 生成缓存键
|
|
40
|
+
*/
|
|
41
|
+
getCacheKey(config) {
|
|
42
|
+
return `${config.method}:${config.fullPath || config.url}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 获取缓存
|
|
46
|
+
*/
|
|
47
|
+
get(key) {
|
|
48
|
+
const entry = this.cache.get(key);
|
|
49
|
+
if (!entry) return void 0;
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
if (entry.expireTime && now > entry.expireTime) {
|
|
52
|
+
this.cache.delete(key);
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
return entry;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 设置缓存
|
|
59
|
+
*/
|
|
60
|
+
set(key, data, response) {
|
|
61
|
+
const {
|
|
62
|
+
etag,
|
|
63
|
+
lastModified
|
|
64
|
+
} = this.extractCacheInfo(response);
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
const cacheControl = response.headers.get("Cache-Control");
|
|
67
|
+
let maxAge = this.options.maxAge;
|
|
68
|
+
if (cacheControl) {
|
|
69
|
+
const maxAgeMatch = cacheControl.match(/max-age=(\d+)/);
|
|
70
|
+
if (maxAgeMatch) {
|
|
71
|
+
maxAge = parseInt(maxAgeMatch[1], 10) * 1e3;
|
|
72
|
+
}
|
|
73
|
+
if (cacheControl.includes("no-cache")) {
|
|
74
|
+
maxAge = 0;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const entry = {
|
|
78
|
+
etag,
|
|
79
|
+
lastModified,
|
|
80
|
+
data,
|
|
81
|
+
expireTime: maxAge > 0 ? now + maxAge : now,
|
|
82
|
+
createTime: now
|
|
83
|
+
};
|
|
84
|
+
this.cache.set(key, entry);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 构建条件请求头
|
|
88
|
+
*/
|
|
89
|
+
buildConditionalHeaders(config) {
|
|
90
|
+
const key = this.getCacheKey(config);
|
|
91
|
+
const entry = this.get(key);
|
|
92
|
+
const headers = {};
|
|
93
|
+
if (entry) {
|
|
94
|
+
if (entry.etag) {
|
|
95
|
+
headers["If-None-Match"] = entry.etag;
|
|
96
|
+
}
|
|
97
|
+
if (entry.lastModified) {
|
|
98
|
+
headers["If-Modified-Since"] = entry.lastModified;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return headers;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 处理条件响应
|
|
105
|
+
*/
|
|
106
|
+
handleConditionalResponse(config, response, data) {
|
|
107
|
+
const key = this.getCacheKey(config);
|
|
108
|
+
const entry = this.get(key);
|
|
109
|
+
if (response.status === 304) {
|
|
110
|
+
if (entry) {
|
|
111
|
+
return {
|
|
112
|
+
isModified: false,
|
|
113
|
+
data: entry.data,
|
|
114
|
+
entry
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
this.set(key, data, response);
|
|
119
|
+
return {
|
|
120
|
+
isModified: true,
|
|
121
|
+
data
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 获取缓存数据(可能返回过期数据用于 stale-while-revalidate)
|
|
126
|
+
*/
|
|
127
|
+
getWithFallback(key) {
|
|
128
|
+
const entry = this.get(key);
|
|
129
|
+
if (!entry) return void 0;
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
const isStale = !!(entry.expireTime && now > entry.expireTime);
|
|
132
|
+
return {
|
|
133
|
+
data: entry.data,
|
|
134
|
+
isStale
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 删除缓存
|
|
139
|
+
*/
|
|
140
|
+
delete(key) {
|
|
141
|
+
return this.cache.delete(key);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* 清空缓存
|
|
145
|
+
*/
|
|
146
|
+
clear() {
|
|
147
|
+
this.cache.clear();
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 获取缓存大小
|
|
151
|
+
*/
|
|
152
|
+
size() {
|
|
153
|
+
return this.cache.size;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* 清理过期缓存
|
|
157
|
+
*/
|
|
158
|
+
cleanup() {
|
|
159
|
+
const now = Date.now();
|
|
160
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
161
|
+
if (entry.expireTime && now > entry.expireTime) {
|
|
162
|
+
this.cache.delete(key);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
exports.HttpCache = HttpCache;
|
|
168
|
+
function createHttpCacheInterceptor(options = {}) {
|
|
169
|
+
const cache = new HttpCache(options);
|
|
170
|
+
return {
|
|
171
|
+
/**
|
|
172
|
+
* 请求拦截 - 添加条件请求头
|
|
173
|
+
*/
|
|
174
|
+
onRequest: config => {
|
|
175
|
+
if (!options.enabled) return config;
|
|
176
|
+
if (config.method !== "GET") return config;
|
|
177
|
+
const conditionalHeaders = cache.buildConditionalHeaders(config);
|
|
178
|
+
return {
|
|
179
|
+
...config,
|
|
180
|
+
headers: {
|
|
181
|
+
...config.headers,
|
|
182
|
+
...conditionalHeaders
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
/**
|
|
187
|
+
* 响应拦截 - 处理条件响应
|
|
188
|
+
*/
|
|
189
|
+
onResponse: response => {
|
|
190
|
+
if (!options.enabled) return response;
|
|
191
|
+
if (response.config.method !== "GET") return response;
|
|
192
|
+
if (response.response.status === 304) {
|
|
193
|
+
const key = `${response.config.method}:${response.config.fullPath || response.config.url}`;
|
|
194
|
+
const entry = cache.get(key);
|
|
195
|
+
if (entry) {
|
|
196
|
+
return {
|
|
197
|
+
...response,
|
|
198
|
+
data: entry.data
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
cache.set(`${response.config.method}:${response.config.fullPath || response.config.url}`, response.data, response.response);
|
|
203
|
+
return response;
|
|
204
|
+
},
|
|
205
|
+
/**
|
|
206
|
+
* 获取缓存实例
|
|
207
|
+
*/
|
|
208
|
+
getCache: () => cache
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const httpCache = exports.httpCache = new HttpCache();
|
|
212
|
+
function createConditionalRequestInterceptor(options = {}) {
|
|
213
|
+
return createHttpCacheInterceptor({
|
|
214
|
+
...options
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
function parseCacheControl(header) {
|
|
218
|
+
if (!header) return {
|
|
219
|
+
noCache: false,
|
|
220
|
+
noStore: false,
|
|
221
|
+
mustRevalidate: false,
|
|
222
|
+
isPublic: false,
|
|
223
|
+
isPrivate: false
|
|
224
|
+
};
|
|
225
|
+
const result = {
|
|
226
|
+
noCache: false,
|
|
227
|
+
noStore: false,
|
|
228
|
+
mustRevalidate: false,
|
|
229
|
+
isPublic: false,
|
|
230
|
+
isPrivate: false
|
|
231
|
+
};
|
|
232
|
+
const maxAgeMatch = header.match(/max-age=(\d+)/);
|
|
233
|
+
if (maxAgeMatch) {
|
|
234
|
+
result.maxAge = parseInt(maxAgeMatch[1], 10);
|
|
235
|
+
}
|
|
236
|
+
if (header.includes("no-cache")) result.noCache = true;
|
|
237
|
+
if (header.includes("no-store")) result.noStore = true;
|
|
238
|
+
if (header.includes("must-revalidate")) result.mustRevalidate = true;
|
|
239
|
+
if (header.includes("public")) result.isPublic = true;
|
|
240
|
+
if (header.includes("private")) result.isPrivate = true;
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
function isResponseCacheable(response) {
|
|
244
|
+
const cacheControl = parseCacheControl(response.headers.get("Cache-Control"));
|
|
245
|
+
if (cacheControl.noStore) return false;
|
|
246
|
+
if (cacheControl.isPublic || cacheControl.isPrivate) return true;
|
|
247
|
+
return response.ok;
|
|
248
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP 缓存协议支持
|
|
3
|
+
* 支持 ETag、Last-Modified、Cache-Control 等 HTTP 缓存机制
|
|
4
|
+
*/
|
|
5
|
+
import type { InternalRequestOptions, RequestResponse } from './types';
|
|
6
|
+
/** HTTP 缓存控制选项 */
|
|
7
|
+
export interface HttpCacheOptions {
|
|
8
|
+
/** 启用条件请求 */
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
/** ETag 头名称 */
|
|
11
|
+
etagHeader?: string;
|
|
12
|
+
/** Last-Modified 头名称 */
|
|
13
|
+
lastModifiedHeader?: string;
|
|
14
|
+
/** 缓存过期时间 (ms) */
|
|
15
|
+
maxAge?: number;
|
|
16
|
+
/** 是否启用 stale-while-revalidate */
|
|
17
|
+
staleWhileRevalidate?: boolean;
|
|
18
|
+
/** 备用缓存时间 (当使用 stale-while-revalidate 时) */
|
|
19
|
+
staleTime?: number;
|
|
20
|
+
}
|
|
21
|
+
/** 缓存条目 */
|
|
22
|
+
export interface HttpCacheEntry {
|
|
23
|
+
/** ETag */
|
|
24
|
+
etag?: string;
|
|
25
|
+
/** Last-Modified */
|
|
26
|
+
lastModified?: string;
|
|
27
|
+
/** 缓存数据 */
|
|
28
|
+
data: unknown;
|
|
29
|
+
/** 过期时间 */
|
|
30
|
+
expireTime: number;
|
|
31
|
+
/** 创建时间 */
|
|
32
|
+
createTime: number;
|
|
33
|
+
/** 响应头 */
|
|
34
|
+
headers?: Record<string, string>;
|
|
35
|
+
}
|
|
36
|
+
/** 条件请求响应 */
|
|
37
|
+
export interface ConditionalResponse {
|
|
38
|
+
/** 是否有修改 */
|
|
39
|
+
isModified: boolean;
|
|
40
|
+
/** 响应数据 */
|
|
41
|
+
data?: unknown;
|
|
42
|
+
/** 缓存条目 */
|
|
43
|
+
entry?: HttpCacheEntry;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* HTTP 缓存管理器
|
|
47
|
+
* 支持 HTTP 条件请求 (ETag、Last-Modified)
|
|
48
|
+
*/
|
|
49
|
+
export declare class HttpCache {
|
|
50
|
+
private cache;
|
|
51
|
+
private options;
|
|
52
|
+
constructor(options?: HttpCacheOptions);
|
|
53
|
+
/**
|
|
54
|
+
* 从响应中提取缓存信息
|
|
55
|
+
*/
|
|
56
|
+
extractCacheInfo(response: Response): {
|
|
57
|
+
etag?: string;
|
|
58
|
+
lastModified?: string;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* 生成缓存键
|
|
62
|
+
*/
|
|
63
|
+
private getCacheKey;
|
|
64
|
+
/**
|
|
65
|
+
* 获取缓存
|
|
66
|
+
*/
|
|
67
|
+
get(key: string): HttpCacheEntry | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* 设置缓存
|
|
70
|
+
*/
|
|
71
|
+
set(key: string, data: unknown, response: Response): void;
|
|
72
|
+
/**
|
|
73
|
+
* 构建条件请求头
|
|
74
|
+
*/
|
|
75
|
+
buildConditionalHeaders(config: InternalRequestOptions): Record<string, string>;
|
|
76
|
+
/**
|
|
77
|
+
* 处理条件响应
|
|
78
|
+
*/
|
|
79
|
+
handleConditionalResponse(config: InternalRequestOptions, response: Response, data: unknown): ConditionalResponse;
|
|
80
|
+
/**
|
|
81
|
+
* 获取缓存数据(可能返回过期数据用于 stale-while-revalidate)
|
|
82
|
+
*/
|
|
83
|
+
getWithFallback(key: string): {
|
|
84
|
+
data: unknown;
|
|
85
|
+
isStale: boolean;
|
|
86
|
+
} | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* 删除缓存
|
|
89
|
+
*/
|
|
90
|
+
delete(key: string): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* 清空缓存
|
|
93
|
+
*/
|
|
94
|
+
clear(): void;
|
|
95
|
+
/**
|
|
96
|
+
* 获取缓存大小
|
|
97
|
+
*/
|
|
98
|
+
size(): number;
|
|
99
|
+
/**
|
|
100
|
+
* 清理过期缓存
|
|
101
|
+
*/
|
|
102
|
+
cleanup(): void;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 创建 HTTP 缓存拦截器
|
|
106
|
+
*/
|
|
107
|
+
export declare function createHttpCacheInterceptor(options?: HttpCacheOptions): {
|
|
108
|
+
/**
|
|
109
|
+
* 请求拦截 - 添加条件请求头
|
|
110
|
+
*/
|
|
111
|
+
onRequest: (config: InternalRequestOptions) => InternalRequestOptions;
|
|
112
|
+
/**
|
|
113
|
+
* 响应拦截 - 处理条件响应
|
|
114
|
+
*/
|
|
115
|
+
onResponse: <T>(response: RequestResponse<T>) => RequestResponse<T>;
|
|
116
|
+
/**
|
|
117
|
+
* 获取缓存实例
|
|
118
|
+
*/
|
|
119
|
+
getCache: () => HttpCache;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* 全局 HTTP 缓存实例
|
|
123
|
+
*/
|
|
124
|
+
export declare const httpCache: HttpCache;
|
|
125
|
+
/**
|
|
126
|
+
* 创建条件请求拦截器(使用全局缓存)
|
|
127
|
+
*/
|
|
128
|
+
export declare function createConditionalRequestInterceptor(options?: HttpCacheOptions): {
|
|
129
|
+
/**
|
|
130
|
+
* 请求拦截 - 添加条件请求头
|
|
131
|
+
*/
|
|
132
|
+
onRequest: (config: InternalRequestOptions) => InternalRequestOptions;
|
|
133
|
+
/**
|
|
134
|
+
* 响应拦截 - 处理条件响应
|
|
135
|
+
*/
|
|
136
|
+
onResponse: <T>(response: RequestResponse<T>) => RequestResponse<T>;
|
|
137
|
+
/**
|
|
138
|
+
* 获取缓存实例
|
|
139
|
+
*/
|
|
140
|
+
getCache: () => HttpCache;
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* 解析 Cache-Control 头
|
|
144
|
+
*/
|
|
145
|
+
export declare function parseCacheControl(header: string | null): {
|
|
146
|
+
maxAge?: number;
|
|
147
|
+
noCache: boolean;
|
|
148
|
+
noStore: boolean;
|
|
149
|
+
mustRevalidate: boolean;
|
|
150
|
+
isPublic: boolean;
|
|
151
|
+
isPrivate: boolean;
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* 判断响应是否可以缓存
|
|
155
|
+
*/
|
|
156
|
+
export declare function isResponseCacheable(response: Response): boolean;
|