@huangqi_an/request 0.0.0-stage → 0.2.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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +580 -2
  3. package/dist/adapters/axios.d.ts +11 -0
  4. package/dist/adapters/axios.js +56 -0
  5. package/dist/adapters/axios.js.map +1 -0
  6. package/dist/adapters/fetch.d.ts +12 -0
  7. package/dist/adapters/fetch.js +109 -0
  8. package/dist/adapters/fetch.js.map +1 -0
  9. package/dist/adapters/uniapp.d.ts +13 -0
  10. package/dist/adapters/uniapp.js +92 -0
  11. package/dist/adapters/uniapp.js.map +1 -0
  12. package/dist/chunk-FY6YU3PV.js +23 -0
  13. package/dist/chunk-FY6YU3PV.js.map +1 -0
  14. package/dist/chunk-GMJTG6KM.js +156 -0
  15. package/dist/chunk-GMJTG6KM.js.map +1 -0
  16. package/dist/chunk-KLAI63DX.js +26 -0
  17. package/dist/chunk-KLAI63DX.js.map +1 -0
  18. package/dist/chunk-OV6LXQTN.js +37 -0
  19. package/dist/chunk-OV6LXQTN.js.map +1 -0
  20. package/dist/chunk-UCTOFBDY.js +27 -0
  21. package/dist/chunk-UCTOFBDY.js.map +1 -0
  22. package/dist/chunk-Z4N22SLG.js +28 -0
  23. package/dist/chunk-Z4N22SLG.js.map +1 -0
  24. package/dist/core.d.ts +61 -0
  25. package/dist/core.js +12 -0
  26. package/dist/core.js.map +1 -0
  27. package/dist/enhancers/dedupe.d.ts +29 -0
  28. package/dist/enhancers/dedupe.js +37 -0
  29. package/dist/enhancers/dedupe.js.map +1 -0
  30. package/dist/enhancers/retry.d.ts +38 -0
  31. package/dist/enhancers/retry.js +47 -0
  32. package/dist/enhancers/retry.js.map +1 -0
  33. package/dist/errors.d.ts +1 -0
  34. package/dist/errors.js +11 -0
  35. package/dist/errors.js.map +1 -0
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.js +19 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/interceptors/auth.d.ts +17 -0
  40. package/dist/interceptors/auth.js +7 -0
  41. package/dist/interceptors/auth.js.map +1 -0
  42. package/dist/interceptors/index.d.ts +5 -0
  43. package/dist/interceptors/index.js +20 -0
  44. package/dist/interceptors/index.js.map +1 -0
  45. package/dist/interceptors/logger.d.ts +25 -0
  46. package/dist/interceptors/logger.js +7 -0
  47. package/dist/interceptors/logger.js.map +1 -0
  48. package/dist/interceptors/request-id.d.ts +23 -0
  49. package/dist/interceptors/request-id.js +7 -0
  50. package/dist/interceptors/request-id.js.map +1 -0
  51. package/dist/interceptors/unwrap.d.ts +22 -0
  52. package/dist/interceptors/unwrap.js +8 -0
  53. package/dist/interceptors/unwrap.js.map +1 -0
  54. package/dist/types-DSmcNU2O.d.ts +146 -0
  55. package/package.json +109 -4
