@huangqi_an/request 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +525 -2
- package/dist/adapters/axios.d.ts +11 -0
- package/dist/adapters/axios.js +56 -0
- package/dist/adapters/axios.js.map +1 -0
- package/dist/adapters/fetch.d.ts +12 -0
- package/dist/adapters/fetch.js +109 -0
- package/dist/adapters/fetch.js.map +1 -0
- package/dist/adapters/uniapp.d.ts +13 -0
- package/dist/adapters/uniapp.js +92 -0
- package/dist/adapters/uniapp.js.map +1 -0
- package/dist/chunk-DQI4SM2Q.js +146 -0
- package/dist/chunk-DQI4SM2Q.js.map +1 -0
- package/dist/chunk-FY6YU3PV.js +23 -0
- package/dist/chunk-FY6YU3PV.js.map +1 -0
- package/dist/chunk-KLAI63DX.js +26 -0
- package/dist/chunk-KLAI63DX.js.map +1 -0
- package/dist/chunk-OV6LXQTN.js +37 -0
- package/dist/chunk-OV6LXQTN.js.map +1 -0
- package/dist/chunk-UCTOFBDY.js +27 -0
- package/dist/chunk-UCTOFBDY.js.map +1 -0
- package/dist/chunk-Z4N22SLG.js +28 -0
- package/dist/chunk-Z4N22SLG.js.map +1 -0
- package/dist/core.d.ts +56 -0
- package/dist/core.js +12 -0
- package/dist/core.js.map +1 -0
- package/dist/enhancers/dedupe.d.ts +29 -0
- package/dist/enhancers/dedupe.js +37 -0
- package/dist/enhancers/dedupe.js.map +1 -0
- package/dist/enhancers/retry.d.ts +38 -0
- package/dist/enhancers/retry.js +47 -0
- package/dist/enhancers/retry.js.map +1 -0
- package/dist/errors.d.ts +1 -0
- package/dist/errors.js +11 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptors/auth.d.ts +17 -0
- package/dist/interceptors/auth.js +7 -0
- package/dist/interceptors/auth.js.map +1 -0
- package/dist/interceptors/index.d.ts +5 -0
- package/dist/interceptors/index.js +20 -0
- package/dist/interceptors/index.js.map +1 -0
- package/dist/interceptors/logger.d.ts +25 -0
- package/dist/interceptors/logger.js +7 -0
- package/dist/interceptors/logger.js.map +1 -0
- package/dist/interceptors/request-id.d.ts +23 -0
- package/dist/interceptors/request-id.js +7 -0
- package/dist/interceptors/request-id.js.map +1 -0
- package/dist/interceptors/unwrap.d.ts +22 -0
- package/dist/interceptors/unwrap.js +8 -0
- package/dist/interceptors/unwrap.js.map +1 -0
- package/dist/types-CoJSp-ar.d.ts +137 -0
- package/package.json +109 -4
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { RequestAdapter, RequestConfig, RawResponse, RequestInterceptor, ResponseInterceptor, ErrorInterceptor, ChainableAdapter } from './index.js';
|
|
2
|
+
|
|
3
|
+
interface CreateRequestOptions {
|
|
4
|
+
/** 必传:adapter 插件(axiosAdapter / fetchAdapter / uniappAdapter / 自定义 / 测试 mock) */
|
|
5
|
+
adapter: RequestAdapter;
|
|
6
|
+
baseURL?: string;
|
|
7
|
+
headers?: Record<string, string>;
|
|
8
|
+
timeout?: number;
|
|
9
|
+
}
|
|
10
|
+
interface RequestInstance {
|
|
11
|
+
request<T = unknown>(config: RequestConfig): Promise<RawResponse<T>>;
|
|
12
|
+
get<T = unknown>(url: string, config?: Omit<RequestConfig, 'url' | 'method'>): Promise<RawResponse<T>>;
|
|
13
|
+
post<T = unknown>(url: string, data?: unknown, config?: Omit<RequestConfig, 'url' | 'method' | 'data'>): Promise<RawResponse<T>>;
|
|
14
|
+
put<T = unknown>(url: string, data?: unknown, config?: Omit<RequestConfig, 'url' | 'method' | 'data'>): Promise<RawResponse<T>>;
|
|
15
|
+
delete<T = unknown>(url: string, config?: Omit<RequestConfig, 'url' | 'method'>): Promise<RawResponse<T>>;
|
|
16
|
+
interceptors: {
|
|
17
|
+
request: {
|
|
18
|
+
use(interceptor: RequestInterceptor): void;
|
|
19
|
+
};
|
|
20
|
+
/** 成功链:转换 / 剥壳 */
|
|
21
|
+
response: {
|
|
22
|
+
use(interceptor: ResponseInterceptor): void;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* 错误链:所有失败都会经过(请求拦截器抛错 / 网络失败 / 非 2xx / 响应链抛错)。
|
|
26
|
+
* 返回 RawResponse = 恢复;返回/抛出 RequestError = 替换;无返回值 = 放行(观察)。
|
|
27
|
+
*/
|
|
28
|
+
error: {
|
|
29
|
+
use(interceptor: ErrorInterceptor): void;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 拼接 baseURL + url,并把 params 序列化为 query。
|
|
35
|
+
* - 绝对 URL 忽略 baseURL
|
|
36
|
+
* - 跳过 null / undefined;数组值序列化为重复 key(?id=1&id=2)
|
|
37
|
+
* - url 已带 ? 时用 & 追加
|
|
38
|
+
* params 统一在这里处理,保证两端一致,adapter 无需各自实现。
|
|
39
|
+
*/
|
|
40
|
+
declare function buildUrl(baseURL: string | undefined, url: string, params?: Record<string, unknown>): string;
|
|
41
|
+
/**
|
|
42
|
+
* adapter 的显式扩展点:把任意(含第三方)adapter 变为可链式增强的 adapter,
|
|
43
|
+
* 同时为自定义 adapter 的实现函数提供参数类型推断。
|
|
44
|
+
*
|
|
45
|
+
* const myAdapter = defineAdapter(async (config) => {
|
|
46
|
+
* // config.url 已是 core 拼好 params 的完整地址
|
|
47
|
+
* // 返回 RawResponse;取消时抛 name='AbortError' 的错误
|
|
48
|
+
* }).use(withRetry()).use(withDedupe()) // 注册顺序 = 请求流经顺序
|
|
49
|
+
*
|
|
50
|
+
* 返回值本身仍是合法的 RequestAdapter,可直接传给 createRequest。
|
|
51
|
+
*/
|
|
52
|
+
declare function defineAdapter(adapter: RequestAdapter): ChainableAdapter;
|
|
53
|
+
declare function createRequest(options: CreateRequestOptions): RequestInstance;
|
|
54
|
+
|
|
55
|
+
export { buildUrl, createRequest, defineAdapter };
|
|
56
|
+
export type { CreateRequestOptions, RequestInstance };
|
package/dist/core.js
ADDED
package/dist/core.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { RequestConfig, AdapterEnhancer, RequestAdapter } from '../index.js';
|
|
2
|
+
|
|
3
|
+
interface DedupeOptions {
|
|
4
|
+
/**
|
|
5
|
+
* 参与去重的方法,默认 ['GET', 'HEAD'](幂等)。
|
|
6
|
+
* 防双击提交等场景显式传入(如 methods: ['POST']),此时相同载荷的并发提交共享同一结果。
|
|
7
|
+
*/
|
|
8
|
+
methods?: string[];
|
|
9
|
+
/**
|
|
10
|
+
* 去重键,默认 `${method} ${url} ${JSON.stringify(data)}`。
|
|
11
|
+
* params 已拼进 url,无需重复纳入;data 键序不同会被视为不同请求(宁可漏去重,不可错合并)。
|
|
12
|
+
*/
|
|
13
|
+
key?: (config: RequestConfig) => string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* adapter 装饰器:进行中的相同请求共享同一个 Promise。
|
|
17
|
+
* - 默认只对幂等方法(GET/HEAD)生效,写方法默认放行
|
|
18
|
+
* - 请求 settle 后自动清理,不影响后续新请求
|
|
19
|
+
* - 共享的是同一个 RawResponse 对象;响应链拦截器用 {...res} 展开产生新对象,不会互相污染
|
|
20
|
+
*
|
|
21
|
+
* 两种形态:
|
|
22
|
+
* - withDedupe(adapter, options):直接包装
|
|
23
|
+
* - withDedupe(options):返回装饰器,交给 adapter.use() 组合
|
|
24
|
+
*/
|
|
25
|
+
declare function withDedupe(options?: DedupeOptions): AdapterEnhancer;
|
|
26
|
+
declare function withDedupe(adapter: RequestAdapter, options?: DedupeOptions): RequestAdapter;
|
|
27
|
+
|
|
28
|
+
export { withDedupe };
|
|
29
|
+
export type { DedupeOptions };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/enhancers/dedupe.ts
|
|
2
|
+
var SAFE_METHODS = ["GET", "HEAD"];
|
|
3
|
+
function defaultKey(config) {
|
|
4
|
+
let dataKey = "";
|
|
5
|
+
if (config.data !== void 0) {
|
|
6
|
+
try {
|
|
7
|
+
dataKey = JSON.stringify(config.data);
|
|
8
|
+
} catch {
|
|
9
|
+
dataKey = `${Math.random()}`;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return `${config.method ?? "GET"} ${config.url} ${dataKey}`;
|
|
13
|
+
}
|
|
14
|
+
function withDedupe(first, maybeOptions) {
|
|
15
|
+
const options = first !== void 0 && typeof first !== "function" ? first : maybeOptions ?? {};
|
|
16
|
+
const decorate = (adapter) => {
|
|
17
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
18
|
+
const methods = options.methods ?? SAFE_METHODS;
|
|
19
|
+
const keyOf = options.key ?? defaultKey;
|
|
20
|
+
return (config) => {
|
|
21
|
+
if (!methods.includes(config.method ?? "GET")) return adapter(config);
|
|
22
|
+
const key = keyOf(config);
|
|
23
|
+
const pending = inFlight.get(key);
|
|
24
|
+
if (pending) return pending;
|
|
25
|
+
const promise = adapter(config).finally(() => {
|
|
26
|
+
inFlight.delete(key);
|
|
27
|
+
});
|
|
28
|
+
inFlight.set(key, promise);
|
|
29
|
+
return promise;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
return typeof first === "function" ? decorate(first) : decorate;
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
withDedupe
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=dedupe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/enhancers/dedupe.ts"],"sourcesContent":["import type { AdapterEnhancer, RawResponse, RequestAdapter, RequestConfig } from '../types'\n\n/**\n * 默认只去重幂等的安全方法。\n * 写方法(POST/PUT/DELETE)默认放行:去重的 share 语义不会\"多发\",但会\"少发\"——\n * 对故意并发的非幂等请求(如计数 +1)静默合并等于丢操作。\n */\nconst SAFE_METHODS = ['GET', 'HEAD']\n\nfunction defaultKey(config: RequestConfig): string {\n let dataKey = ''\n if (config.data !== undefined) {\n try {\n dataKey = JSON.stringify(config.data)\n } catch {\n // 无法稳定序列化的 data 不参与去重:宁可漏去重,不可错合并\n dataKey = `${Math.random()}`\n }\n }\n return `${config.method ?? 'GET'} ${config.url} ${dataKey}`\n}\n\nexport interface DedupeOptions {\n /**\n * 参与去重的方法,默认 ['GET', 'HEAD'](幂等)。\n * 防双击提交等场景显式传入(如 methods: ['POST']),此时相同载荷的并发提交共享同一结果。\n */\n methods?: string[]\n /**\n * 去重键,默认 `${method} ${url} ${JSON.stringify(data)}`。\n * params 已拼进 url,无需重复纳入;data 键序不同会被视为不同请求(宁可漏去重,不可错合并)。\n */\n key?: (config: RequestConfig) => string\n}\n\n/**\n * adapter 装饰器:进行中的相同请求共享同一个 Promise。\n * - 默认只对幂等方法(GET/HEAD)生效,写方法默认放行\n * - 请求 settle 后自动清理,不影响后续新请求\n * - 共享的是同一个 RawResponse 对象;响应链拦截器用 {...res} 展开产生新对象,不会互相污染\n *\n * 两种形态:\n * - withDedupe(adapter, options):直接包装\n * - withDedupe(options):返回装饰器,交给 adapter.use() 组合\n */\nexport function withDedupe(options?: DedupeOptions): AdapterEnhancer\nexport function withDedupe(adapter: RequestAdapter, options?: DedupeOptions): RequestAdapter\nexport function withDedupe(\n first?: RequestAdapter | DedupeOptions,\n maybeOptions?: DedupeOptions,\n): RequestAdapter | AdapterEnhancer {\n const options: DedupeOptions =\n first !== undefined && typeof first !== 'function' ? first : (maybeOptions ?? {})\n\n const decorate = (adapter: RequestAdapter): RequestAdapter => {\n const inFlight = new Map<string, Promise<RawResponse>>()\n const methods = options.methods ?? SAFE_METHODS\n const keyOf = options.key ?? defaultKey\n\n return (config) => {\n // 非幂等方法直接放行,不参与去重\n if (!methods.includes(config.method ?? 'GET')) return adapter(config)\n\n const key = keyOf(config)\n const pending = inFlight.get(key)\n if (pending) return pending\n\n const promise = adapter(config).finally(() => {\n inFlight.delete(key)\n })\n inFlight.set(key, promise)\n return promise\n }\n }\n\n return typeof first === 'function' ? decorate(first) : decorate\n}\n"],"mappings":";AAOA,IAAM,eAAe,CAAC,OAAO,MAAM;AAEnC,SAAS,WAAW,QAA+B;AACjD,MAAI,UAAU;AACd,MAAI,OAAO,SAAS,QAAW;AAC7B,QAAI;AACF,gBAAU,KAAK,UAAU,OAAO,IAAI;AAAA,IACtC,QAAQ;AAEN,gBAAU,GAAG,KAAK,OAAO,CAAC;AAAA,IAC5B;AAAA,EACF;AACA,SAAO,GAAG,OAAO,UAAU,KAAK,IAAI,OAAO,GAAG,IAAI,OAAO;AAC3D;AA2BO,SAAS,WACd,OACA,cACkC;AAClC,QAAM,UACJ,UAAU,UAAa,OAAO,UAAU,aAAa,QAAS,gBAAgB,CAAC;AAEjF,QAAM,WAAW,CAAC,YAA4C;AAC5D,UAAM,WAAW,oBAAI,IAAkC;AACvD,UAAM,UAAU,QAAQ,WAAW;AACnC,UAAM,QAAQ,QAAQ,OAAO;AAE7B,WAAO,CAAC,WAAW;AAEjB,UAAI,CAAC,QAAQ,SAAS,OAAO,UAAU,KAAK,EAAG,QAAO,QAAQ,MAAM;AAEpE,YAAM,MAAM,MAAM,MAAM;AACxB,YAAM,UAAU,SAAS,IAAI,GAAG;AAChC,UAAI,QAAS,QAAO;AAEpB,YAAM,UAAU,QAAQ,MAAM,EAAE,QAAQ,MAAM;AAC5C,iBAAS,OAAO,GAAG;AAAA,MACrB,CAAC;AACD,eAAS,IAAI,KAAK,OAAO;AACzB,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO,OAAO,UAAU,aAAa,SAAS,KAAK,IAAI;AACzD;","names":[]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { RawResponse, RequestConfig, AdapterEnhancer, RequestAdapter } from '../index.js';
|
|
2
|
+
|
|
3
|
+
interface RetryFailure {
|
|
4
|
+
/** adapter 抛出的错误(取消为 name='AbortError') */
|
|
5
|
+
error?: unknown;
|
|
6
|
+
/** adapter 返回的非 2xx 响应 */
|
|
7
|
+
response?: RawResponse;
|
|
8
|
+
}
|
|
9
|
+
interface RetryOptions {
|
|
10
|
+
/** 最大重试次数(不含首次),默认 2 */
|
|
11
|
+
retries?: number;
|
|
12
|
+
/**
|
|
13
|
+
* 默认重试规则覆盖的方法,默认 ['GET', 'HEAD'](幂等)。
|
|
14
|
+
* 只约束默认规则;提供 shouldRetry 后完全由 shouldRetry 决定。
|
|
15
|
+
*/
|
|
16
|
+
methods?: string[];
|
|
17
|
+
/**
|
|
18
|
+
* 自定义是否重试,提供后完全接管默认规则(methods 不再生效)。
|
|
19
|
+
* 默认:抛错且方法幂等 → 重试;非 2xx 响应 / 取消 → 不重试。
|
|
20
|
+
* 需要对写方法重试时自行判断(如请求已带幂等键 Idempotency-Key)。
|
|
21
|
+
*/
|
|
22
|
+
shouldRetry?: (failure: RetryFailure, attempt: number, config: RequestConfig) => boolean;
|
|
23
|
+
/** 第 attempt 次重试前的等待毫秒,默认不等待 */
|
|
24
|
+
delayMs?: (attempt: number) => number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* adapter 装饰器:失败自动重试(不引 core,可与任何 adapter 组合)。
|
|
28
|
+
* 与错误链恢复不同:重试成功的响应会正常走完整 core 管道(响应链剥壳等)。
|
|
29
|
+
*
|
|
30
|
+
* 两种形态:
|
|
31
|
+
* - withRetry(adapter, options):直接包装
|
|
32
|
+
* - withRetry(options):返回装饰器,交给 adapter.use() 组合
|
|
33
|
+
*/
|
|
34
|
+
declare function withRetry(options?: RetryOptions): AdapterEnhancer;
|
|
35
|
+
declare function withRetry(adapter: RequestAdapter, options?: RetryOptions): RequestAdapter;
|
|
36
|
+
|
|
37
|
+
export { withRetry };
|
|
38
|
+
export type { RetryFailure, RetryOptions };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isAbortError
|
|
3
|
+
} from "../chunk-Z4N22SLG.js";
|
|
4
|
+
|
|
5
|
+
// src/enhancers/retry.ts
|
|
6
|
+
var SAFE_METHODS = ["GET", "HEAD"];
|
|
7
|
+
function withRetry(first, maybeOptions) {
|
|
8
|
+
const options = first !== void 0 && typeof first !== "function" ? first : maybeOptions ?? {};
|
|
9
|
+
const decorate = (adapter) => {
|
|
10
|
+
const { retries = 2, shouldRetry, delayMs } = options;
|
|
11
|
+
const methods = options.methods ?? SAFE_METHODS;
|
|
12
|
+
const defaultShouldRetry = (failure, _attempt, config) => failure.error !== void 0 && methods.includes(config.method ?? "GET");
|
|
13
|
+
const decideRetry = shouldRetry ?? defaultShouldRetry;
|
|
14
|
+
return async (config) => {
|
|
15
|
+
let attempt = 0;
|
|
16
|
+
for (; ; ) {
|
|
17
|
+
let failure;
|
|
18
|
+
try {
|
|
19
|
+
const res = await adapter(config);
|
|
20
|
+
if (res.status >= 200 && res.status < 300) return res;
|
|
21
|
+
failure = { response: res };
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (isAbortError(error)) throw error;
|
|
24
|
+
failure = { error };
|
|
25
|
+
}
|
|
26
|
+
const giveUp = () => {
|
|
27
|
+
if (failure.error !== void 0) throw failure.error;
|
|
28
|
+
return failure.response;
|
|
29
|
+
};
|
|
30
|
+
if (attempt >= retries) return giveUp();
|
|
31
|
+
if (!decideRetry(failure, attempt + 1, config)) {
|
|
32
|
+
return giveUp();
|
|
33
|
+
}
|
|
34
|
+
attempt++;
|
|
35
|
+
const delay = delayMs?.(attempt);
|
|
36
|
+
if (delay && delay > 0) {
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
return typeof first === "function" ? decorate(first) : decorate;
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
withRetry
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/enhancers/retry.ts"],"sourcesContent":["import { isAbortError } from '../errors'\nimport type { AdapterEnhancer, RawResponse, RequestAdapter, RequestConfig } from '../types'\n\n/**\n * 默认只重试幂等的安全方法。\n * 写方法(POST/PUT/DELETE)默认不重试:网络错误可能是\"请求已到达服务端但响应丢失\",\n * 自动重发会重复执行非幂等操作(如重复扣款)。\n */\nconst SAFE_METHODS = ['GET', 'HEAD']\n\nexport interface RetryFailure {\n /** adapter 抛出的错误(取消为 name='AbortError') */\n error?: unknown\n /** adapter 返回的非 2xx 响应 */\n response?: RawResponse\n}\n\nexport interface RetryOptions {\n /** 最大重试次数(不含首次),默认 2 */\n retries?: number\n /**\n * 默认重试规则覆盖的方法,默认 ['GET', 'HEAD'](幂等)。\n * 只约束默认规则;提供 shouldRetry 后完全由 shouldRetry 决定。\n */\n methods?: string[]\n /**\n * 自定义是否重试,提供后完全接管默认规则(methods 不再生效)。\n * 默认:抛错且方法幂等 → 重试;非 2xx 响应 / 取消 → 不重试。\n * 需要对写方法重试时自行判断(如请求已带幂等键 Idempotency-Key)。\n */\n shouldRetry?: (\n failure: RetryFailure,\n attempt: number,\n config: RequestConfig,\n ) => boolean\n /** 第 attempt 次重试前的等待毫秒,默认不等待 */\n delayMs?: (attempt: number) => number\n}\n\n/**\n * adapter 装饰器:失败自动重试(不引 core,可与任何 adapter 组合)。\n * 与错误链恢复不同:重试成功的响应会正常走完整 core 管道(响应链剥壳等)。\n *\n * 两种形态:\n * - withRetry(adapter, options):直接包装\n * - withRetry(options):返回装饰器,交给 adapter.use() 组合\n */\nexport function withRetry(options?: RetryOptions): AdapterEnhancer\nexport function withRetry(adapter: RequestAdapter, options?: RetryOptions): RequestAdapter\nexport function withRetry(\n first?: RequestAdapter | RetryOptions,\n maybeOptions?: RetryOptions,\n): RequestAdapter | AdapterEnhancer {\n const options: RetryOptions =\n first !== undefined && typeof first !== 'function' ? first : (maybeOptions ?? {})\n\n const decorate = (adapter: RequestAdapter): RequestAdapter => {\n const { retries = 2, shouldRetry, delayMs } = options\n const methods = options.methods ?? SAFE_METHODS\n const defaultShouldRetry = (failure: RetryFailure, _attempt: number, config: RequestConfig) =>\n failure.error !== undefined && methods.includes(config.method ?? 'GET')\n const decideRetry: (\n failure: RetryFailure,\n attempt: number,\n config: RequestConfig,\n ) => boolean = shouldRetry ?? defaultShouldRetry\n\n return async (config) => {\n let attempt = 0\n for (;;) {\n let failure: RetryFailure\n try {\n const res = await adapter(config)\n if (res.status >= 200 && res.status < 300) return res\n failure = { response: res }\n } catch (error) {\n // 取消语义永不重试\n if (isAbortError(error)) throw error\n failure = { error }\n }\n\n const giveUp = () => {\n if (failure.error !== undefined) throw failure.error\n return failure.response as RawResponse\n }\n if (attempt >= retries) return giveUp()\n if (!decideRetry(failure, attempt + 1, config)) {\n return giveUp()\n }\n\n attempt++\n const delay = delayMs?.(attempt)\n if (delay && delay > 0) {\n await new Promise((resolve) => setTimeout(resolve, delay))\n }\n }\n }\n }\n\n return typeof first === 'function' ? decorate(first) : decorate\n}\n"],"mappings":";;;;;AAQA,IAAM,eAAe,CAAC,OAAO,MAAM;AAyC5B,SAAS,UACd,OACA,cACkC;AAClC,QAAM,UACJ,UAAU,UAAa,OAAO,UAAU,aAAa,QAAS,gBAAgB,CAAC;AAEjF,QAAM,WAAW,CAAC,YAA4C;AAC5D,UAAM,EAAE,UAAU,GAAG,aAAa,QAAQ,IAAI;AAC9C,UAAM,UAAU,QAAQ,WAAW;AACnC,UAAM,qBAAqB,CAAC,SAAuB,UAAkB,WACnE,QAAQ,UAAU,UAAa,QAAQ,SAAS,OAAO,UAAU,KAAK;AACxE,UAAM,cAIS,eAAe;AAE9B,WAAO,OAAO,WAAW;AACvB,UAAI,UAAU;AACd,iBAAS;AACP,YAAI;AACJ,YAAI;AACF,gBAAM,MAAM,MAAM,QAAQ,MAAM;AAChC,cAAI,IAAI,UAAU,OAAO,IAAI,SAAS,IAAK,QAAO;AAClD,oBAAU,EAAE,UAAU,IAAI;AAAA,QAC5B,SAAS,OAAO;AAEd,cAAI,aAAa,KAAK,EAAG,OAAM;AAC/B,oBAAU,EAAE,MAAM;AAAA,QACpB;AAEA,cAAM,SAAS,MAAM;AACnB,cAAI,QAAQ,UAAU,OAAW,OAAM,QAAQ;AAC/C,iBAAO,QAAQ;AAAA,QACjB;AACA,YAAI,WAAW,QAAS,QAAO,OAAO;AACtC,YAAI,CAAC,YAAY,SAAS,UAAU,GAAG,MAAM,GAAG;AAC9C,iBAAO,OAAO;AAAA,QAChB;AAEA;AACA,cAAM,QAAQ,UAAU,OAAO;AAC/B,YAAI,SAAS,QAAQ,GAAG;AACtB,gBAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,KAAK,CAAC;AAAA,QAC3D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,OAAO,UAAU,aAAa,SAAS,KAAK,IAAI;AACzD;","names":[]}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { f as RequestError, g as RequestErrorKind, h as RequestErrorOptions, l as createAbortError, m as isAbortError } from './types-CoJSp-ar.js';
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { A as AdapterEnhancer, e as AdapterOptions, C as ChainableAdapter, E as ErrorInterceptor, b as RawResponse, a as RequestAdapter, R as RequestConfig, f as RequestError, g as RequestErrorKind, h as RequestErrorOptions, i as RequestEvents, c as RequestInterceptor, j as RequestMethod, k as RequestProgress, d as ResponseInterceptor, l as createAbortError, m as isAbortError } from './types-CoJSp-ar.js';
|
|
2
|
+
export { CreateRequestOptions, RequestInstance, buildUrl, createRequest, defineAdapter } from './core.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildUrl,
|
|
3
|
+
createRequest,
|
|
4
|
+
defineAdapter
|
|
5
|
+
} from "./chunk-DQI4SM2Q.js";
|
|
6
|
+
import {
|
|
7
|
+
RequestError,
|
|
8
|
+
createAbortError,
|
|
9
|
+
isAbortError
|
|
10
|
+
} from "./chunk-Z4N22SLG.js";
|
|
11
|
+
export {
|
|
12
|
+
RequestError,
|
|
13
|
+
buildUrl,
|
|
14
|
+
createAbortError,
|
|
15
|
+
createRequest,
|
|
16
|
+
defineAdapter,
|
|
17
|
+
isAbortError
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { RequestConfig, RequestInterceptor } from '../index.js';
|
|
2
|
+
|
|
3
|
+
interface AuthInterceptorOptions {
|
|
4
|
+
/** 取 token;支持异步(如先静默刷新)。返回空值则跳过注入 */
|
|
5
|
+
getToken: () => string | undefined | null | Promise<string | undefined | null>;
|
|
6
|
+
/** 注入的头名,默认 'Authorization' */
|
|
7
|
+
headerName?: string;
|
|
8
|
+
/** token 前缀,默认 'Bearer ';不需要前缀传空字符串 */
|
|
9
|
+
scheme?: string;
|
|
10
|
+
/** 返回 false 的请求不注入(如登录接口本身) */
|
|
11
|
+
shouldAttach?: (config: RequestConfig) => boolean;
|
|
12
|
+
}
|
|
13
|
+
/** 请求链拦截器:从 getToken() 注入凭证头 */
|
|
14
|
+
declare function createAuthInterceptor(options: AuthInterceptorOptions): RequestInterceptor;
|
|
15
|
+
|
|
16
|
+
export { createAuthInterceptor };
|
|
17
|
+
export type { AuthInterceptorOptions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { AuthInterceptorOptions, createAuthInterceptor } from './auth.js';
|
|
2
|
+
export { UnwrapInterceptorOptions, createUnwrapInterceptor } from './unwrap.js';
|
|
3
|
+
export { LogFn, LogLevel, LoggerInterceptorsOptions, createLoggerInterceptors } from './logger.js';
|
|
4
|
+
export { RequestIdInterceptorOptions, createRequestIdInterceptor } from './request-id.js';
|
|
5
|
+
import '../types-CoJSp-ar.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAuthInterceptor
|
|
3
|
+
} from "../chunk-FY6YU3PV.js";
|
|
4
|
+
import {
|
|
5
|
+
createLoggerInterceptors
|
|
6
|
+
} from "../chunk-KLAI63DX.js";
|
|
7
|
+
import {
|
|
8
|
+
createRequestIdInterceptor
|
|
9
|
+
} from "../chunk-UCTOFBDY.js";
|
|
10
|
+
import {
|
|
11
|
+
createUnwrapInterceptor
|
|
12
|
+
} from "../chunk-OV6LXQTN.js";
|
|
13
|
+
import "../chunk-Z4N22SLG.js";
|
|
14
|
+
export {
|
|
15
|
+
createAuthInterceptor,
|
|
16
|
+
createLoggerInterceptors,
|
|
17
|
+
createRequestIdInterceptor,
|
|
18
|
+
createUnwrapInterceptor
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { RequestInterceptor, ResponseInterceptor, ErrorInterceptor } from '../index.js';
|
|
2
|
+
|
|
3
|
+
type LogLevel = 'debug' | 'warn';
|
|
4
|
+
type LogFn = (level: LogLevel, ...args: unknown[]) => void;
|
|
5
|
+
interface LoggerInterceptorsOptions {
|
|
6
|
+
/**
|
|
7
|
+
* 自定义日志函数(如接埋点 SDK)。注意:不应抛错——
|
|
8
|
+
* request/response 链中抛错会进入错误链,error 链中抛错会替换原错误。
|
|
9
|
+
*/
|
|
10
|
+
log?: LogFn;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 三链日志:返回 request / response / error 三件套,分别注册到对应链。
|
|
14
|
+
* - request:method / url / params
|
|
15
|
+
* - response:status / 耗时 / url(读 core 注入的 res.durationMs 与 res.config)
|
|
16
|
+
* - error:kind / message / url(读 err.config)
|
|
17
|
+
*/
|
|
18
|
+
declare function createLoggerInterceptors(options?: LoggerInterceptorsOptions): {
|
|
19
|
+
request: RequestInterceptor;
|
|
20
|
+
response: ResponseInterceptor;
|
|
21
|
+
error: ErrorInterceptor;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export { createLoggerInterceptors };
|
|
25
|
+
export type { LogFn, LogLevel, LoggerInterceptorsOptions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { RequestInterceptor } from '../index.js';
|
|
2
|
+
|
|
3
|
+
interface RequestIdInterceptorOptions {
|
|
4
|
+
/** 请求头名,默认 'X-Request-Id' */
|
|
5
|
+
headerName?: string;
|
|
6
|
+
/** 自定义 ID 生成器(如接入 tracing/Sentry 等链路库) */
|
|
7
|
+
generateId?: () => string;
|
|
8
|
+
/**
|
|
9
|
+
* 请求已携带该头时不覆盖(用于上游链路 ID 透传),默认 false。
|
|
10
|
+
* 注意:实例级默认头也算"已携带",开启后会导致所有请求共用一个 ID,慎用。
|
|
11
|
+
*/
|
|
12
|
+
keepExisting?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 请求链拦截器:为每个请求生成唯一 ID 注入请求头,用于前后端日志关联与报障定位。
|
|
16
|
+
* - core 会把最终 config 盖章到 RawResponse.config 与 RequestError.config,
|
|
17
|
+
* 因此响应链日志、错误上报都能取到同一个 ID
|
|
18
|
+
* - 重试共享同一 config:同一次逻辑请求(含重试)带同一个 ID,服务端可识别重发
|
|
19
|
+
*/
|
|
20
|
+
declare function createRequestIdInterceptor(options?: RequestIdInterceptorOptions): RequestInterceptor;
|
|
21
|
+
|
|
22
|
+
export { createRequestIdInterceptor };
|
|
23
|
+
export type { RequestIdInterceptorOptions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ResponseInterceptor } from '../index.js';
|
|
2
|
+
|
|
3
|
+
interface UnwrapInterceptorOptions {
|
|
4
|
+
/** 业务成功码(后端常见字符串码,允许 string | number),默认 [0] */
|
|
5
|
+
successCodes?: Array<string | number>;
|
|
6
|
+
/** 业务码字段名,默认 'code' */
|
|
7
|
+
codeKey?: string;
|
|
8
|
+
/** 业务数据字段名,默认 'data' */
|
|
9
|
+
dataKey?: string;
|
|
10
|
+
/** 业务消息字段名,默认 'message' */
|
|
11
|
+
messageKey?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 响应链拦截器:剥离 {code, data, message} 业务壳。
|
|
15
|
+
* - body 非对象或缺 codeKey:视为非业务壳响应,原样放行(防御非 JSON 响应)
|
|
16
|
+
* - 业务成功:res.data 替换为业务 data
|
|
17
|
+
* - 业务失败:抛 RequestError(kind='business'),进入错误链
|
|
18
|
+
*/
|
|
19
|
+
declare function createUnwrapInterceptor(options?: UnwrapInterceptorOptions): ResponseInterceptor;
|
|
20
|
+
|
|
21
|
+
export { createUnwrapInterceptor };
|
|
22
|
+
export type { UnwrapInterceptorOptions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一错误四分类:
|
|
3
|
+
* - http:收到响应但状态码非 2xx(业务层面最常处理的一类)
|
|
4
|
+
* - business:HTTP 2xx 但业务码失败(由内置 unwrap 拦截器产生)
|
|
5
|
+
* - abort:请求被取消(AbortSignal 或平台取消语义)
|
|
6
|
+
* - network:传输层失败,含超时;通过 raw 可拿到原始错误(axios 的 error.code / uni 的 errMsg)
|
|
7
|
+
*/
|
|
8
|
+
type RequestErrorKind = 'abort' | 'http' | 'business' | 'network';
|
|
9
|
+
interface RequestErrorOptions {
|
|
10
|
+
status?: number;
|
|
11
|
+
data?: unknown;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
config?: RequestConfig;
|
|
14
|
+
/** 原始错误:axios error / uni fail 回调的 err,便于排查 */
|
|
15
|
+
raw?: unknown;
|
|
16
|
+
}
|
|
17
|
+
declare class RequestError extends Error {
|
|
18
|
+
readonly kind: RequestErrorKind;
|
|
19
|
+
readonly status?: number;
|
|
20
|
+
readonly data?: unknown;
|
|
21
|
+
readonly headers?: Record<string, string>;
|
|
22
|
+
readonly config?: RequestConfig;
|
|
23
|
+
readonly raw?: unknown;
|
|
24
|
+
constructor(kind: RequestErrorKind, message: string, options?: RequestErrorOptions);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 生成统一形态的 AbortError。
|
|
28
|
+
* 不用 DOMException:小程序运行时没有 DOMException,
|
|
29
|
+
* Error + name='AbortError' 与 `e.name === 'AbortError'` 的通用判定方式兼容。
|
|
30
|
+
*/
|
|
31
|
+
declare function createAbortError(message?: string): Error;
|
|
32
|
+
/** 是否为取消错误(兼容原生 DOMException/AbortController 与本库生成的) */
|
|
33
|
+
declare function isAbortError(error: unknown): boolean;
|
|
34
|
+
|
|
35
|
+
/** 统一的请求方法,axios 与 uni.request 均可表达 */
|
|
36
|
+
type RequestMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
|
37
|
+
/**
|
|
38
|
+
* 请求事件集合:adapter 在支持的平台能力上触发。
|
|
39
|
+
* 扩展策略:新增事件只加这里的可选成员(并在对应 adapter 中 emit),
|
|
40
|
+
* RequestConfig 结构保持封闭——不再往根上加回调字段。
|
|
41
|
+
*/
|
|
42
|
+
interface RequestEvents {
|
|
43
|
+
/** 上传进度(字节数);axios/uniapp 支持,fetch 平台不支持(README 有能力矩阵) */
|
|
44
|
+
uploadProgress?: (progress: RequestProgress) => void;
|
|
45
|
+
/** 下载进度(字节数);fetch 走流式读取,Content-Length 缺失时 total/progress 为 undefined */
|
|
46
|
+
downloadProgress?: (progress: RequestProgress) => void;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* adapter 专有配置透传袋:按 adapter 名取用,仅对应 adapter 消费,其它 adapter 忽略。
|
|
50
|
+
* 这是平台特有能力的统一逃生舱(如 axios 的 adapter/responseType、fetch 的 credentials/cache、
|
|
51
|
+
* uni.request 的 enableHttp2)——RequestConfig 保持平台无关,特有参数走这里,不往根上加字段。
|
|
52
|
+
*
|
|
53
|
+
* 冲突规则:契约派生字段(url/method/headers/timeout/signal/validateStatus 等)
|
|
54
|
+
* 以 RequestConfig 为准,透传项覆盖不了它们——统一语义不被破坏。
|
|
55
|
+
*/
|
|
56
|
+
interface AdapterOptions {
|
|
57
|
+
/** axiosAdapter 透传给 axios.request 的额外配置(axios 专有类型见 axios 官方文档) */
|
|
58
|
+
axios?: Record<string, unknown>;
|
|
59
|
+
/** uniappAdapter 透传给 uni.request 的额外配置 */
|
|
60
|
+
uniapp?: Record<string, unknown>;
|
|
61
|
+
/** fetchAdapter 透传给 fetch() 的额外配置(credentials / cache / redirect 等) */
|
|
62
|
+
fetch?: Record<string, unknown>;
|
|
63
|
+
/** 自定义 adapter 可按自己的名字扩展(结构由该 adapter 决定) */
|
|
64
|
+
[adapter: string]: Record<string, unknown> | undefined;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 平台无关的请求配置,adapter 的唯一输入。
|
|
68
|
+
* 注意:adapter 拿到的 url 已经是 core 拼好 params 的完整地址。
|
|
69
|
+
*/
|
|
70
|
+
interface RequestConfig {
|
|
71
|
+
url: string;
|
|
72
|
+
method?: RequestMethod;
|
|
73
|
+
baseURL?: string;
|
|
74
|
+
/** query 参数,由 core 统一序列化后拼进 url,adapter 无需感知 */
|
|
75
|
+
params?: Record<string, unknown>;
|
|
76
|
+
data?: unknown;
|
|
77
|
+
headers?: Record<string, string>;
|
|
78
|
+
timeout?: number;
|
|
79
|
+
signal?: AbortSignal;
|
|
80
|
+
/** 事件订阅(唯一的扩展点:新增事件只扩展 RequestEvents,不改 RequestConfig) */
|
|
81
|
+
on?: RequestEvents;
|
|
82
|
+
/** adapter 专有配置透传(见 AdapterOptions) */
|
|
83
|
+
adapterOptions?: AdapterOptions;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 平台无关的原始响应,adapter 的唯一输出。不做任何业务判断(含状态码)。
|
|
87
|
+
* durationMs 与 config 由 core 统一注入(adapter 无需填写)。
|
|
88
|
+
*/
|
|
89
|
+
interface RawResponse<T = unknown> {
|
|
90
|
+
status: number;
|
|
91
|
+
data: T;
|
|
92
|
+
headers: Record<string, string>;
|
|
93
|
+
/** adapter 调用耗时(毫秒),core 注入;供日志/性能监控使用 */
|
|
94
|
+
durationMs?: number;
|
|
95
|
+
/** 发起请求的最终配置(url 已拼好 params),core 注入;供响应链做条件逻辑 */
|
|
96
|
+
config?: RequestConfig;
|
|
97
|
+
}
|
|
98
|
+
/** 进度事件:loaded/total 为字节数,progress 为 0-1(total 未知时为 undefined) */
|
|
99
|
+
interface RequestProgress {
|
|
100
|
+
loaded: number;
|
|
101
|
+
/** 总字节数;服务端未返回长度(chunked 等)时为 undefined */
|
|
102
|
+
total?: number;
|
|
103
|
+
/** 0-1;total 未知时为 undefined */
|
|
104
|
+
progress?: number;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* adapter 契约:发请求、返回原始结果、翻译取消语义。
|
|
108
|
+
* 状态码判断、错误归一化、拦截器全部由 core 负责,adapter 保持"哑"。
|
|
109
|
+
*/
|
|
110
|
+
type RequestAdapter = (config: RequestConfig) => Promise<RawResponse>;
|
|
111
|
+
/** adapter 装饰器:输入 adapter,输出增强后的 adapter(retry/dedupe 以该形态参与 .use 组合) */
|
|
112
|
+
type AdapterEnhancer = (adapter: RequestAdapter) => RequestAdapter;
|
|
113
|
+
/**
|
|
114
|
+
* 可链式增强的 adapter:本身可直接当 RequestAdapter 用,同时提供 .use 叠加装饰器。
|
|
115
|
+
* 注册顺序 = 请求流经顺序(与拦截器链一致):adapter.use(a).use(b) 时请求依次经过 a → b → adapter。
|
|
116
|
+
*/
|
|
117
|
+
interface ChainableAdapter extends RequestAdapter {
|
|
118
|
+
/** 在外层叠加一个装饰器,返回新的链式 adapter(原 adapter 不被修改) */
|
|
119
|
+
use: (enhancer: AdapterEnhancer) => ChainableAdapter;
|
|
120
|
+
}
|
|
121
|
+
/** 请求拦截器:返回新 config(可异步) */
|
|
122
|
+
type RequestInterceptor = (config: RequestConfig) => RequestConfig | Promise<RequestConfig>;
|
|
123
|
+
/** 响应拦截器(成功链):转换 / 剥壳 */
|
|
124
|
+
type ResponseInterceptor = (res: RawResponse) => RawResponse | Promise<RawResponse>;
|
|
125
|
+
/**
|
|
126
|
+
* 错误拦截器:所有失败都会按注册顺序经过——
|
|
127
|
+
* 请求拦截器抛错 / 网络失败(含超时)/ 非 2xx / 响应成功链抛错。
|
|
128
|
+
*
|
|
129
|
+
* - 返回 RawResponse → 恢复:结束错误链,直接作为最终结果(后续错误拦截器不再执行)
|
|
130
|
+
* - 返回 RequestError → 用它替换当前错误,继续错误链
|
|
131
|
+
* - 无返回值(undefined)→ 放行:适合纯观察(上报 / toast / 401 跳转)
|
|
132
|
+
* - 抛错 → 视为替换错误,继续错误链
|
|
133
|
+
*/
|
|
134
|
+
type ErrorInterceptor = (error: RequestError) => RawResponse | RequestError | void | Promise<RawResponse | RequestError | void>;
|
|
135
|
+
|
|
136
|
+
export { RequestError as f, createAbortError as l, isAbortError as m };
|
|
137
|
+
export type { AdapterEnhancer as A, ChainableAdapter as C, ErrorInterceptor as E, RequestConfig as R, RequestAdapter as a, RawResponse as b, RequestInterceptor as c, ResponseInterceptor as d, AdapterOptions as e, RequestErrorKind as g, RequestErrorOptions as h, RequestEvents as i, RequestMethod as j, RequestProgress as k };
|