@lytjs/plugin-data-fetch 6.4.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/dist/index.cjs ADDED
@@ -0,0 +1,422 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DefaultCacheStorage: () => DefaultCacheStorage,
24
+ createFetch: () => createFetch,
25
+ createFetchManager: () => createFetchManager,
26
+ default: () => index_default,
27
+ generateCacheKey: () => generateCacheKey
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+ var import_core = require("@lytjs/core");
31
+ var import_reactivity = require("@lytjs/reactivity");
32
+ var DefaultCacheStorage = class {
33
+ constructor() {
34
+ this.cache = /* @__PURE__ */ new Map();
35
+ }
36
+ get(key) {
37
+ const entry = this.cache.get(key);
38
+ if (!entry) return null;
39
+ if (Date.now() > entry.expiresAt) {
40
+ this.cache.delete(key);
41
+ return null;
42
+ }
43
+ return entry;
44
+ }
45
+ set(key, value) {
46
+ this.cache.set(key, value);
47
+ }
48
+ delete(key) {
49
+ this.cache.delete(key);
50
+ }
51
+ clear() {
52
+ this.cache.clear();
53
+ }
54
+ has(key) {
55
+ const entry = this.cache.get(key);
56
+ if (!entry) return false;
57
+ if (Date.now() > entry.expiresAt) {
58
+ this.cache.delete(key);
59
+ return false;
60
+ }
61
+ return true;
62
+ }
63
+ };
64
+ function generateCacheKey(url, options = {}) {
65
+ const { method = "GET", headers, body, requestKey } = options;
66
+ if (requestKey) {
67
+ return requestKey;
68
+ }
69
+ let headerString = "";
70
+ if (headers) {
71
+ const headerEntries = Array.isArray(headers) ? headers : headers instanceof Headers ? Array.from(headers.entries()) : Object.entries(headers);
72
+ headerString = JSON.stringify(headerEntries.sort());
73
+ }
74
+ let bodyString = "";
75
+ if (body) {
76
+ bodyString = typeof body === "string" ? body : JSON.stringify(body);
77
+ }
78
+ return `${method}:${url}:${headerString}:${bodyString}`;
79
+ }
80
+ function delay(ms) {
81
+ return new Promise((resolve) => setTimeout(resolve, ms));
82
+ }
83
+ function createFetchError(message, config, response, originalError) {
84
+ const error = new Error(message);
85
+ error.config = config;
86
+ error.response = response;
87
+ error.originalError = originalError;
88
+ if (response) {
89
+ error.status = response.status;
90
+ }
91
+ return error;
92
+ }
93
+ function createFetch(url, options = {}, globalOptions = {}) {
94
+ const dataSignal = (0, import_reactivity.signal)(null);
95
+ const isLoadingSignal = (0, import_reactivity.signal)(false);
96
+ const errorSignal = (0, import_reactivity.signal)(null);
97
+ const refetchCountSignal = (0, import_reactivity.signal)(0);
98
+ let abortController = null;
99
+ const state = (0, import_reactivity.signalComputed)(() => ({
100
+ data: dataSignal(),
101
+ isLoading: isLoadingSignal(),
102
+ error: errorSignal(),
103
+ isSuccess: !isLoadingSignal() && dataSignal() !== null && errorSignal() === null,
104
+ isError: !isLoadingSignal() && errorSignal() !== null,
105
+ refetchCount: refetchCountSignal()
106
+ }));
107
+ const {
108
+ baseUrl = globalOptions.baseUrl || "",
109
+ timeout = globalOptions.timeout || 3e4,
110
+ retries = globalOptions.retries || 0,
111
+ retryDelay = globalOptions.retryDelay || 1e3,
112
+ cacheStrategy = globalOptions.defaultCacheStrategy || "no-cache",
113
+ cacheTime = globalOptions.defaultCacheTime || 3e5,
114
+ cancelDuplicate = false
115
+ } = options;
116
+ const cacheStorage = globalOptions.cacheStorage || new DefaultCacheStorage();
117
+ const fullUrl = baseUrl ? baseUrl.endsWith("/") ? baseUrl + url.slice(1) : baseUrl + url : url;
118
+ const cacheKey = generateCacheKey(fullUrl, options);
119
+ async function executeFetch() {
120
+ if (cancelDuplicate && abortController) {
121
+ abortController.abort();
122
+ }
123
+ abortController = new AbortController();
124
+ const abortSignal = abortController.signal;
125
+ let currentConfig = { ...options, signal: abortSignal };
126
+ const requestInterceptors = globalOptions.requestInterceptors || [];
127
+ for (const interceptor of requestInterceptors) {
128
+ currentConfig = await interceptor(currentConfig);
129
+ }
130
+ if (cacheStrategy === "cache-only") {
131
+ const cached = cacheStorage.get(cacheKey);
132
+ if (cached) {
133
+ return cached.data;
134
+ }
135
+ throw createFetchError("Cache miss", currentConfig);
136
+ }
137
+ if (cacheStrategy === "cache-first" && cacheStorage.has(cacheKey)) {
138
+ const cached = cacheStorage.get(cacheKey);
139
+ if (cached) {
140
+ return cached.data;
141
+ }
142
+ }
143
+ isLoadingSignal.set(true);
144
+ errorSignal.set(null);
145
+ let lastError = null;
146
+ for (let attempt = 0; attempt <= retries; attempt++) {
147
+ try {
148
+ const response = await fetchWithTimeout(fullUrl, currentConfig, timeout, abortSignal);
149
+ if (!response.ok) {
150
+ const error = createFetchError(`HTTP ${response.status}`, currentConfig, response);
151
+ let processedError = error;
152
+ const errorInterceptors = globalOptions.errorInterceptors || [];
153
+ for (const interceptor of errorInterceptors) {
154
+ processedError = await interceptor(processedError);
155
+ }
156
+ lastError = processedError;
157
+ if (options.onError) {
158
+ await options.onError(processedError);
159
+ }
160
+ if (attempt === retries || response.status >= 400 && response.status < 500) {
161
+ throw processedError;
162
+ }
163
+ await delay(retryDelay);
164
+ continue;
165
+ }
166
+ let result;
167
+ const contentType = response.headers.get("content-type") || "";
168
+ if (contentType.includes("application/json")) {
169
+ result = await response.json();
170
+ } else {
171
+ result = await response.text();
172
+ }
173
+ const responseInterceptors = globalOptions.responseInterceptors || [];
174
+ for (const interceptor of responseInterceptors) {
175
+ result = await interceptor(result);
176
+ }
177
+ if (cacheStrategy !== "no-cache") {
178
+ cacheStorage.set(cacheKey, {
179
+ data: result,
180
+ createdAt: Date.now(),
181
+ expiresAt: Date.now() + cacheTime
182
+ });
183
+ }
184
+ dataSignal.set(result);
185
+ isLoadingSignal.set(false);
186
+ return result;
187
+ } catch (error) {
188
+ if (abortSignal.aborted) {
189
+ lastError = createFetchError("Request cancelled", currentConfig, void 0, error);
190
+ break;
191
+ }
192
+ if (attempt < retries) {
193
+ await delay(retryDelay);
194
+ } else {
195
+ if (error instanceof Error && !error.config) {
196
+ lastError = createFetchError(error.message, currentConfig, void 0, error);
197
+ } else {
198
+ lastError = error;
199
+ }
200
+ }
201
+ }
202
+ }
203
+ if (lastError) {
204
+ if (cacheStrategy === "network-first" && cacheStorage.has(cacheKey)) {
205
+ const cached = cacheStorage.get(cacheKey);
206
+ if (cached) {
207
+ dataSignal.set(cached.data);
208
+ isLoadingSignal.set(false);
209
+ return cached.data;
210
+ }
211
+ }
212
+ errorSignal.set(lastError);
213
+ isLoadingSignal.set(false);
214
+ throw lastError;
215
+ }
216
+ throw createFetchError("Unknown error", currentConfig);
217
+ }
218
+ async function fetchWithTimeout(url2, config, _timeout, _signal) {
219
+ const response = await fetch(url2, config);
220
+ return response;
221
+ }
222
+ const doFetch = async () => {
223
+ return executeFetch();
224
+ };
225
+ const refetch = async () => {
226
+ refetchCountSignal.update((v) => v + 1);
227
+ return executeFetch();
228
+ };
229
+ const cancel = () => {
230
+ if (abortController) {
231
+ abortController.abort();
232
+ abortController = null;
233
+ }
234
+ isLoadingSignal.set(false);
235
+ };
236
+ const setData = (data) => {
237
+ if (typeof data === "function") {
238
+ dataSignal.update((prev) => data(prev));
239
+ } else {
240
+ dataSignal.set(data);
241
+ }
242
+ };
243
+ const setError = (error) => {
244
+ errorSignal.set(error);
245
+ };
246
+ const reset = () => {
247
+ dataSignal.set(null);
248
+ isLoadingSignal.set(false);
249
+ errorSignal.set(null);
250
+ refetchCountSignal.set(0);
251
+ abortController = null;
252
+ };
253
+ return {
254
+ get state() {
255
+ return state();
256
+ },
257
+ fetch: doFetch,
258
+ refetch,
259
+ cancel,
260
+ setData,
261
+ setError,
262
+ reset
263
+ };
264
+ }
265
+ function createFetchManager(globalOptions = {}) {
266
+ const cacheStorage = globalOptions.cacheStorage || new DefaultCacheStorage();
267
+ const requestInterceptors = globalOptions.requestInterceptors || [];
268
+ const responseInterceptors = globalOptions.responseInterceptors || [];
269
+ const errorInterceptors = globalOptions.errorInterceptors || [];
270
+ return {
271
+ /**
272
+ * 创建 Fetch 实例
273
+ */
274
+ createFetch(url, options = {}) {
275
+ return createFetch(url, options, {
276
+ ...globalOptions,
277
+ cacheStorage,
278
+ requestInterceptors,
279
+ responseInterceptors,
280
+ errorInterceptors
281
+ });
282
+ },
283
+ /**
284
+ * 简单 GET 请求
285
+ */
286
+ async get(url, options = {}) {
287
+ const instance = createFetch(url, { ...options, method: "GET" }, {
288
+ ...globalOptions,
289
+ cacheStorage,
290
+ requestInterceptors,
291
+ responseInterceptors,
292
+ errorInterceptors
293
+ });
294
+ return instance.fetch();
295
+ },
296
+ /**
297
+ * 简单 POST 请求
298
+ */
299
+ async post(url, body, options = {}) {
300
+ const instance = createFetch(url, {
301
+ ...options,
302
+ method: "POST",
303
+ body: body ? typeof body === "string" ? body : JSON.stringify(body) : void 0,
304
+ headers: {
305
+ "Content-Type": "application/json",
306
+ ...options.headers
307
+ }
308
+ }, {
309
+ ...globalOptions,
310
+ cacheStorage,
311
+ requestInterceptors,
312
+ responseInterceptors,
313
+ errorInterceptors
314
+ });
315
+ return instance.fetch();
316
+ },
317
+ /**
318
+ * 简单 PUT 请求
319
+ */
320
+ async put(url, body, options = {}) {
321
+ const instance = createFetch(url, {
322
+ ...options,
323
+ method: "PUT",
324
+ body: body ? typeof body === "string" ? body : JSON.stringify(body) : void 0,
325
+ headers: {
326
+ "Content-Type": "application/json",
327
+ ...options.headers
328
+ }
329
+ }, {
330
+ ...globalOptions,
331
+ cacheStorage,
332
+ requestInterceptors,
333
+ responseInterceptors,
334
+ errorInterceptors
335
+ });
336
+ return instance.fetch();
337
+ },
338
+ /**
339
+ * 简单 DELETE 请求
340
+ */
341
+ async delete(url, options = {}) {
342
+ const instance = createFetch(url, { ...options, method: "DELETE" }, {
343
+ ...globalOptions,
344
+ cacheStorage,
345
+ requestInterceptors,
346
+ responseInterceptors,
347
+ errorInterceptors
348
+ });
349
+ return instance.fetch();
350
+ },
351
+ /**
352
+ * 添加请求拦截器
353
+ */
354
+ addRequestInterceptor(interceptor) {
355
+ requestInterceptors.push(interceptor);
356
+ },
357
+ /**
358
+ * 添加响应拦截器
359
+ */
360
+ addResponseInterceptor(interceptor) {
361
+ responseInterceptors.push(interceptor);
362
+ },
363
+ /**
364
+ * 添加错误拦截器
365
+ */
366
+ addErrorInterceptor(interceptor) {
367
+ errorInterceptors.push(interceptor);
368
+ },
369
+ /**
370
+ * 获取缓存存储
371
+ */
372
+ getCacheStorage() {
373
+ return cacheStorage;
374
+ },
375
+ /**
376
+ * 清除缓存
377
+ */
378
+ clearCache() {
379
+ cacheStorage.clear();
380
+ },
381
+ /**
382
+ * 删除特定缓存
383
+ */
384
+ invalidateCache(key) {
385
+ cacheStorage.delete(key);
386
+ }
387
+ };
388
+ }
389
+ var pluginDataFetch = (0, import_core.definePlugin)({
390
+ name: "data-fetch",
391
+ version: "6.0.0",
392
+ description: "LytJS official data fetch plugin with caching, retries, and interceptors",
393
+ author: "LytJS Team",
394
+ keywords: ["lytjs", "fetch", "ajax", "cache", "interceptor"],
395
+ schema: {
396
+ type: "object",
397
+ object: {
398
+ properties: {
399
+ baseUrl: { type: "string" },
400
+ timeout: { type: "number", default: 3e4 },
401
+ retries: { type: "number", default: 0 },
402
+ retryDelay: { type: "number", default: 1e3 },
403
+ defaultCacheStrategy: { type: "string", default: "no-cache" },
404
+ defaultCacheTime: { type: "number", default: 3e5 }
405
+ }
406
+ }
407
+ },
408
+ install(app, options) {
409
+ const fetchManager = createFetchManager(options);
410
+ app.config.globalProperties.$fetch = fetchManager;
411
+ app.config.globalProperties.$http = fetchManager;
412
+ app.provide("lyt-fetch", fetchManager);
413
+ }
414
+ });
415
+ var index_default = pluginDataFetch;
416
+ // Annotate the CommonJS export names for ESM import in node:
417
+ 0 && (module.exports = {
418
+ DefaultCacheStorage,
419
+ createFetch,
420
+ createFetchManager,
421
+ generateCacheKey
422
+ });
@@ -0,0 +1,196 @@
1
+ import * as _lytjs_core from '@lytjs/core';
2
+
3
+ /**
4
+ * @lytjs/plugin-data-fetch - 类型定义
5
+ */
6
+ interface RequestOptions extends RequestInit {
7
+ /** 基础 URL */
8
+ baseUrl?: string;
9
+ /** 超时时间(毫秒) */
10
+ timeout?: number;
11
+ /** 重试次数 */
12
+ retries?: number;
13
+ /** 重试延迟(毫秒) */
14
+ retryDelay?: number;
15
+ /** 缓存策略 */
16
+ cacheStrategy?: 'no-cache' | 'cache-first' | 'network-first' | 'cache-only';
17
+ /** 缓存时间(毫秒) */
18
+ cacheTime?: number;
19
+ /** 请求标识,用于缓存键生成 */
20
+ requestKey?: string;
21
+ /** 是否取消重复请求 */
22
+ cancelDuplicate?: boolean;
23
+ /** 自定义错误处理 */
24
+ onError?: (error: FetchError) => void | Promise<void>;
25
+ }
26
+ interface FetchError extends Error {
27
+ /** HTTP 状态码 */
28
+ status?: number;
29
+ /** 原始响应 */
30
+ response?: Response;
31
+ /** 原始错误 */
32
+ originalError?: Error;
33
+ /** 请求配置 */
34
+ config?: RequestOptions;
35
+ }
36
+ interface CacheEntry<T = unknown> {
37
+ /** 缓存数据 */
38
+ data: T;
39
+ /** 过期时间 */
40
+ expiresAt: number;
41
+ /** 创建时间 */
42
+ createdAt: number;
43
+ }
44
+ interface CacheStorage {
45
+ /** 获取缓存 */
46
+ get<T = unknown>(key: string): CacheEntry<T> | null;
47
+ /** 设置缓存 */
48
+ set<T = unknown>(key: string, value: CacheEntry<T>): void;
49
+ /** 删除缓存 */
50
+ delete(key: string): void;
51
+ /** 清空缓存 */
52
+ clear(): void;
53
+ /** 检查缓存是否存在且有效 */
54
+ has(key: string): boolean;
55
+ }
56
+ interface Interceptor<T = unknown> {
57
+ /** 请求拦截器 */
58
+ request?: (config: RequestOptions) => RequestOptions | Promise<RequestOptions>;
59
+ /** 响应拦截器 */
60
+ response?: (response: T) => T | Promise<T>;
61
+ /** 错误拦截器 */
62
+ error?: (error: FetchError) => FetchError | Promise<FetchError>;
63
+ }
64
+ interface FetchState<T = unknown> {
65
+ /** 请求数据 */
66
+ data: T | null;
67
+ /** 加载状态 */
68
+ isLoading: boolean;
69
+ /** 错误状态 */
70
+ error: FetchError | null;
71
+ /** 是否已完成 */
72
+ isSuccess: boolean;
73
+ /** 是否失败 */
74
+ isError: boolean;
75
+ /** 请求次数 */
76
+ refetchCount: number;
77
+ }
78
+ interface FetchInstance<T = unknown> {
79
+ /** 当前状态 */
80
+ state: Readonly<FetchState<T>>;
81
+ /** 发起请求 */
82
+ fetch(): Promise<T>;
83
+ /** 重新请求 */
84
+ refetch(): Promise<T>;
85
+ /** 取消请求 */
86
+ cancel(): void;
87
+ /** 手动更新数据 */
88
+ setData(data: T | ((prev: T | null) => T)): void;
89
+ /** 手动更新错误 */
90
+ setError(error: FetchError | null): void;
91
+ /** 清空状态 */
92
+ reset(): void;
93
+ }
94
+ interface FetchPluginOptions {
95
+ /** 基础 URL */
96
+ baseUrl?: string;
97
+ /** 默认超时时间 */
98
+ timeout?: number;
99
+ /** 默认重试次数 */
100
+ retries?: number;
101
+ /** 默认重试延迟 */
102
+ retryDelay?: number;
103
+ /** 默认缓存策略 */
104
+ defaultCacheStrategy?: RequestOptions['cacheStrategy'];
105
+ /** 默认缓存时间 */
106
+ defaultCacheTime?: number;
107
+ /** 请求拦截器 */
108
+ requestInterceptors?: Interceptor['request'][];
109
+ /** 响应拦截器 */
110
+ responseInterceptors?: Interceptor['response'][];
111
+ /** 错误拦截器 */
112
+ errorInterceptors?: Interceptor['error'][];
113
+ /** 自定义缓存存储 */
114
+ cacheStorage?: CacheStorage;
115
+ }
116
+ interface RequestInterceptor {
117
+ (config: RequestOptions): RequestOptions | Promise<RequestOptions>;
118
+ }
119
+ interface ResponseInterceptor {
120
+ <T = unknown>(response: T): T | Promise<T>;
121
+ }
122
+ interface ErrorInterceptor {
123
+ (error: FetchError): FetchError | Promise<FetchError>;
124
+ }
125
+
126
+ /**
127
+ * 默认内存缓存实现
128
+ */
129
+ declare class DefaultCacheStorage implements CacheStorage {
130
+ private cache;
131
+ get<T = unknown>(key: string): CacheEntry<T> | null;
132
+ set<T = unknown>(key: string, value: CacheEntry<T>): void;
133
+ delete(key: string): void;
134
+ clear(): void;
135
+ has(key: string): boolean;
136
+ }
137
+ /**
138
+ * 生成缓存键
139
+ */
140
+ declare function generateCacheKey(url: string, options?: RequestOptions): string;
141
+ /**
142
+ * 创建数据获取实例
143
+ */
144
+ declare function createFetch<T = unknown>(url: string, options?: RequestOptions, globalOptions?: FetchPluginOptions): FetchInstance<T>;
145
+ /**
146
+ * 创建 Fetch 管理器
147
+ */
148
+ declare function createFetchManager(globalOptions?: FetchPluginOptions): {
149
+ /**
150
+ * 创建 Fetch 实例
151
+ */
152
+ createFetch<T = unknown>(url: string, options?: RequestOptions): FetchInstance<T>;
153
+ /**
154
+ * 简单 GET 请求
155
+ */
156
+ get<T = unknown>(url: string, options?: RequestOptions): Promise<T>;
157
+ /**
158
+ * 简单 POST 请求
159
+ */
160
+ post<T = unknown>(url: string, body?: unknown, options?: RequestOptions): Promise<T>;
161
+ /**
162
+ * 简单 PUT 请求
163
+ */
164
+ put<T = unknown>(url: string, body?: unknown, options?: RequestOptions): Promise<T>;
165
+ /**
166
+ * 简单 DELETE 请求
167
+ */
168
+ delete<T = unknown>(url: string, options?: RequestOptions): Promise<T>;
169
+ /**
170
+ * 添加请求拦截器
171
+ */
172
+ addRequestInterceptor(interceptor: RequestInterceptor): void;
173
+ /**
174
+ * 添加响应拦截器
175
+ */
176
+ addResponseInterceptor(interceptor: ResponseInterceptor): void;
177
+ /**
178
+ * 添加错误拦截器
179
+ */
180
+ addErrorInterceptor(interceptor: ErrorInterceptor): void;
181
+ /**
182
+ * 获取缓存存储
183
+ */
184
+ getCacheStorage(): CacheStorage;
185
+ /**
186
+ * 清除缓存
187
+ */
188
+ clearCache(): void;
189
+ /**
190
+ * 删除特定缓存
191
+ */
192
+ invalidateCache(key: string): void;
193
+ };
194
+ declare const pluginDataFetch: _lytjs_core.PluginDefinition<unknown>;
195
+
196
+ export { type CacheEntry, type CacheStorage, DefaultCacheStorage, type ErrorInterceptor, type FetchError, type FetchInstance, type FetchPluginOptions, type FetchState, type Interceptor, type RequestInterceptor, type RequestOptions, type ResponseInterceptor, createFetch, createFetchManager, pluginDataFetch as default, generateCacheKey };
@@ -0,0 +1,196 @@
1
+ import * as _lytjs_core from '@lytjs/core';
2
+
3
+ /**
4
+ * @lytjs/plugin-data-fetch - 类型定义
5
+ */
6
+ interface RequestOptions extends RequestInit {
7
+ /** 基础 URL */
8
+ baseUrl?: string;
9
+ /** 超时时间(毫秒) */
10
+ timeout?: number;
11
+ /** 重试次数 */
12
+ retries?: number;
13
+ /** 重试延迟(毫秒) */
14
+ retryDelay?: number;
15
+ /** 缓存策略 */
16
+ cacheStrategy?: 'no-cache' | 'cache-first' | 'network-first' | 'cache-only';
17
+ /** 缓存时间(毫秒) */
18
+ cacheTime?: number;
19
+ /** 请求标识,用于缓存键生成 */
20
+ requestKey?: string;
21
+ /** 是否取消重复请求 */
22
+ cancelDuplicate?: boolean;
23
+ /** 自定义错误处理 */
24
+ onError?: (error: FetchError) => void | Promise<void>;
25
+ }
26
+ interface FetchError extends Error {
27
+ /** HTTP 状态码 */
28
+ status?: number;
29
+ /** 原始响应 */
30
+ response?: Response;
31
+ /** 原始错误 */
32
+ originalError?: Error;
33
+ /** 请求配置 */
34
+ config?: RequestOptions;
35
+ }
36
+ interface CacheEntry<T = unknown> {
37
+ /** 缓存数据 */
38
+ data: T;
39
+ /** 过期时间 */
40
+ expiresAt: number;
41
+ /** 创建时间 */
42
+ createdAt: number;
43
+ }
44
+ interface CacheStorage {
45
+ /** 获取缓存 */
46
+ get<T = unknown>(key: string): CacheEntry<T> | null;
47
+ /** 设置缓存 */
48
+ set<T = unknown>(key: string, value: CacheEntry<T>): void;
49
+ /** 删除缓存 */
50
+ delete(key: string): void;
51
+ /** 清空缓存 */
52
+ clear(): void;
53
+ /** 检查缓存是否存在且有效 */
54
+ has(key: string): boolean;
55
+ }
56
+ interface Interceptor<T = unknown> {
57
+ /** 请求拦截器 */
58
+ request?: (config: RequestOptions) => RequestOptions | Promise<RequestOptions>;
59
+ /** 响应拦截器 */
60
+ response?: (response: T) => T | Promise<T>;
61
+ /** 错误拦截器 */
62
+ error?: (error: FetchError) => FetchError | Promise<FetchError>;
63
+ }
64
+ interface FetchState<T = unknown> {
65
+ /** 请求数据 */
66
+ data: T | null;
67
+ /** 加载状态 */
68
+ isLoading: boolean;
69
+ /** 错误状态 */
70
+ error: FetchError | null;
71
+ /** 是否已完成 */
72
+ isSuccess: boolean;
73
+ /** 是否失败 */
74
+ isError: boolean;
75
+ /** 请求次数 */
76
+ refetchCount: number;
77
+ }
78
+ interface FetchInstance<T = unknown> {
79
+ /** 当前状态 */
80
+ state: Readonly<FetchState<T>>;
81
+ /** 发起请求 */
82
+ fetch(): Promise<T>;
83
+ /** 重新请求 */
84
+ refetch(): Promise<T>;
85
+ /** 取消请求 */
86
+ cancel(): void;
87
+ /** 手动更新数据 */
88
+ setData(data: T | ((prev: T | null) => T)): void;
89
+ /** 手动更新错误 */
90
+ setError(error: FetchError | null): void;
91
+ /** 清空状态 */
92
+ reset(): void;
93
+ }
94
+ interface FetchPluginOptions {
95
+ /** 基础 URL */
96
+ baseUrl?: string;
97
+ /** 默认超时时间 */
98
+ timeout?: number;
99
+ /** 默认重试次数 */
100
+ retries?: number;
101
+ /** 默认重试延迟 */
102
+ retryDelay?: number;
103
+ /** 默认缓存策略 */
104
+ defaultCacheStrategy?: RequestOptions['cacheStrategy'];
105
+ /** 默认缓存时间 */
106
+ defaultCacheTime?: number;
107
+ /** 请求拦截器 */
108
+ requestInterceptors?: Interceptor['request'][];
109
+ /** 响应拦截器 */
110
+ responseInterceptors?: Interceptor['response'][];
111
+ /** 错误拦截器 */
112
+ errorInterceptors?: Interceptor['error'][];
113
+ /** 自定义缓存存储 */
114
+ cacheStorage?: CacheStorage;
115
+ }
116
+ interface RequestInterceptor {
117
+ (config: RequestOptions): RequestOptions | Promise<RequestOptions>;
118
+ }
119
+ interface ResponseInterceptor {
120
+ <T = unknown>(response: T): T | Promise<T>;
121
+ }
122
+ interface ErrorInterceptor {
123
+ (error: FetchError): FetchError | Promise<FetchError>;
124
+ }
125
+
126
+ /**
127
+ * 默认内存缓存实现
128
+ */
129
+ declare class DefaultCacheStorage implements CacheStorage {
130
+ private cache;
131
+ get<T = unknown>(key: string): CacheEntry<T> | null;
132
+ set<T = unknown>(key: string, value: CacheEntry<T>): void;
133
+ delete(key: string): void;
134
+ clear(): void;
135
+ has(key: string): boolean;
136
+ }
137
+ /**
138
+ * 生成缓存键
139
+ */
140
+ declare function generateCacheKey(url: string, options?: RequestOptions): string;
141
+ /**
142
+ * 创建数据获取实例
143
+ */
144
+ declare function createFetch<T = unknown>(url: string, options?: RequestOptions, globalOptions?: FetchPluginOptions): FetchInstance<T>;
145
+ /**
146
+ * 创建 Fetch 管理器
147
+ */
148
+ declare function createFetchManager(globalOptions?: FetchPluginOptions): {
149
+ /**
150
+ * 创建 Fetch 实例
151
+ */
152
+ createFetch<T = unknown>(url: string, options?: RequestOptions): FetchInstance<T>;
153
+ /**
154
+ * 简单 GET 请求
155
+ */
156
+ get<T = unknown>(url: string, options?: RequestOptions): Promise<T>;
157
+ /**
158
+ * 简单 POST 请求
159
+ */
160
+ post<T = unknown>(url: string, body?: unknown, options?: RequestOptions): Promise<T>;
161
+ /**
162
+ * 简单 PUT 请求
163
+ */
164
+ put<T = unknown>(url: string, body?: unknown, options?: RequestOptions): Promise<T>;
165
+ /**
166
+ * 简单 DELETE 请求
167
+ */
168
+ delete<T = unknown>(url: string, options?: RequestOptions): Promise<T>;
169
+ /**
170
+ * 添加请求拦截器
171
+ */
172
+ addRequestInterceptor(interceptor: RequestInterceptor): void;
173
+ /**
174
+ * 添加响应拦截器
175
+ */
176
+ addResponseInterceptor(interceptor: ResponseInterceptor): void;
177
+ /**
178
+ * 添加错误拦截器
179
+ */
180
+ addErrorInterceptor(interceptor: ErrorInterceptor): void;
181
+ /**
182
+ * 获取缓存存储
183
+ */
184
+ getCacheStorage(): CacheStorage;
185
+ /**
186
+ * 清除缓存
187
+ */
188
+ clearCache(): void;
189
+ /**
190
+ * 删除特定缓存
191
+ */
192
+ invalidateCache(key: string): void;
193
+ };
194
+ declare const pluginDataFetch: _lytjs_core.PluginDefinition<unknown>;
195
+
196
+ export { type CacheEntry, type CacheStorage, DefaultCacheStorage, type ErrorInterceptor, type FetchError, type FetchInstance, type FetchPluginOptions, type FetchState, type Interceptor, type RequestInterceptor, type RequestOptions, type ResponseInterceptor, createFetch, createFetchManager, pluginDataFetch as default, generateCacheKey };
package/dist/index.js ADDED
@@ -0,0 +1,394 @@
1
+ // src/index.ts
2
+ import { definePlugin } from "@lytjs/core";
3
+ import { signal, signalComputed as computed } from "@lytjs/reactivity";
4
+ var DefaultCacheStorage = class {
5
+ constructor() {
6
+ this.cache = /* @__PURE__ */ new Map();
7
+ }
8
+ get(key) {
9
+ const entry = this.cache.get(key);
10
+ if (!entry) return null;
11
+ if (Date.now() > entry.expiresAt) {
12
+ this.cache.delete(key);
13
+ return null;
14
+ }
15
+ return entry;
16
+ }
17
+ set(key, value) {
18
+ this.cache.set(key, value);
19
+ }
20
+ delete(key) {
21
+ this.cache.delete(key);
22
+ }
23
+ clear() {
24
+ this.cache.clear();
25
+ }
26
+ has(key) {
27
+ const entry = this.cache.get(key);
28
+ if (!entry) return false;
29
+ if (Date.now() > entry.expiresAt) {
30
+ this.cache.delete(key);
31
+ return false;
32
+ }
33
+ return true;
34
+ }
35
+ };
36
+ function generateCacheKey(url, options = {}) {
37
+ const { method = "GET", headers, body, requestKey } = options;
38
+ if (requestKey) {
39
+ return requestKey;
40
+ }
41
+ let headerString = "";
42
+ if (headers) {
43
+ const headerEntries = Array.isArray(headers) ? headers : headers instanceof Headers ? Array.from(headers.entries()) : Object.entries(headers);
44
+ headerString = JSON.stringify(headerEntries.sort());
45
+ }
46
+ let bodyString = "";
47
+ if (body) {
48
+ bodyString = typeof body === "string" ? body : JSON.stringify(body);
49
+ }
50
+ return `${method}:${url}:${headerString}:${bodyString}`;
51
+ }
52
+ function delay(ms) {
53
+ return new Promise((resolve) => setTimeout(resolve, ms));
54
+ }
55
+ function createFetchError(message, config, response, originalError) {
56
+ const error = new Error(message);
57
+ error.config = config;
58
+ error.response = response;
59
+ error.originalError = originalError;
60
+ if (response) {
61
+ error.status = response.status;
62
+ }
63
+ return error;
64
+ }
65
+ function createFetch(url, options = {}, globalOptions = {}) {
66
+ const dataSignal = signal(null);
67
+ const isLoadingSignal = signal(false);
68
+ const errorSignal = signal(null);
69
+ const refetchCountSignal = signal(0);
70
+ let abortController = null;
71
+ const state = computed(() => ({
72
+ data: dataSignal(),
73
+ isLoading: isLoadingSignal(),
74
+ error: errorSignal(),
75
+ isSuccess: !isLoadingSignal() && dataSignal() !== null && errorSignal() === null,
76
+ isError: !isLoadingSignal() && errorSignal() !== null,
77
+ refetchCount: refetchCountSignal()
78
+ }));
79
+ const {
80
+ baseUrl = globalOptions.baseUrl || "",
81
+ timeout = globalOptions.timeout || 3e4,
82
+ retries = globalOptions.retries || 0,
83
+ retryDelay = globalOptions.retryDelay || 1e3,
84
+ cacheStrategy = globalOptions.defaultCacheStrategy || "no-cache",
85
+ cacheTime = globalOptions.defaultCacheTime || 3e5,
86
+ cancelDuplicate = false
87
+ } = options;
88
+ const cacheStorage = globalOptions.cacheStorage || new DefaultCacheStorage();
89
+ const fullUrl = baseUrl ? baseUrl.endsWith("/") ? baseUrl + url.slice(1) : baseUrl + url : url;
90
+ const cacheKey = generateCacheKey(fullUrl, options);
91
+ async function executeFetch() {
92
+ if (cancelDuplicate && abortController) {
93
+ abortController.abort();
94
+ }
95
+ abortController = new AbortController();
96
+ const abortSignal = abortController.signal;
97
+ let currentConfig = { ...options, signal: abortSignal };
98
+ const requestInterceptors = globalOptions.requestInterceptors || [];
99
+ for (const interceptor of requestInterceptors) {
100
+ currentConfig = await interceptor(currentConfig);
101
+ }
102
+ if (cacheStrategy === "cache-only") {
103
+ const cached = cacheStorage.get(cacheKey);
104
+ if (cached) {
105
+ return cached.data;
106
+ }
107
+ throw createFetchError("Cache miss", currentConfig);
108
+ }
109
+ if (cacheStrategy === "cache-first" && cacheStorage.has(cacheKey)) {
110
+ const cached = cacheStorage.get(cacheKey);
111
+ if (cached) {
112
+ return cached.data;
113
+ }
114
+ }
115
+ isLoadingSignal.set(true);
116
+ errorSignal.set(null);
117
+ let lastError = null;
118
+ for (let attempt = 0; attempt <= retries; attempt++) {
119
+ try {
120
+ const response = await fetchWithTimeout(fullUrl, currentConfig, timeout, abortSignal);
121
+ if (!response.ok) {
122
+ const error = createFetchError(`HTTP ${response.status}`, currentConfig, response);
123
+ let processedError = error;
124
+ const errorInterceptors = globalOptions.errorInterceptors || [];
125
+ for (const interceptor of errorInterceptors) {
126
+ processedError = await interceptor(processedError);
127
+ }
128
+ lastError = processedError;
129
+ if (options.onError) {
130
+ await options.onError(processedError);
131
+ }
132
+ if (attempt === retries || response.status >= 400 && response.status < 500) {
133
+ throw processedError;
134
+ }
135
+ await delay(retryDelay);
136
+ continue;
137
+ }
138
+ let result;
139
+ const contentType = response.headers.get("content-type") || "";
140
+ if (contentType.includes("application/json")) {
141
+ result = await response.json();
142
+ } else {
143
+ result = await response.text();
144
+ }
145
+ const responseInterceptors = globalOptions.responseInterceptors || [];
146
+ for (const interceptor of responseInterceptors) {
147
+ result = await interceptor(result);
148
+ }
149
+ if (cacheStrategy !== "no-cache") {
150
+ cacheStorage.set(cacheKey, {
151
+ data: result,
152
+ createdAt: Date.now(),
153
+ expiresAt: Date.now() + cacheTime
154
+ });
155
+ }
156
+ dataSignal.set(result);
157
+ isLoadingSignal.set(false);
158
+ return result;
159
+ } catch (error) {
160
+ if (abortSignal.aborted) {
161
+ lastError = createFetchError("Request cancelled", currentConfig, void 0, error);
162
+ break;
163
+ }
164
+ if (attempt < retries) {
165
+ await delay(retryDelay);
166
+ } else {
167
+ if (error instanceof Error && !error.config) {
168
+ lastError = createFetchError(error.message, currentConfig, void 0, error);
169
+ } else {
170
+ lastError = error;
171
+ }
172
+ }
173
+ }
174
+ }
175
+ if (lastError) {
176
+ if (cacheStrategy === "network-first" && cacheStorage.has(cacheKey)) {
177
+ const cached = cacheStorage.get(cacheKey);
178
+ if (cached) {
179
+ dataSignal.set(cached.data);
180
+ isLoadingSignal.set(false);
181
+ return cached.data;
182
+ }
183
+ }
184
+ errorSignal.set(lastError);
185
+ isLoadingSignal.set(false);
186
+ throw lastError;
187
+ }
188
+ throw createFetchError("Unknown error", currentConfig);
189
+ }
190
+ async function fetchWithTimeout(url2, config, _timeout, _signal) {
191
+ const response = await fetch(url2, config);
192
+ return response;
193
+ }
194
+ const doFetch = async () => {
195
+ return executeFetch();
196
+ };
197
+ const refetch = async () => {
198
+ refetchCountSignal.update((v) => v + 1);
199
+ return executeFetch();
200
+ };
201
+ const cancel = () => {
202
+ if (abortController) {
203
+ abortController.abort();
204
+ abortController = null;
205
+ }
206
+ isLoadingSignal.set(false);
207
+ };
208
+ const setData = (data) => {
209
+ if (typeof data === "function") {
210
+ dataSignal.update((prev) => data(prev));
211
+ } else {
212
+ dataSignal.set(data);
213
+ }
214
+ };
215
+ const setError = (error) => {
216
+ errorSignal.set(error);
217
+ };
218
+ const reset = () => {
219
+ dataSignal.set(null);
220
+ isLoadingSignal.set(false);
221
+ errorSignal.set(null);
222
+ refetchCountSignal.set(0);
223
+ abortController = null;
224
+ };
225
+ return {
226
+ get state() {
227
+ return state();
228
+ },
229
+ fetch: doFetch,
230
+ refetch,
231
+ cancel,
232
+ setData,
233
+ setError,
234
+ reset
235
+ };
236
+ }
237
+ function createFetchManager(globalOptions = {}) {
238
+ const cacheStorage = globalOptions.cacheStorage || new DefaultCacheStorage();
239
+ const requestInterceptors = globalOptions.requestInterceptors || [];
240
+ const responseInterceptors = globalOptions.responseInterceptors || [];
241
+ const errorInterceptors = globalOptions.errorInterceptors || [];
242
+ return {
243
+ /**
244
+ * 创建 Fetch 实例
245
+ */
246
+ createFetch(url, options = {}) {
247
+ return createFetch(url, options, {
248
+ ...globalOptions,
249
+ cacheStorage,
250
+ requestInterceptors,
251
+ responseInterceptors,
252
+ errorInterceptors
253
+ });
254
+ },
255
+ /**
256
+ * 简单 GET 请求
257
+ */
258
+ async get(url, options = {}) {
259
+ const instance = createFetch(url, { ...options, method: "GET" }, {
260
+ ...globalOptions,
261
+ cacheStorage,
262
+ requestInterceptors,
263
+ responseInterceptors,
264
+ errorInterceptors
265
+ });
266
+ return instance.fetch();
267
+ },
268
+ /**
269
+ * 简单 POST 请求
270
+ */
271
+ async post(url, body, options = {}) {
272
+ const instance = createFetch(url, {
273
+ ...options,
274
+ method: "POST",
275
+ body: body ? typeof body === "string" ? body : JSON.stringify(body) : void 0,
276
+ headers: {
277
+ "Content-Type": "application/json",
278
+ ...options.headers
279
+ }
280
+ }, {
281
+ ...globalOptions,
282
+ cacheStorage,
283
+ requestInterceptors,
284
+ responseInterceptors,
285
+ errorInterceptors
286
+ });
287
+ return instance.fetch();
288
+ },
289
+ /**
290
+ * 简单 PUT 请求
291
+ */
292
+ async put(url, body, options = {}) {
293
+ const instance = createFetch(url, {
294
+ ...options,
295
+ method: "PUT",
296
+ body: body ? typeof body === "string" ? body : JSON.stringify(body) : void 0,
297
+ headers: {
298
+ "Content-Type": "application/json",
299
+ ...options.headers
300
+ }
301
+ }, {
302
+ ...globalOptions,
303
+ cacheStorage,
304
+ requestInterceptors,
305
+ responseInterceptors,
306
+ errorInterceptors
307
+ });
308
+ return instance.fetch();
309
+ },
310
+ /**
311
+ * 简单 DELETE 请求
312
+ */
313
+ async delete(url, options = {}) {
314
+ const instance = createFetch(url, { ...options, method: "DELETE" }, {
315
+ ...globalOptions,
316
+ cacheStorage,
317
+ requestInterceptors,
318
+ responseInterceptors,
319
+ errorInterceptors
320
+ });
321
+ return instance.fetch();
322
+ },
323
+ /**
324
+ * 添加请求拦截器
325
+ */
326
+ addRequestInterceptor(interceptor) {
327
+ requestInterceptors.push(interceptor);
328
+ },
329
+ /**
330
+ * 添加响应拦截器
331
+ */
332
+ addResponseInterceptor(interceptor) {
333
+ responseInterceptors.push(interceptor);
334
+ },
335
+ /**
336
+ * 添加错误拦截器
337
+ */
338
+ addErrorInterceptor(interceptor) {
339
+ errorInterceptors.push(interceptor);
340
+ },
341
+ /**
342
+ * 获取缓存存储
343
+ */
344
+ getCacheStorage() {
345
+ return cacheStorage;
346
+ },
347
+ /**
348
+ * 清除缓存
349
+ */
350
+ clearCache() {
351
+ cacheStorage.clear();
352
+ },
353
+ /**
354
+ * 删除特定缓存
355
+ */
356
+ invalidateCache(key) {
357
+ cacheStorage.delete(key);
358
+ }
359
+ };
360
+ }
361
+ var pluginDataFetch = definePlugin({
362
+ name: "data-fetch",
363
+ version: "6.0.0",
364
+ description: "LytJS official data fetch plugin with caching, retries, and interceptors",
365
+ author: "LytJS Team",
366
+ keywords: ["lytjs", "fetch", "ajax", "cache", "interceptor"],
367
+ schema: {
368
+ type: "object",
369
+ object: {
370
+ properties: {
371
+ baseUrl: { type: "string" },
372
+ timeout: { type: "number", default: 3e4 },
373
+ retries: { type: "number", default: 0 },
374
+ retryDelay: { type: "number", default: 1e3 },
375
+ defaultCacheStrategy: { type: "string", default: "no-cache" },
376
+ defaultCacheTime: { type: "number", default: 3e5 }
377
+ }
378
+ }
379
+ },
380
+ install(app, options) {
381
+ const fetchManager = createFetchManager(options);
382
+ app.config.globalProperties.$fetch = fetchManager;
383
+ app.config.globalProperties.$http = fetchManager;
384
+ app.provide("lyt-fetch", fetchManager);
385
+ }
386
+ });
387
+ var index_default = pluginDataFetch;
388
+ export {
389
+ DefaultCacheStorage,
390
+ createFetch,
391
+ createFetchManager,
392
+ index_default as default,
393
+ generateCacheKey
394
+ };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@lytjs/plugin-data-fetch",
3
+ "version": "6.4.0",
4
+ "description": "LytJS official data fetch plugin with caching, retries, and interceptors",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./package.json": "./package.json"
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "sideEffects": false,
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "dev": "tsup --watch",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest",
26
+ "test:coverage": "vitest run --coverage",
27
+ "type-check": "tsc --noEmit",
28
+ "lint": "eslint \"src/**/*.ts\"",
29
+ "clean": "rm -rf dist"
30
+ },
31
+ "dependencies": {
32
+ "@lytjs/core": "^6.4.0",
33
+ "@lytjs/reactivity": "^6.4.0"
34
+ },
35
+ "devDependencies": {
36
+ "tsup": "^8.0.0",
37
+ "typescript": "^5.4.0",
38
+ "vitest": "^3.0.0"
39
+ },
40
+ "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://gitee.com/lytjs/lytjs.git",
44
+ "directory": "packages/plugins/packages/plugin-data-fetch"
45
+ },
46
+ "keywords": [
47
+ "lytjs",
48
+ "fetch",
49
+ "ajax",
50
+ "cache",
51
+ "interceptor"
52
+ ]
53
+ }