@@ -0,0 +1,12 @@
1
+ import { ChainableAdapter } from '../index.js';
2
+
3
+ /**
4
+ * Web/Node/SSR adapter 插件:基于全局 fetch(零依赖,Node 18+ / 现代浏览器)。
5
+ * 经 defineAdapter 导出:可直接使用,也可 .use() 叠加装饰器。
6
+ * - 任何状态码都 resolve,判断统一交给 core
7
+ * - fetch 无内建 timeout,用 AbortController 桥接 signal 与超时;
8
+ * 超时抛普通错误(core 归类 kind='network'),与 axios/uni 的超时归类一致
9
+ */
10
+ declare const fetchAdapter: ChainableAdapter;
11
+
12
+ export { fetchAdapter };
@@ -0,0 +1,109 @@
1
+ import {
2
+ defineAdapter
3
+ } from "../chunk-GMJTG6KM.js";
4
+ import {
5
+ createAbortError
6
+ } from "../chunk-Z4N22SLG.js";
7
+
8
+ // src/adapters/fetch.ts
9
+ function isPassthroughBody(data) {
10
+ return typeof data === "string" || data instanceof Blob || data instanceof ArrayBuffer || data instanceof Uint8Array || data instanceof URLSearchParams || typeof FormData !== "undefined" && data instanceof FormData;
11
+ }
12
+ function prepareBody(config) {
13
+ const method = config.method ?? "GET";
14
+ const headers = { ...config.headers };
15
+ const data = config.data;
16
+ if (data === void 0 || data === null || method === "GET" || method === "HEAD") {
17
+ return { headers };
18
+ }
19
+ if (isPassthroughBody(data)) {
20
+ return { body: data, headers };
21
+ }
22
+ if (!Object.keys(headers).some((key) => key.toLowerCase() === "content-type")) {
23
+ headers["Content-Type"] = "application/json";
24
+ }
25
+ return { body: JSON.stringify(data), headers };
26
+ }
27
+ function parseData(text, contentType) {
28
+ if (contentType.includes("json") && text !== "") {
29
+ try {
30
+ return JSON.parse(text);
31
+ } catch {
32
+ }
33
+ }
34
+ return text;
35
+ }
36
+ function concatChunks(chunks, totalLength) {
37
+ const merged = new Uint8Array(totalLength);
38
+ let offset = 0;
39
+ for (const chunk of chunks) {
40
+ merged.set(chunk, offset);
41
+ offset += chunk.byteLength;
42
+ }
43
+ return merged;
44
+ }
45
+ async function readWithProgress(res, onProgress) {
46
+ const lengthHeader = Number(res.headers.get("content-length"));
47
+ const total = Number.isFinite(lengthHeader) && lengthHeader > 0 ? lengthHeader : void 0;
48
+ const reader = res.body.getReader();
49
+ const chunks = [];
50
+ let loaded = 0;
51
+ for (; ; ) {
52
+ const { done, value } = await reader.read();
53
+ if (done) break;
54
+ chunks.push(value);
55
+ loaded += value.byteLength;
56
+ onProgress({
57
+ loaded,
58
+ total,
59
+ progress: total !== void 0 ? Math.min(loaded / total, 1) : void 0
60
+ });
61
+ }
62
+ return new TextDecoder().decode(concatChunks(chunks, loaded));
63
+ }
64
+ var fetchAdapter = defineAdapter(
65
+ async (config) => {
66
+ const { signal, timeout } = config;
67
+ if (signal?.aborted) throw createAbortError();
68
+ const controller = new AbortController();
69
+ let timedOut = false;
70
+ let timer;
71
+ const onExternalAbort = () => controller.abort();
72
+ signal?.addEventListener("abort", onExternalAbort, { once: true });
73
+ if (timeout !== void 0 && timeout > 0) {
74
+ timer = setTimeout(() => {
75
+ timedOut = true;
76
+ controller.abort();
77
+ }, timeout);
78
+ }
79
+ try {
80
+ const { body, headers } = prepareBody(config);
81
+ const { on } = config;
82
+ const res = await fetch(config.url, {
83
+ // 专有配置先展开(如 credentials / cache / redirect):契约派生字段在后覆盖
84
+ ...config.adapterOptions?.fetch,
85
+ method: config.method ?? "GET",
86
+ headers,
87
+ body,
88
+ signal: controller.signal
89
+ });
90
+ const text = on?.downloadProgress && res.body ? await readWithProgress(res, on.downloadProgress) : await res.text();
91
+ return {
92
+ status: res.status,
93
+ data: parseData(text, res.headers.get("content-type") ?? ""),
94
+ headers: Object.fromEntries(res.headers.entries())
95
+ };
96
+ } catch (error) {
97
+ if (signal?.aborted) throw createAbortError(error?.message);
98
+ if (timedOut) throw new Error(`Request timed out after ${timeout}ms`);
99
+ throw error;
100
+ } finally {
101
+ signal?.removeEventListener("abort", onExternalAbort);
102
+ if (timer !== void 0) clearTimeout(timer);
103
+ }
104
+ }
105
+ );
106
+ export {
107
+ fetchAdapter
108
+ };
109
+ //# sourceMappingURL=fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/fetch.ts"],"sourcesContent":["import { defineAdapter } from '../core'\nimport { createAbortError } from '../errors'\nimport type { RawResponse, RequestConfig, RequestProgress } from '../types'\n\n/** 判断 data 是否可不经序列化直接作为 fetch 的 body(字符串或内置二进制类型) */\nfunction isPassthroughBody(data: unknown): boolean {\n return (\n typeof data === 'string' ||\n data instanceof Blob ||\n data instanceof ArrayBuffer ||\n data instanceof Uint8Array ||\n data instanceof URLSearchParams ||\n (typeof FormData !== 'undefined' && data instanceof FormData)\n )\n}\n\n/**\n * 组装 body:\n * - GET/HEAD 不应有 body(fetch 对此直接抛错),data 被忽略,query 请用 params\n * - 字符串/内置二进制类型原样发送,不改动 Content-Type\n * - 对象/数组自动 JSON 序列化,未显式指定 Content-Type 时补 application/json(与 axios/uni 行为一致)\n */\nfunction prepareBody(config: RequestConfig): {\n body?: BodyInit\n headers: Record<string, string>\n} {\n const method = config.method ?? 'GET'\n const headers = { ...config.headers }\n const data = config.data\n\n if (data === undefined || data === null || method === 'GET' || method === 'HEAD') {\n return { headers }\n }\n if (isPassthroughBody(data)) {\n return { body: data as BodyInit, headers }\n }\n if (!Object.keys(headers).some((key) => key.toLowerCase() === 'content-type')) {\n headers['Content-Type'] = 'application/json'\n }\n return { body: JSON.stringify(data), headers }\n}\n\n/** 响应解析:content-type 含 json 时自动 parse,解析失败保留原文(交由业务处理) */\nfunction parseData(text: string, contentType: string): unknown {\n if (contentType.includes('json') && text !== '') {\n try {\n return JSON.parse(text)\n } catch {\n // 保留原文\n }\n }\n return text\n}\n\n/** 拼接分块字节后再整体解码:避免多字节 UTF-8 字符被块边界截断 */\nfunction concatChunks(chunks: Uint8Array[], totalLength: number): Uint8Array {\n const merged = new Uint8Array(totalLength)\n let offset = 0\n for (const chunk of chunks) {\n merged.set(chunk, offset)\n offset += chunk.byteLength\n }\n return merged\n}\n\n/**\n * 下载进度:走 res.body 流式读取并逐块上报。\n * Content-Length 缺失(chunked 等)时 total/progress 为 undefined。\n */\nasync function readWithProgress(\n res: Response,\n onProgress: (progress: RequestProgress) => void,\n): Promise<string> {\n const lengthHeader = Number(res.headers.get('content-length'))\n const total = Number.isFinite(lengthHeader) && lengthHeader > 0 ? lengthHeader : undefined\n const reader = res.body!.getReader()\n const chunks: Uint8Array[] = []\n let loaded = 0\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n chunks.push(value)\n loaded += value.byteLength\n onProgress({\n loaded,\n total,\n progress: total !== undefined ? Math.min(loaded / total, 1) : undefined,\n })\n }\n return new TextDecoder().decode(concatChunks(chunks, loaded))\n}\n\n/**\n * Web/Node/SSR adapter 插件:基于全局 fetch(零依赖,Node 18+ / 现代浏览器)。\n * 经 defineAdapter 导出:可直接使用,也可 .use() 叠加装饰器。\n * - 任何状态码都 resolve,判断统一交给 core\n * - fetch 无内建 timeout,用 AbortController 桥接 signal 与超时;\n * 超时抛普通错误(core 归类 kind='network'),与 axios/uni 的超时归类一致\n */\nexport const fetchAdapter = defineAdapter(\n async (config: RequestConfig): Promise<RawResponse> => {\n const { signal, timeout } = config\n // 与其他 adapter 一致:发出前已取消直接终止\n if (signal?.aborted) throw createAbortError()\n\n const controller = new AbortController()\n let timedOut = false\n let timer: ReturnType<typeof setTimeout> | undefined\n const onExternalAbort = () => controller.abort()\n signal?.addEventListener('abort', onExternalAbort, { once: true })\n if (timeout !== undefined && timeout > 0) {\n timer = setTimeout(() => {\n timedOut = true\n controller.abort()\n }, timeout)\n }\n\n try {\n const { body, headers } = prepareBody(config)\n const { on } = config\n const res = await fetch(config.url, {\n // 专有配置先展开(如 credentials / cache / redirect):契约派生字段在后覆盖\n ...(config.adapterOptions?.fetch as RequestInit | undefined),\n method: config.method ?? 'GET',\n headers,\n body,\n signal: controller.signal,\n })\n // fetch 无上传进度(平台限制);下载进度走流式读取\n const text =\n on?.downloadProgress && res.body\n ? await readWithProgress(res, on.downloadProgress)\n : await res.text()\n return {\n status: res.status,\n data: parseData(text, res.headers.get('content-type') ?? ''),\n headers: Object.fromEntries(res.headers.entries()),\n }\n } catch (error) {\n // 用户取消 → 统一 AbortError;桥接超时 → 普通错误(交 core 归类 network)\n if (signal?.aborted) throw createAbortError((error as Error)?.message)\n if (timedOut) throw new Error(`Request timed out after ${timeout}ms`)\n throw error\n } finally {\n signal?.removeEventListener('abort', onExternalAbort)\n if (timer !== undefined) clearTimeout(timer)\n }\n },\n)\n"],"mappings":"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","names":[]}
@@ -0,0 +1,13 @@
1
+ import { ChainableAdapter } from '../index.js';
2
+
3
+ /**
4
+ * uni-app adapter 插件:基于 uni.request(uni-app 全局 API),按需导入,主入口不含本模块。
5
+ * 经 defineAdapter 导出:可直接使用,也可 .use() 叠加装饰器。
6
+ * 注意:uni-app ≠ 原生微信小程序——原生小程序的全局 API 是 wx.request,需要单独的 adapter。
7
+ * - uni.request 的非 2xx 响应也走 success 且不 reject,所以这里原样 resolve,状态码判断统一交给 core
8
+ * - fail 中的 abort(errMsg 形如 "request:fail abort")翻译为统一 AbortError
9
+ * - AbortSignal 桥接到 task.abort(),实现与其他 adapter 一致的统一取消
10
+ */
11
+ declare const uniappAdapter: ChainableAdapter;
12
+
13
+ export { uniappAdapter };
@@ -0,0 +1,92 @@
1
+ import {
2
+ defineAdapter
3
+ } from "../chunk-GMJTG6KM.js";
4
+ import {
5
+ createAbortError
6
+ } from "../chunk-Z4N22SLG.js";
7
+
8
+ // src/adapters/uniapp.ts
9
+ function toProgress(loaded, total) {
10
+ const t = total && total > 0 ? total : void 0;
11
+ return {
12
+ loaded,
13
+ total: t,
14
+ progress: t !== void 0 ? Math.min(loaded / t, 1) : void 0
15
+ };
16
+ }
17
+ var uniappAdapter = defineAdapter((config) => {
18
+ return new Promise((resolve, reject) => {
19
+ const signal = config.signal;
20
+ if (signal?.aborted) {
21
+ reject(createAbortError());
22
+ return;
23
+ }
24
+ let settled = false;
25
+ let task;
26
+ const cleanup = () => signal?.removeEventListener("abort", onAbort);
27
+ const onAbort = () => {
28
+ if (settled) return;
29
+ settled = true;
30
+ cleanup();
31
+ task?.abort();
32
+ reject(createAbortError());
33
+ };
34
+ const settleSuccess = (res) => {
35
+ if (settled) return;
36
+ settled = true;
37
+ cleanup();
38
+ resolve(res);
39
+ };
40
+ const settleFail = (error) => {
41
+ if (settled) return;
42
+ settled = true;
43
+ cleanup();
44
+ reject(error);
45
+ };
46
+ signal?.addEventListener("abort", onAbort, { once: true });
47
+ try {
48
+ task = uni.request({
49
+ // 专有配置先展开(如 enableHttp2):契约派生字段在后覆盖
50
+ ...config.adapterOptions?.uniapp,
51
+ url: config.url,
52
+ method: config.method,
53
+ data: config.data,
54
+ header: config.headers,
55
+ timeout: config.timeout,
56
+ success: (res) => {
57
+ settleSuccess({
58
+ status: res.statusCode,
59
+ data: res.data,
60
+ headers: res.header ?? {}
61
+ });
62
+ },
63
+ fail: (err) => {
64
+ if (/abort/i.test(err?.errMsg ?? "")) {
65
+ onAbort();
66
+ return;
67
+ }
68
+ settleFail(err ?? new Error("request:fail"));
69
+ }
70
+ });
71
+ if (task?.onProgressUpdate) {
72
+ const { uploadProgress, downloadProgress } = config.on ?? {};
73
+ if (uploadProgress) {
74
+ task.onProgressUpdate(
75
+ (res) => uploadProgress(toProgress(res.totalBytesSent, res.totalBytesExpectedToSend))
76
+ );
77
+ }
78
+ if (downloadProgress) {
79
+ task.onProgressUpdate(
80
+ (res) => downloadProgress(toProgress(res.totalBytesReceived, res.totalBytesExpectedToReceive))
81
+ );
82
+ }
83
+ }
84
+ } catch (error) {
85
+ settleFail(error);
86
+ }
87
+ });
88
+ });
89
+ export {
90
+ uniappAdapter
91
+ };
92
+ //# sourceMappingURL=uniapp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/uniapp.ts"],"sourcesContent":["import { defineAdapter } from '../core'\nimport { createAbortError } from '../errors'\nimport type { RawResponse, RequestConfig, RequestProgress } from '../types'\n\n/** 归一化 uni 进度事件(progress 0-100 → 0-1):total 缺失/为 0 时 progress 为 undefined */\nfunction toProgress(loaded: number, total?: number): RequestProgress {\n const t = total && total > 0 ? total : undefined\n return {\n loaded,\n total: t,\n progress: t !== undefined ? Math.min(loaded / t, 1) : undefined,\n }\n}\n\n/** uni.request 的最小类型声明(按需扩展) */\ninterface UniProgressInfo {\n /** 0-100 */\n progress: number\n totalBytesSent: number\n totalBytesExpectedToSend: number\n totalBytesReceived: number\n totalBytesExpectedToReceive: number\n}\n\ninterface UniRequestTask {\n abort(): void\n /** 进度监听为可选能力(旧基础库可能没有) */\n onProgressUpdate?(callback: (res: UniProgressInfo) => void): void\n}\n\ninterface UniRequestOptions {\n url: string\n method?: string\n data?: unknown\n /** 注意:uni.request 的请求头字段是 header,不是 headers */\n header?: Record<string, string>\n timeout?: number\n success?: (res: {\n statusCode: number\n data: unknown\n header?: Record<string, string>\n }) => void\n fail?: (err: { errMsg?: string }) => void\n}\n\ninterface UniLike {\n request(options: UniRequestOptions): UniRequestTask\n}\n\ndeclare const uni: UniLike\n\n/**\n * uni-app adapter 插件:基于 uni.request(uni-app 全局 API),按需导入,主入口不含本模块。\n * 经 defineAdapter 导出:可直接使用,也可 .use() 叠加装饰器。\n * 注意:uni-app ≠ 原生微信小程序——原生小程序的全局 API 是 wx.request,需要单独的 adapter。\n * - uni.request 的非 2xx 响应也走 success 且不 reject,所以这里原样 resolve,状态码判断统一交给 core\n * - fail 中的 abort(errMsg 形如 \"request:fail abort\")翻译为统一 AbortError\n * - AbortSignal 桥接到 task.abort(),实现与其他 adapter 一致的统一取消\n */\nexport const uniappAdapter = defineAdapter((config: RequestConfig) => {\n return new Promise<RawResponse>((resolve, reject) => {\n const signal = config.signal\n\n // 请求发出前就已取消:直接终止,不碰 uni.request\n if (signal?.aborted) {\n reject(createAbortError())\n return\n }\n\n // settled 防重复 settle;cleanup 保证结束后摘掉 abort 监听,避免泄漏\n let settled = false\n let task: UniRequestTask | undefined\n\n const cleanup = () => signal?.removeEventListener('abort', onAbort)\n\n const onAbort = () => {\n if (settled) return\n settled = true\n cleanup()\n task?.abort()\n reject(createAbortError())\n }\n\n const settleSuccess = (res: RawResponse) => {\n if (settled) return\n settled = true\n cleanup()\n resolve(res)\n }\n\n const settleFail = (error: unknown) => {\n if (settled) return\n settled = true\n cleanup()\n reject(error)\n }\n\n // 先挂监听再发请求,避免\"发出与取消之间\"的竞态窗口\n signal?.addEventListener('abort', onAbort, { once: true })\n\n try {\n task = uni.request({\n // 专有配置先展开(如 enableHttp2):契约派生字段在后覆盖\n ...(config.adapterOptions?.uniapp as Record<string, unknown> | undefined),\n url: config.url,\n method: config.method,\n data: config.data,\n header: config.headers,\n timeout: config.timeout,\n success: (res) => {\n // 非 2xx 响应也走 success,原样交给 core 统一判断\n settleSuccess({\n status: res.statusCode,\n data: res.data,\n headers: res.header ?? {},\n })\n },\n fail: (err) => {\n // 主动取消 → 统一取消语义\n if (/abort/i.test(err?.errMsg ?? '')) {\n onAbort()\n return\n }\n settleFail(err ?? new Error('request:fail'))\n },\n })\n\n // 进度回调注册(task.onProgressUpdate 为可选能力,缺失时静默跳过)\n if (task?.onProgressUpdate) {\n const { uploadProgress, downloadProgress } = config.on ?? {}\n if (uploadProgress) {\n task.onProgressUpdate((res) =>\n uploadProgress(toProgress(res.totalBytesSent, res.totalBytesExpectedToSend)),\n )\n }\n if (downloadProgress) {\n task.onProgressUpdate((res) =>\n downloadProgress(toProgress(res.totalBytesReceived, res.totalBytesExpectedToReceive)),\n )\n }\n }\n } catch (error) {\n settleFail(error)\n }\n })\n})\n"],"mappings":";;;;;;;;AAKA,SAAS,WAAW,QAAgB,OAAiC;AACnE,QAAM,IAAI,SAAS,QAAQ,IAAI,QAAQ;AACvC,SAAO;AAAA,IACL;AAAA,IACA,OAAO;AAAA,IACP,UAAU,MAAM,SAAY,KAAK,IAAI,SAAS,GAAG,CAAC,IAAI;AAAA,EACxD;AACF;AA+CO,IAAM,gBAAgB,cAAc,CAAC,WAA0B;AACpE,SAAO,IAAI,QAAqB,CAAC,SAAS,WAAW;AACnD,UAAM,SAAS,OAAO;AAGtB,QAAI,QAAQ,SAAS;AACnB,aAAO,iBAAiB,CAAC;AACzB;AAAA,IACF;AAGA,QAAI,UAAU;AACd,QAAI;AAEJ,UAAM,UAAU,MAAM,QAAQ,oBAAoB,SAAS,OAAO;AAElE,UAAM,UAAU,MAAM;AACpB,UAAI,QAAS;AACb,gBAAU;AACV,cAAQ;AACR,YAAM,MAAM;AACZ,aAAO,iBAAiB,CAAC;AAAA,IAC3B;AAEA,UAAM,gBAAgB,CAAC,QAAqB;AAC1C,UAAI,QAAS;AACb,gBAAU;AACV,cAAQ;AACR,cAAQ,GAAG;AAAA,IACb;AAEA,UAAM,aAAa,CAAC,UAAmB;AACrC,UAAI,QAAS;AACb,gBAAU;AACV,cAAQ;AACR,aAAO,KAAK;AAAA,IACd;AAGA,YAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAEzD,QAAI;AACF,aAAO,IAAI,QAAQ;AAAA;AAAA,QAEjB,GAAI,OAAO,gBAAgB;AAAA,QAC3B,KAAK,OAAO;AAAA,QACZ,QAAQ,OAAO;AAAA,QACf,MAAM,OAAO;AAAA,QACb,QAAQ,OAAO;AAAA,QACf,SAAS,OAAO;AAAA,QAChB,SAAS,CAAC,QAAQ;AAEhB,wBAAc;AAAA,YACZ,QAAQ,IAAI;AAAA,YACZ,MAAM,IAAI;AAAA,YACV,SAAS,IAAI,UAAU,CAAC;AAAA,UAC1B,CAAC;AAAA,QACH;AAAA,QACA,MAAM,CAAC,QAAQ;AAEb,cAAI,SAAS,KAAK,KAAK,UAAU,EAAE,GAAG;AACpC,oBAAQ;AACR;AAAA,UACF;AACA,qBAAW,OAAO,IAAI,MAAM,cAAc,CAAC;AAAA,QAC7C;AAAA,MACF,CAAC;AAGD,UAAI,MAAM,kBAAkB;AAC1B,cAAM,EAAE,gBAAgB,iBAAiB,IAAI,OAAO,MAAM,CAAC;AAC3D,YAAI,gBAAgB;AAClB,eAAK;AAAA,YAAiB,CAAC,QACrB,eAAe,WAAW,IAAI,gBAAgB,IAAI,wBAAwB,CAAC;AAAA,UAC7E;AAAA,QACF;AACA,YAAI,kBAAkB;AACpB,eAAK;AAAA,YAAiB,CAAC,QACrB,iBAAiB,WAAW,IAAI,oBAAoB,IAAI,2BAA2B,CAAC;AAAA,UACtF;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,CAAC;AACH,CAAC;","names":[]}
@@ -0,0 +1,23 @@
1
+ // src/interceptors/auth.ts
2
+ function createAuthInterceptor(options) {
3
+ const {
4
+ getToken,
5
+ headerName = "Authorization",
6
+ scheme = "Bearer ",
7
+ shouldAttach
8
+ } = options;
9
+ return async (config) => {
10
+ if (shouldAttach && !shouldAttach(config)) return config;
11
+ const token = await getToken();
12
+ if (!token) return config;
13
+ return {
14
+ ...config,
15
+ headers: { ...config.headers, [headerName]: `${scheme}${token}` }
16
+ };
17
+ };
18
+ }
19
+
20
+ export {
21
+ createAuthInterceptor
22
+ };
23
+ //# sourceMappingURL=chunk-FY6YU3PV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/interceptors/auth.ts"],"sourcesContent":["import type { RequestConfig, RequestInterceptor } from '../types'\n\nexport interface AuthInterceptorOptions {\n /** 取 token;支持异步(如先静默刷新)。返回空值则跳过注入 */\n getToken: () => string | undefined | null | Promise<string | undefined | null>\n /** 注入的头名,默认 'Authorization' */\n headerName?: string\n /** token 前缀,默认 'Bearer ';不需要前缀传空字符串 */\n scheme?: string\n /** 返回 false 的请求不注入(如登录接口本身) */\n shouldAttach?: (config: RequestConfig) => boolean\n}\n\n/** 请求链拦截器:从 getToken() 注入凭证头 */\nexport function createAuthInterceptor(\n options: AuthInterceptorOptions,\n): RequestInterceptor {\n const {\n getToken,\n headerName = 'Authorization',\n scheme = 'Bearer ',\n shouldAttach,\n } = options\n\n return async (config) => {\n if (shouldAttach && !shouldAttach(config)) return config\n const token = await getToken()\n if (!token) return config\n return {\n ...config,\n headers: { ...config.headers, [headerName]: `${scheme}${token}` },\n }\n }\n}\n"],"mappings":";AAcO,SAAS,sBACd,SACoB;AACpB,QAAM;AAAA,IACJ;AAAA,IACA,aAAa;AAAA,IACb,SAAS;AAAA,IACT;AAAA,EACF,IAAI;AAEJ,SAAO,OAAO,WAAW;AACvB,QAAI,gBAAgB,CAAC,aAAa,MAAM,EAAG,QAAO;AAClD,UAAM,QAAQ,MAAM,SAAS;AAC7B,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO;AAAA,MACL,GAAG;AAAA,MACH,SAAS,EAAE,GAAG,OAAO,SAAS,CAAC,UAAU,GAAG,GAAG,MAAM,GAAG,KAAK,GAAG;AAAA,IAClE;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,156 @@
1
+ import {
2
+ RequestError,
3
+ createAbortError,
4
+ isAbortError
5
+ } from "./chunk-Z4N22SLG.js";
6
+
7
+ // src/core.ts
8
+ var ABSOLUTE_URL_RE = /^https?:\/\//;
9
+ function buildUrl(baseURL, url, params) {
10
+ let full = url;
11
+ if (baseURL && !ABSOLUTE_URL_RE.test(url)) {
12
+ full = `${baseURL.replace(/\/+$/, "")}/${url.replace(/^\/+/, "")}`;
13
+ }
14
+ if (!params) return full;
15
+ const pairs = Object.entries(params).filter(([, value]) => value !== void 0 && value !== null).flatMap(
16
+ ([key, value]) => Array.isArray(value) ? value.map((item) => [key, item]) : [[key, value]]
17
+ ).map(
18
+ ([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`
19
+ );
20
+ if (pairs.length === 0) return full;
21
+ return full + (full.includes("?") ? "&" : "?") + pairs.join("&");
22
+ }
23
+ function isOkStatus(status) {
24
+ return status >= 200 && status < 300;
25
+ }
26
+ function now() {
27
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
28
+ }
29
+ function isRawResponseLike(value) {
30
+ return typeof value === "object" && value !== null && "status" in value && "data" in value;
31
+ }
32
+ function toRequestError(error, config) {
33
+ if (error instanceof RequestError) return error;
34
+ if (isAbortError(error)) {
35
+ return new RequestError("abort", error.message, {
36
+ config,
37
+ raw: error
38
+ });
39
+ }
40
+ const message = error instanceof Error ? error.message : String(error);
41
+ return new RequestError("network", message || "Network request failed", {
42
+ config,
43
+ raw: error
44
+ });
45
+ }
46
+ function defineAdapter(adapter) {
47
+ const chain = (enhancers, extras) => {
48
+ let composed;
49
+ const getComposed = () => {
50
+ if (composed === void 0) {
51
+ composed = enhancers.reduceRight((acc, enhance) => enhance(acc), adapter);
52
+ }
53
+ return composed;
54
+ };
55
+ const wrapped = ((config) => getComposed()(config));
56
+ wrapped.use = (enhancer) => chain([...enhancers, enhancer], extras);
57
+ wrapped.extend = ((factory) => {
58
+ const added = factory((config) => getComposed()(config));
59
+ return chain([...enhancers], { ...extras, ...added });
60
+ });
61
+ for (const [key, value] of Object.entries(extras)) {
62
+ ;
63
+ wrapped[key] = value;
64
+ }
65
+ return wrapped;
66
+ };
67
+ return chain([], {});
68
+ }
69
+ function createRequest(options) {
70
+ const requestInterceptors = [];
71
+ const responseInterceptors = [];
72
+ const errorInterceptors = [];
73
+ async function request(input) {
74
+ let config = {
75
+ ...input,
76
+ method: input.method ?? "GET",
77
+ baseURL: input.baseURL ?? options.baseURL,
78
+ timeout: input.timeout ?? options.timeout,
79
+ headers: { ...options.headers, ...input.headers }
80
+ };
81
+ try {
82
+ for (const interceptor of requestInterceptors) {
83
+ config = await interceptor(config);
84
+ }
85
+ config = { ...config, url: buildUrl(config.baseURL, config.url, config.params) };
86
+ if (config.signal?.aborted) throw createAbortError();
87
+ const startedAt = now();
88
+ let res;
89
+ try {
90
+ res = await options.adapter({ ...config, params: void 0 });
91
+ } catch (error) {
92
+ throw toRequestError(error, config);
93
+ }
94
+ res.durationMs = Math.round(now() - startedAt);
95
+ res.config = { ...config };
96
+ if (!isOkStatus(res.status)) {
97
+ throw new RequestError("http", `Request failed with status ${res.status}`, {
98
+ status: res.status,
99
+ data: res.data,
100
+ headers: res.headers,
101
+ config
102
+ });
103
+ }
104
+ for (const onFulfilled of responseInterceptors) {
105
+ res = await onFulfilled(res);
106
+ }
107
+ return res;
108
+ } catch (error) {
109
+ let current = toRequestError(error, config);
110
+ for (const onError of errorInterceptors) {
111
+ let out;
112
+ try {
113
+ out = await onError(current);
114
+ } catch (thrown) {
115
+ current = toRequestError(thrown, config);
116
+ continue;
117
+ }
118
+ if (isRawResponseLike(out)) return out;
119
+ if (out instanceof RequestError) current = out;
120
+ }
121
+ throw current;
122
+ }
123
+ }
124
+ return {
125
+ request,
126
+ get: (url, config) => request({ ...config, url, method: "GET" }),
127
+ post: (url, data, config) => request({ ...config, url, data, method: "POST" }),
128
+ put: (url, data, config) => request({ ...config, url, data, method: "PUT" }),
129
+ patch: (url, data, config) => request({ ...config, url, data, method: "PATCH" }),
130
+ delete: (url, config) => request({ ...config, url, method: "DELETE" }),
131
+ interceptors: {
132
+ request: {
133
+ use: (interceptor) => {
134
+ requestInterceptors.push(interceptor);
135
+ }
136
+ },
137
+ response: {
138
+ use: (interceptor) => {
139
+ responseInterceptors.push(interceptor);
140
+ }
141
+ },
142
+ error: {
143
+ use: (interceptor) => {
144
+ errorInterceptors.push(interceptor);
145
+ }
146
+ }
147
+ }
148
+ };
149
+ }
150
+
151
+ export {
152
+ buildUrl,
153
+ defineAdapter,
154
+ createRequest
155
+ };
156
+ //# sourceMappingURL=chunk-GMJTG6KM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core.ts"],"sourcesContent":["import { createAbortError, isAbortError, RequestError } from './errors'\nimport type {\n AdapterEnhancer,\n ChainableAdapter,\n ErrorInterceptor,\n RawResponse,\n RequestAdapter,\n RequestConfig,\n RequestInterceptor,\n RequestMethod,\n ResponseInterceptor,\n} from './types'\n\nexport interface CreateRequestOptions {\n /** 必传:adapter 插件(axiosAdapter / fetchAdapter / uniappAdapter / 自定义 / 测试 mock) */\n adapter: RequestAdapter\n baseURL?: string\n headers?: Record<string, string>\n timeout?: number\n}\n\nexport interface RequestInstance {\n request<T = unknown>(config: RequestConfig): Promise<RawResponse<T>>\n get<T = unknown>(\n url: string,\n config?: Omit<RequestConfig, 'url' | 'method'>,\n ): Promise<RawResponse<T>>\n post<T = unknown>(\n url: string,\n data?: unknown,\n config?: Omit<RequestConfig, 'url' | 'method' | 'data'>,\n ): Promise<RawResponse<T>>\n put<T = unknown>(\n url: string,\n data?: unknown,\n config?: Omit<RequestConfig, 'url' | 'method' | 'data'>,\n ): Promise<RawResponse<T>>\n patch<T = unknown>(\n url: string,\n data?: unknown,\n config?: Omit<RequestConfig, 'url' | 'method' | 'data'>,\n ): Promise<RawResponse<T>>\n delete<T = unknown>(\n url: string,\n config?: Omit<RequestConfig, 'url' | 'method'>,\n ): Promise<RawResponse<T>>\n interceptors: {\n request: { use(interceptor: RequestInterceptor): void }\n /** 成功链:转换 / 剥壳 */\n response: { use(interceptor: ResponseInterceptor): void }\n /**\n * 错误链:所有失败都会经过(请求拦截器抛错 / 网络失败 / 非 2xx / 响应链抛错)。\n * 返回 RawResponse = 恢复;返回/抛出 RequestError = 替换;无返回值 = 放行(观察)。\n */\n error: { use(interceptor: ErrorInterceptor): void }\n }\n}\n\nconst ABSOLUTE_URL_RE = /^https?:\\/\\//\n\n/**\n * 拼接 baseURL + url,并把 params 序列化为 query。\n * - 绝对 URL 忽略 baseURL\n * - 跳过 null / undefined;数组值序列化为重复 key(?id=1&id=2)\n * - url 已带 ? 时用 & 追加\n * params 统一在这里处理,保证两端一致,adapter 无需各自实现。\n */\nexport function buildUrl(\n baseURL: string | undefined,\n url: string,\n params?: Record<string, unknown>,\n): string {\n let full = url\n if (baseURL && !ABSOLUTE_URL_RE.test(url)) {\n full = `${baseURL.replace(/\\/+$/, '')}/${url.replace(/^\\/+/, '')}`\n }\n if (!params) return full\n\n const pairs = Object.entries(params)\n .filter(([, value]) => value !== undefined && value !== null)\n .flatMap(([key, value]) =>\n Array.isArray(value)\n ? value.map((item) => [key, item] as const)\n : [[key, value] as const],\n )\n .map(\n ([key, value]) =>\n `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`,\n )\n if (pairs.length === 0) return full\n\n return full + (full.includes('?') ? '&' : '?') + pairs.join('&')\n}\n\nfunction isOkStatus(status: number): boolean {\n return status >= 200 && status < 300\n}\n\n/** 高精度计时;performance 不存在的运行时(部分小程序环境)退化为 Date.now */\nfunction now(): number {\n return typeof performance !== 'undefined' ? performance.now() : Date.now()\n}\n\n/** 判断错误拦截器的返回值是否为恢复(RawResponse 形状) */\nfunction isRawResponseLike(value: unknown): value is RawResponse {\n return (\n typeof value === 'object' && value !== null && 'status' in value && 'data' in value\n )\n}\n\n/** 把任意抛出的错误归一化为 RequestError(已是则透传) */\nfunction toRequestError(error: unknown, config?: RequestConfig): RequestError {\n if (error instanceof RequestError) return error\n if (isAbortError(error)) {\n return new RequestError('abort', (error as Error).message, {\n config,\n raw: error,\n })\n }\n const message = error instanceof Error ? error.message : String(error)\n return new RequestError('network', message || 'Network request failed', {\n config,\n raw: error,\n })\n}\n\n/**\n * adapter 的显式扩展点:把任意(含第三方)adapter 变为可链式增强的 adapter,\n * 同时为自定义 adapter 的实现函数提供参数类型推断。\n *\n * const myAdapter = defineAdapter(async (config) => {\n * // config.url 已是 core 拼好 params 的完整地址\n * // 返回 RawResponse;取消时抛 name='AbortError' 的错误\n * }).use(withRetry()).use(withDedupe()) // 注册顺序 = 请求流经顺序\n *\n * 平台特有方法(如 upload/downloadFile)用 .extend 附加:\n * .extend((request) => ({ upload: (file) => ... }))\n * 工厂收到\"组合后的适配器\",附加方法可路由完整管道,并在后续 .use 链上保持不丢。\n *\n * 返回值本身仍是合法的 RequestAdapter,可直接传给 createRequest。\n */\nexport function defineAdapter(adapter: RequestAdapter): ChainableAdapter {\n const chain = <T extends object>(\n enhancers: AdapterEnhancer[],\n extras: T,\n ): ChainableAdapter<T> => {\n let composed: RequestAdapter | undefined\n const getComposed = (): RequestAdapter => {\n // 惰性组合并缓存:装饰器工厂只在首次请求时执行一次,.use 不触发重建\n if (composed === undefined) {\n composed = enhancers.reduceRight((acc, enhance) => enhance(acc), adapter)\n }\n return composed\n }\n\n const wrapped = ((config: RequestConfig) =>\n getComposed()(config)) as ChainableAdapter<T>\n wrapped.use = (enhancer) => chain([...enhancers, enhancer], extras)\n wrapped.extend = (<U extends object>(\n factory: (composed: RequestAdapter) => U,\n ) => {\n // extras 工厂以\"当前已组合的适配器\"执行一次,附加方法天然带上此前注册的装饰器\n const added = factory((config) => getComposed()(config))\n return chain([...enhancers], { ...extras, ...added })\n }) as ChainableAdapter<T>['extend']\n for (const [key, value] of Object.entries(extras)) {\n ;(wrapped as unknown as Record<string, unknown>)[key] = value\n }\n return wrapped\n }\n return chain([], {})\n}\n\nexport function createRequest(options: CreateRequestOptions): RequestInstance {\n const requestInterceptors: RequestInterceptor[] = []\n const responseInterceptors: ResponseInterceptor[] = []\n const errorInterceptors: ErrorInterceptor[] = []\n\n async function request<T = unknown>(\n input: RequestConfig,\n ): Promise<RawResponse<T>> {\n // 1. 合并实例默认值,请求级配置优先\n let config: RequestConfig = {\n ...input,\n method: (input.method ?? 'GET') as RequestMethod,\n baseURL: input.baseURL ?? options.baseURL,\n timeout: input.timeout ?? options.timeout,\n headers: { ...options.headers, ...input.headers },\n }\n\n try {\n // 2. 请求拦截器,按注册顺序执行\n for (const interceptor of requestInterceptors) {\n config = await interceptor(config)\n }\n\n // 3. params 统一在 core 拼进 url,并回写 config——错误链与响应链拿到的都是完整地址\n config = { ...config, url: buildUrl(config.baseURL, config.url, config.params) }\n\n // 4. dispatch 前检查取消,避免\"已取消还发请求\"的竞态\n if (config.signal?.aborted) throw createAbortError()\n\n // 5. 调 adapter,任何失败在此归一化;耗时与最终 config 由 core 统一注入响应\n const startedAt = now()\n let res: RawResponse\n try {\n res = await options.adapter({ ...config, params: undefined })\n } catch (error) {\n throw toRequestError(error, config)\n }\n res.durationMs = Math.round(now() - startedAt)\n res.config = { ...config }\n\n // 6. 状态码判断统一在 core:非 2xx → http 错误(两端行为一致的关键)\n if (!isOkStatus(res.status)) {\n throw new RequestError('http', `Request failed with status ${res.status}`, {\n status: res.status,\n data: res.data,\n headers: res.headers,\n config,\n })\n }\n\n // 7. 响应拦截器(成功链,按注册顺序执行)\n for (const onFulfilled of responseInterceptors) {\n res = await onFulfilled(res)\n }\n return res as RawResponse<T>\n } catch (error) {\n // 8. 统一错误出口:错误链按注册顺序执行\n // 返回 RawResponse = 恢复;返回/抛出 RequestError = 替换;无返回值 = 放行(观察)\n let current = toRequestError(error, config)\n for (const onError of errorInterceptors) {\n let out: RawResponse | RequestError | void\n try {\n out = await onError(current)\n } catch (thrown) {\n current = toRequestError(thrown, config)\n continue\n }\n if (isRawResponseLike(out)) return out as RawResponse<T>\n if (out instanceof RequestError) current = out\n }\n throw current\n }\n }\n\n return {\n request,\n get: (url, config) => request({ ...config, url, method: 'GET' }),\n post: (url, data, config) => request({ ...config, url, data, method: 'POST' }),\n put: (url, data, config) => request({ ...config, url, data, method: 'PUT' }),\n patch: (url, data, config) => request({ ...config, url, data, method: 'PATCH' }),\n delete: (url, config) => request({ ...config, url, method: 'DELETE' }),\n interceptors: {\n request: {\n use: (interceptor) => {\n requestInterceptors.push(interceptor)\n },\n },\n response: {\n use: (interceptor) => {\n responseInterceptors.push(interceptor)\n },\n },\n error: {\n use: (interceptor) => {\n errorInterceptors.push(interceptor)\n },\n },\n },\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,26 @@
1
+ // src/interceptors/logger.ts
2
+ var defaultLog = (level, ...args) => {
3
+ if (level === "warn") console.warn(...args);
4
+ else console.log(...args);
5
+ };
6
+ function createLoggerInterceptors(options = {}) {
7
+ const log = options.log ?? defaultLog;
8
+ return {
9
+ request: (config) => {
10
+ log("debug", "[request]", config.method ?? "GET", config.url, config.params ?? "");
11
+ return config;
12
+ },
13
+ response: (res) => {
14
+ log("debug", "[response]", res.status, `${res.durationMs ?? "?"}ms`, res.config?.url ?? "");
15
+ return res;
16
+ },
17
+ error: (error) => {
18
+ log("warn", "[error]", error.kind, error.message, error.config?.url ?? "");
19
+ }
20
+ };
21
+ }
22
+
23
+ export {
24
+ createLoggerInterceptors
25
+ };
26
+ //# sourceMappingURL=chunk-KLAI63DX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/interceptors/logger.ts"],"sourcesContent":["import type {\n ErrorInterceptor,\n RequestInterceptor,\n ResponseInterceptor,\n} from '../types'\n\nexport type LogLevel = 'debug' | 'warn'\nexport type LogFn = (level: LogLevel, ...args: unknown[]) => void\n\nexport interface LoggerInterceptorsOptions {\n /**\n * 自定义日志函数(如接埋点 SDK)。注意:不应抛错——\n * request/response 链中抛错会进入错误链,error 链中抛错会替换原错误。\n */\n log?: LogFn\n}\n\nconst defaultLog: LogFn = (level, ...args) => {\n if (level === 'warn') console.warn(...args)\n else console.log(...args)\n}\n\n/**\n * 三链日志:返回 request / response / error 三件套,分别注册到对应链。\n * - request:method / url / params\n * - response:status / 耗时 / url(读 core 注入的 res.durationMs 与 res.config)\n * - error:kind / message / url(读 err.config)\n */\nexport function createLoggerInterceptors(options: LoggerInterceptorsOptions = {}): {\n request: RequestInterceptor\n response: ResponseInterceptor\n error: ErrorInterceptor\n} {\n const log = options.log ?? defaultLog\n\n return {\n request: (config) => {\n log('debug', '[request]', config.method ?? 'GET', config.url, config.params ?? '')\n return config\n },\n response: (res) => {\n log('debug', '[response]', res.status, `${res.durationMs ?? '?'}ms`, res.config?.url ?? '')\n return res\n },\n error: (error) => {\n log('warn', '[error]', error.kind, error.message, error.config?.url ?? '')\n },\n }\n}\n"],"mappings":";AAiBA,IAAM,aAAoB,CAAC,UAAU,SAAS;AAC5C,MAAI,UAAU,OAAQ,SAAQ,KAAK,GAAG,IAAI;AAAA,MACrC,SAAQ,IAAI,GAAG,IAAI;AAC1B;AAQO,SAAS,yBAAyB,UAAqC,CAAC,GAI7E;AACA,QAAM,MAAM,QAAQ,OAAO;AAE3B,SAAO;AAAA,IACL,SAAS,CAAC,WAAW;AACnB,UAAI,SAAS,aAAa,OAAO,UAAU,OAAO,OAAO,KAAK,OAAO,UAAU,EAAE;AACjF,aAAO;AAAA,IACT;AAAA,IACA,UAAU,CAAC,QAAQ;AACjB,UAAI,SAAS,cAAc,IAAI,QAAQ,GAAG,IAAI,cAAc,GAAG,MAAM,IAAI,QAAQ,OAAO,EAAE;AAC1F,aAAO;AAAA,IACT;AAAA,IACA,OAAO,CAAC,UAAU;AAChB,UAAI,QAAQ,WAAW,MAAM,MAAM,MAAM,SAAS,MAAM,QAAQ,OAAO,EAAE;AAAA,IAC3E;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,37 @@
1
+ import {
2
+ RequestError
3
+ } from "./chunk-Z4N22SLG.js";
4
+
5
+ // src/interceptors/unwrap.ts
6
+ function createUnwrapInterceptor(options = {}) {
7
+ const {
8
+ successCodes = [0],
9
+ codeKey = "code",
10
+ dataKey = "data",
11
+ messageKey = "message"
12
+ } = options;
13
+ return (res) => {
14
+ const body = res.data;
15
+ if (typeof body !== "object" || body === null || !(codeKey in body)) {
16
+ return res;
17
+ }
18
+ const code = body[codeKey];
19
+ if (successCodes.includes(code)) {
20
+ return { ...res, data: body[dataKey] };
21
+ }
22
+ throw new RequestError(
23
+ "business",
24
+ String(body[messageKey] || `Business error (code: ${String(code)})`),
25
+ {
26
+ status: res.status,
27
+ data: body,
28
+ config: res.config
29
+ }
30
+ );
31
+ };
32
+ }
33
+
34
+ export {
35
+ createUnwrapInterceptor
36
+ };
37
+ //# sourceMappingURL=chunk-OV6LXQTN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/interceptors/unwrap.ts"],"sourcesContent":["import { RequestError } from '../errors'\nimport type { ResponseInterceptor } from '../types'\n\nexport interface UnwrapInterceptorOptions {\n /** 业务成功码(后端常见字符串码,允许 string | number),默认 [0] */\n successCodes?: Array<string | number>\n /** 业务码字段名,默认 'code' */\n codeKey?: string\n /** 业务数据字段名,默认 'data' */\n dataKey?: string\n /** 业务消息字段名,默认 'message' */\n messageKey?: string\n}\n\n/**\n * 响应链拦截器:剥离 {code, data, message} 业务壳。\n * - body 非对象或缺 codeKey:视为非业务壳响应,原样放行(防御非 JSON 响应)\n * - 业务成功:res.data 替换为业务 data\n * - 业务失败:抛 RequestError(kind='business'),进入错误链\n */\nexport function createUnwrapInterceptor(\n options: UnwrapInterceptorOptions = {},\n): ResponseInterceptor {\n const {\n successCodes = [0],\n codeKey = 'code',\n dataKey = 'data',\n messageKey = 'message',\n } = options\n\n return (res) => {\n const body = res.data as Record<string, unknown> | undefined\n if (typeof body !== 'object' || body === null || !(codeKey in body)) {\n return res\n }\n\n const code = body[codeKey]\n if (successCodes.includes(code as string | number)) {\n return { ...res, data: body[dataKey] }\n }\n\n throw new RequestError(\n 'business',\n String(body[messageKey] || `Business error (code: ${String(code)})`),\n {\n status: res.status,\n data: body,\n config: res.config,\n },\n )\n }\n}\n"],"mappings":";;;;;AAoBO,SAAS,wBACd,UAAoC,CAAC,GAChB;AACrB,QAAM;AAAA,IACJ,eAAe,CAAC,CAAC;AAAA,IACjB,UAAU;AAAA,IACV,UAAU;AAAA,IACV,aAAa;AAAA,EACf,IAAI;AAEJ,SAAO,CAAC,QAAQ;AACd,UAAM,OAAO,IAAI;AACjB,QAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,EAAE,WAAW,OAAO;AACnE,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,KAAK,OAAO;AACzB,QAAI,aAAa,SAAS,IAAuB,GAAG;AAClD,aAAO,EAAE,GAAG,KAAK,MAAM,KAAK,OAAO,EAAE;AAAA,IACvC;AAEA,UAAM,IAAI;AAAA,MACR;AAAA,MACA,OAAO,KAAK,UAAU,KAAK,yBAAyB,OAAO,IAAI,CAAC,GAAG;AAAA,MACnE;AAAA,QACE,QAAQ,IAAI;AAAA,QACZ,MAAM;AAAA,QACN,QAAQ,IAAI;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,27 @@
1
+ // src/interceptors/request-id.ts
2
+ function defaultGenerateId() {
3
+ const crypto = globalThis.crypto;
4
+ if (typeof crypto?.randomUUID === "function") {
5
+ return crypto.randomUUID();
6
+ }
7
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
8
+ }
9
+ function createRequestIdInterceptor(options = {}) {
10
+ const {
11
+ headerName = "X-Request-Id",
12
+ generateId = defaultGenerateId,
13
+ keepExisting = false
14
+ } = options;
15
+ return (config) => {
16
+ if (keepExisting && config.headers?.[headerName]) return config;
17
+ return {
18
+ ...config,
19
+ headers: { ...config.headers, [headerName]: generateId() }
20
+ };
21
+ };
22
+ }
23
+
24
+ export {
25
+ createRequestIdInterceptor
26
+ };
27
+ //# sourceMappingURL=chunk-UCTOFBDY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/interceptors/request-id.ts"],"sourcesContent":["import type { RequestConfig, RequestInterceptor } from '../types'\n\n/**\n * 默认 ID 生成器:优先 crypto.randomUUID(安全上下文/新运行时),\n * 运行时缺失(部分小程序/旧 WebView)退化为时间戳+随机数——\n * 请求 ID 只求会话内唯一便于关联,不要求密码学强度。\n */\nfunction defaultGenerateId(): string {\n const crypto = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto\n if (typeof crypto?.randomUUID === 'function') {\n return crypto.randomUUID()\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n}\n\nexport interface RequestIdInterceptorOptions {\n /** 请求头名,默认 'X-Request-Id' */\n headerName?: string\n /** 自定义 ID 生成器(如接入 tracing/Sentry 等链路库) */\n generateId?: () => string\n /**\n * 请求已携带该头时不覆盖(用于上游链路 ID 透传),默认 false。\n * 注意:实例级默认头也算\"已携带\",开启后会导致所有请求共用一个 ID,慎用。\n */\n keepExisting?: boolean\n}\n\n/**\n * 请求链拦截器:为每个请求生成唯一 ID 注入请求头,用于前后端日志关联与报障定位。\n * - core 会把最终 config 盖章到 RawResponse.config 与 RequestError.config,\n * 因此响应链日志、错误上报都能取到同一个 ID\n * - 重试共享同一 config:同一次逻辑请求(含重试)带同一个 ID,服务端可识别重发\n */\nexport function createRequestIdInterceptor(\n options: RequestIdInterceptorOptions = {},\n): RequestInterceptor {\n const {\n headerName = 'X-Request-Id',\n generateId = defaultGenerateId,\n keepExisting = false,\n } = options\n\n return (config: RequestConfig) => {\n if (keepExisting && config.headers?.[headerName]) return config\n return {\n ...config,\n headers: { ...config.headers, [headerName]: generateId() },\n }\n }\n}\n"],"mappings":";AAOA,SAAS,oBAA4B;AACnC,QAAM,SAAU,WAA0D;AAC1E,MAAI,OAAO,QAAQ,eAAe,YAAY;AAC5C,WAAO,OAAO,WAAW;AAAA,EAC3B;AACA,SAAO,GAAG,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAC9E;AAoBO,SAAS,2BACd,UAAuC,CAAC,GACpB;AACpB,QAAM;AAAA,IACJ,aAAa;AAAA,IACb,aAAa;AAAA,IACb,eAAe;AAAA,EACjB,IAAI;AAEJ,SAAO,CAAC,WAA0B;AAChC,QAAI,gBAAgB,OAAO,UAAU,UAAU,EAAG,QAAO;AACzD,WAAO;AAAA,MACL,GAAG;AAAA,MACH,SAAS,EAAE,GAAG,OAAO,SAAS,CAAC,UAAU,GAAG,WAAW,EAAE;AAAA,IAC3D;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,28 @@
1
+ // src/errors.ts
2
+ var RequestError = class extends Error {
3
+ constructor(kind, message, options = {}) {
4
+ super(message);
5
+ this.name = "RequestError";
6
+ this.kind = kind;
7
+ this.status = options.status;
8
+ this.data = options.data;
9
+ this.headers = options.headers;
10
+ this.config = options.config;
11
+ this.raw = options.raw;
12
+ }
13
+ };
14
+ function createAbortError(message = "The request was aborted") {
15
+ const error = new Error(message);
16
+ error.name = "AbortError";
17
+ return error;
18
+ }
19
+ function isAbortError(error) {
20
+ return typeof error === "object" && error !== null && error.name === "AbortError";
21
+ }
22
+
23
+ export {
24
+ RequestError,
25
+ createAbortError,
26
+ isAbortError
27
+ };
28
+ //# sourceMappingURL=chunk-Z4N22SLG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts"],"sourcesContent":["import type { RequestConfig } from './types'\n\n/**\n * 统一错误四分类:\n * - http:收到响应但状态码非 2xx(业务层面最常处理的一类)\n * - business:HTTP 2xx 但业务码失败(由内置 unwrap 拦截器产生)\n * - abort:请求被取消(AbortSignal 或平台取消语义)\n * - network:传输层失败,含超时;通过 raw 可拿到原始错误(axios 的 error.code / uni 的 errMsg)\n */\nexport type RequestErrorKind = 'abort' | 'http' | 'business' | 'network'\n\nexport interface RequestErrorOptions {\n status?: number\n data?: unknown\n headers?: Record<string, string>\n config?: RequestConfig\n /** 原始错误:axios error / uni fail 回调的 err,便于排查 */\n raw?: unknown\n}\n\nexport class RequestError extends Error {\n readonly kind: RequestErrorKind\n readonly status?: number\n readonly data?: unknown\n readonly headers?: Record<string, string>\n readonly config?: RequestConfig\n readonly raw?: unknown\n\n constructor(\n kind: RequestErrorKind,\n message: string,\n options: RequestErrorOptions = {},\n ) {\n super(message)\n this.name = 'RequestError'\n this.kind = kind\n this.status = options.status\n this.data = options.data\n this.headers = options.headers\n this.config = options.config\n this.raw = options.raw\n }\n}\n\n/**\n * 生成统一形态的 AbortError。\n * 不用 DOMException:小程序运行时没有 DOMException,\n * Error + name='AbortError' 与 `e.name === 'AbortError'` 的通用判定方式兼容。\n */\nexport function createAbortError(message = 'The request was aborted'): Error {\n const error = new Error(message)\n error.name = 'AbortError'\n return error\n}\n\n/** 是否为取消错误(兼容原生 DOMException/AbortController 与本库生成的) */\nexport function isAbortError(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n (error as Error).name === 'AbortError'\n )\n}\n"],"mappings":";AAoBO,IAAM,eAAN,cAA2B,MAAM;AAAA,EAQtC,YACE,MACA,SACA,UAA+B,CAAC,GAChC;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,SAAS,QAAQ;AACtB,SAAK,OAAO,QAAQ;AACpB,SAAK,UAAU,QAAQ;AACvB,SAAK,SAAS,QAAQ;AACtB,SAAK,MAAM,QAAQ;AAAA,EACrB;AACF;AAOO,SAAS,iBAAiB,UAAU,2BAAkC;AAC3E,QAAM,QAAQ,IAAI,MAAM,OAAO;AAC/B,QAAM,OAAO;AACb,SAAO;AACT;AAGO,SAAS,aAAa,OAAyB;AACpD,SACE,OAAO,UAAU,YACjB,UAAU,QACT,MAAgB,SAAS;AAE9B;","names":[]}