@huangqi_an/request 0.1.0 → 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.
- package/README.md +57 -2
- package/dist/adapters/axios.js +1 -1
- package/dist/adapters/fetch.js +1 -1
- package/dist/adapters/uniapp.js +1 -1
- package/dist/{chunk-DQI4SM2Q.js → chunk-GMJTG6KM.js} +16 -6
- package/dist/chunk-GMJTG6KM.js.map +1 -0
- package/dist/core.d.ts +5 -0
- package/dist/core.js +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/interceptors/index.d.ts +1 -1
- package/dist/{types-CoJSp-ar.d.ts → types-DSmcNU2O.d.ts} +15 -6
- package/package.json +1 -1
- package/dist/chunk-DQI4SM2Q.js.map +0 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
- [适配器装饰器](#适配器装饰器)
|
|
21
21
|
- [组合:use 链](#组合use-链) / [withRetry](#withretry) / [withDedupe](#withdedupe) / [幂等性默认](#幂等性默认)
|
|
22
22
|
- [适配器](#适配器)
|
|
23
|
-
- [选型](#选型) / [能力矩阵](#能力矩阵) / [uni-app 平台选择](#uni-app-平台选择) / [自定义适配器](#自定义适配器)
|
|
23
|
+
- [选型](#选型) / [能力矩阵](#能力矩阵) / [uni-app 平台选择](#uni-app-平台选择) / [自定义适配器](#自定义适配器) / [extend:附加平台方法](#extend附加平台方法)
|
|
24
24
|
- [进阶](#进阶)
|
|
25
25
|
- [统一取消](#统一取消) / [事件](#事件) / [adapterOptions 透传](#adapteroptions-透传)
|
|
26
26
|
- [设计决策](#设计决策)
|
|
@@ -152,10 +152,11 @@ const http = createRequest({ adapter: fetchAdapter, baseURL: '...' })
|
|
|
152
152
|
|
|
153
153
|
| 方法 | 说明 |
|
|
154
154
|
| --- | --- |
|
|
155
|
-
| `request<T>(config)` |
|
|
155
|
+
| `request<T>(config)` | 全量配置发请求,`method` 接受任意 `RequestMethod`(含 PATCH 等)——这是唯一必需的入口 |
|
|
156
156
|
| `get<T>(url, config?)` | 快捷方法,`config` 中省略 url/method |
|
|
157
157
|
| `post<T>(url, data?, config?)` | 同上,省略 url/method/data |
|
|
158
158
|
| `put<T>(url, data?, config?)` | 同上 |
|
|
159
|
+
| `patch<T>(url, data?, config?)` | 同上 |
|
|
159
160
|
| `delete<T>(url, config?)` | 同上 |
|
|
160
161
|
| `http.interceptors.request.use(fn)` | 注册请求链拦截器 |
|
|
161
162
|
| `http.interceptors.response.use(fn)` | 注册响应链拦截器(成功转换) |
|
|
@@ -163,6 +164,24 @@ const http = createRequest({ adapter: fetchAdapter, baseURL: '...' })
|
|
|
163
164
|
|
|
164
165
|
全部请求方法返回 `Promise<RawResponse<T>>`。
|
|
165
166
|
|
|
167
|
+
快捷方法只是 `request()` 的语法糖,你不调用就没有任何成本。需要**裁剪方法面**(如团队规范禁用
|
|
168
|
+
DELETE)或自定义动词时,在业务层组合自己的 client 对象——库保持完整能力,导出面由你决定:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
// src/api.ts —— 团队实际消费的入口
|
|
172
|
+
import { createRequest } from './src'
|
|
173
|
+
import { fetchAdapter } from './src/adapters/fetch'
|
|
174
|
+
|
|
175
|
+
const base = createRequest({ adapter: fetchAdapter, baseURL: 'https://api.example.com' })
|
|
176
|
+
|
|
177
|
+
export const api = {
|
|
178
|
+
get: base.get,
|
|
179
|
+
post: base.post,
|
|
180
|
+
patch: (url: string, data?: unknown) => base.request({ url, data, method: 'PATCH' }),
|
|
181
|
+
}
|
|
182
|
+
// 没有 put/delete:业务方拿不到,core 的任何语义不受影响
|
|
183
|
+
```
|
|
184
|
+
|
|
166
185
|
### RequestConfig
|
|
167
186
|
|
|
168
187
|
| 字段 | 类型 | 说明 |
|
|
@@ -396,6 +415,10 @@ const antiDoubleSubmit = fetchAdapter.use(withDedupe({ methods: ['POST'] })) //
|
|
|
396
415
|
| `uniappAdapter` | ✓ | ✓ | `task.onProgressUpdate` 归一化(旧基础库缺失该 API 时跳过) |
|
|
397
416
|
| `fetchAdapter` | ✗ | ✓ | fetch 无上传进度(平台限制);下载走 `res.body` 流式读取,`Content-Length` 缺失时 `total/progress` 为 undefined |
|
|
398
417
|
|
|
418
|
+
> HTTP 动词备注:`method` 直接透传(`RequestMethod` 含全动词),但微信小程序端受 `wx.request`
|
|
419
|
+
> 方法白名单限制(仅 OPTIONS/GET/HEAD/POST/PUT/DELETE/TRACE/CONNECT,**不含 PATCH**)——
|
|
420
|
+
> mp 端需要 PATCH 时用 POST + 语义路径代替,或经 `adapterOptions: { uniapp: {...} }` 定制。
|
|
421
|
+
|
|
399
422
|
### uni-app 平台选择
|
|
400
423
|
|
|
401
424
|
注意:**uni-app ≠ 原生微信小程序**。本库的 `uniappAdapter` 依赖 uni-app 全局 `uni.request`;
|
|
@@ -436,6 +459,38 @@ const http = createRequest({ adapter: myAdapter.use(withRetry()) })
|
|
|
436
459
|
|
|
437
460
|
可运行的完整示例见 `tests/custom-adapter.test.ts`(一个与内置 `adapters/fetch.ts` 同构的 fetch 实现)。
|
|
438
461
|
|
|
462
|
+
### extend:附加平台方法
|
|
463
|
+
|
|
464
|
+
适配器可携带平台特有方法(如小程序的 `uploadFile`)。`.extend` 的工厂收到**组合后的适配器**,
|
|
465
|
+
附加方法可路由完整管道(此前的拦截器/装饰器都生效),并在后续 `.use` 链上保持不丢:
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
import { uniappAdapter } from './src/adapters/uniapp'
|
|
469
|
+
import { withRetry } from './src/enhancers/retry'
|
|
470
|
+
|
|
471
|
+
const mp = uniappAdapter
|
|
472
|
+
.use(withRetry({ methods: ['GET', 'POST'] }))
|
|
473
|
+
.extend((request) => ({
|
|
474
|
+
upload: (filePath: string, url: string) =>
|
|
475
|
+
new Promise((resolve, reject) => {
|
|
476
|
+
uni.uploadFile({
|
|
477
|
+
url,
|
|
478
|
+
filePath,
|
|
479
|
+
name: 'file',
|
|
480
|
+
success: (res) => resolve({ status: res.statusCode, data: res.data, headers: {} }),
|
|
481
|
+
fail: reject,
|
|
482
|
+
})
|
|
483
|
+
}),
|
|
484
|
+
}))
|
|
485
|
+
|
|
486
|
+
await mp.post('/x', {}) // 统一面不变
|
|
487
|
+
await mp.upload('/a.png', uploadUrl) // 平台特有方法(已带 retry)
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
语义:工厂以 `extend` 调用时刻的组合执行一次——要让附加方法也享受某个装饰器,**先 `.use` 再
|
|
491
|
+
`.extend`**;之后对同一对象的 `.use` 不影响已附加方法。自定义适配器同样支持
|
|
492
|
+
(`defineAdapter(fn).extend(...)`)。附加方法不要命名为 `use` / `extend`。
|
|
493
|
+
|
|
439
494
|
## 进阶
|
|
440
495
|
|
|
441
496
|
### 统一取消
|
package/dist/adapters/axios.js
CHANGED
package/dist/adapters/fetch.js
CHANGED
package/dist/adapters/uniapp.js
CHANGED
|
@@ -44,18 +44,27 @@ function toRequestError(error, config) {
|
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
function defineAdapter(adapter) {
|
|
47
|
-
const chain = (enhancers) => {
|
|
47
|
+
const chain = (enhancers, extras) => {
|
|
48
48
|
let composed;
|
|
49
|
-
const
|
|
49
|
+
const getComposed = () => {
|
|
50
50
|
if (composed === void 0) {
|
|
51
51
|
composed = enhancers.reduceRight((acc, enhance) => enhance(acc), adapter);
|
|
52
52
|
}
|
|
53
|
-
return composed
|
|
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 });
|
|
54
60
|
});
|
|
55
|
-
|
|
61
|
+
for (const [key, value] of Object.entries(extras)) {
|
|
62
|
+
;
|
|
63
|
+
wrapped[key] = value;
|
|
64
|
+
}
|
|
56
65
|
return wrapped;
|
|
57
66
|
};
|
|
58
|
-
return chain([]);
|
|
67
|
+
return chain([], {});
|
|
59
68
|
}
|
|
60
69
|
function createRequest(options) {
|
|
61
70
|
const requestInterceptors = [];
|
|
@@ -117,6 +126,7 @@ function createRequest(options) {
|
|
|
117
126
|
get: (url, config) => request({ ...config, url, method: "GET" }),
|
|
118
127
|
post: (url, data, config) => request({ ...config, url, data, method: "POST" }),
|
|
119
128
|
put: (url, data, config) => request({ ...config, url, data, method: "PUT" }),
|
|
129
|
+
patch: (url, data, config) => request({ ...config, url, data, method: "PATCH" }),
|
|
120
130
|
delete: (url, config) => request({ ...config, url, method: "DELETE" }),
|
|
121
131
|
interceptors: {
|
|
122
132
|
request: {
|
|
@@ -143,4 +153,4 @@ export {
|
|
|
143
153
|
defineAdapter,
|
|
144
154
|
createRequest
|
|
145
155
|
};
|
|
146
|
-
//# sourceMappingURL=chunk-
|
|
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":[]}
|
package/dist/core.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ interface RequestInstance {
|
|
|
12
12
|
get<T = unknown>(url: string, config?: Omit<RequestConfig, 'url' | 'method'>): Promise<RawResponse<T>>;
|
|
13
13
|
post<T = unknown>(url: string, data?: unknown, config?: Omit<RequestConfig, 'url' | 'method' | 'data'>): Promise<RawResponse<T>>;
|
|
14
14
|
put<T = unknown>(url: string, data?: unknown, config?: Omit<RequestConfig, 'url' | 'method' | 'data'>): Promise<RawResponse<T>>;
|
|
15
|
+
patch<T = unknown>(url: string, data?: unknown, config?: Omit<RequestConfig, 'url' | 'method' | 'data'>): Promise<RawResponse<T>>;
|
|
15
16
|
delete<T = unknown>(url: string, config?: Omit<RequestConfig, 'url' | 'method'>): Promise<RawResponse<T>>;
|
|
16
17
|
interceptors: {
|
|
17
18
|
request: {
|
|
@@ -47,6 +48,10 @@ declare function buildUrl(baseURL: string | undefined, url: string, params?: Rec
|
|
|
47
48
|
* // 返回 RawResponse;取消时抛 name='AbortError' 的错误
|
|
48
49
|
* }).use(withRetry()).use(withDedupe()) // 注册顺序 = 请求流经顺序
|
|
49
50
|
*
|
|
51
|
+
* 平台特有方法(如 upload/downloadFile)用 .extend 附加:
|
|
52
|
+
* .extend((request) => ({ upload: (file) => ... }))
|
|
53
|
+
* 工厂收到"组合后的适配器",附加方法可路由完整管道,并在后续 .use 链上保持不丢。
|
|
54
|
+
*
|
|
50
55
|
* 返回值本身仍是合法的 RequestAdapter,可直接传给 createRequest。
|
|
51
56
|
*/
|
|
52
57
|
declare function defineAdapter(adapter: RequestAdapter): ChainableAdapter;
|
package/dist/core.js
CHANGED
package/dist/errors.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { f as RequestError, g as RequestErrorKind, h as RequestErrorOptions, l as createAbortError, m as isAbortError } from './types-
|
|
1
|
+
export { f as RequestError, g as RequestErrorKind, h as RequestErrorOptions, l as createAbortError, m as isAbortError } from './types-DSmcNU2O.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +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-
|
|
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-DSmcNU2O.js';
|
|
2
2
|
export { CreateRequestOptions, RequestInstance, buildUrl, createRequest, defineAdapter } from './core.js';
|
package/dist/index.js
CHANGED
|
@@ -2,4 +2,4 @@ export { AuthInterceptorOptions, createAuthInterceptor } from './auth.js';
|
|
|
2
2
|
export { UnwrapInterceptorOptions, createUnwrapInterceptor } from './unwrap.js';
|
|
3
3
|
export { LogFn, LogLevel, LoggerInterceptorsOptions, createLoggerInterceptors } from './logger.js';
|
|
4
4
|
export { RequestIdInterceptorOptions, createRequestIdInterceptor } from './request-id.js';
|
|
5
|
-
import '../types-
|
|
5
|
+
import '../types-DSmcNU2O.js';
|
|
@@ -111,13 +111,22 @@ type RequestAdapter = (config: RequestConfig) => Promise<RawResponse>;
|
|
|
111
111
|
/** adapter 装饰器:输入 adapter,输出增强后的 adapter(retry/dedupe 以该形态参与 .use 组合) */
|
|
112
112
|
type AdapterEnhancer = (adapter: RequestAdapter) => RequestAdapter;
|
|
113
113
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
114
|
+
* 可链式增强的适配器:本身可直接当 RequestAdapter 用。
|
|
115
|
+
* - use:叠加装饰器(注册顺序 = 请求流经顺序)
|
|
116
|
+
* - extend:附加平台特有方法(如 upload / downloadFile)。工厂收到"组合后的适配器",
|
|
117
|
+
* 附加方法可路由完整管道(拦截器/装饰器);附加方法在后续 .use 链上保持不丢
|
|
118
|
+
*
|
|
119
|
+
* T 为已附加方法的类型形状(defineAdapter 默认为空)。
|
|
116
120
|
*/
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
use: (enhancer: AdapterEnhancer) => ChainableAdapter
|
|
120
|
-
|
|
121
|
+
type ChainableAdapter<T extends object = object> = RequestAdapter & T & {
|
|
122
|
+
/** 在外层叠加一个装饰器,返回新的链式适配器(原适配器不被修改) */
|
|
123
|
+
use: (enhancer: AdapterEnhancer) => ChainableAdapter<T>;
|
|
124
|
+
/**
|
|
125
|
+
* 附加平台特有方法。工厂以当前已组合的适配器执行一次——
|
|
126
|
+
* 附加方法天然带上此前注册的装饰器;之后的 .use 不影响已附加方法(先 use 后 extend)。
|
|
127
|
+
*/
|
|
128
|
+
extend: <U extends object>(factory: (composed: RequestAdapter) => U) => ChainableAdapter<T & U>;
|
|
129
|
+
};
|
|
121
130
|
/** 请求拦截器:返回新 config(可异步) */
|
|
122
131
|
type RequestInterceptor = (config: RequestConfig) => RequestConfig | Promise<RequestConfig>;
|
|
123
132
|
/** 响应拦截器(成功链):转换 / 剥壳 */
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
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 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 * 返回值本身仍是合法的 RequestAdapter,可直接传给 createRequest。\n */\nexport function defineAdapter(adapter: RequestAdapter): ChainableAdapter {\n const chain = (enhancers: AdapterEnhancer[]): ChainableAdapter => {\n let composed: RequestAdapter | undefined\n const wrapped = ((config: RequestConfig) => {\n // 惰性组合并缓存:装饰器工厂只在首次请求时执行一次,.use 不触发重建\n if (composed === undefined) {\n composed = enhancers.reduceRight((acc, enhance) => enhance(acc), adapter)\n }\n return composed(config)\n }) as ChainableAdapter\n wrapped.use = (enhancer) => chain([...enhancers, enhancer])\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 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":[]}
|