fzkit 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.
@@ -0,0 +1,265 @@
1
+ import { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
2
+ //#region src/http-client/types/token.d.ts
3
+ /**
4
+ * token 详细信息,用于主动刷新判断。
5
+ */
6
+ interface AccessTokenDetail {
7
+ token: string;
8
+ expiresAt: Date | string | number;
9
+ }
10
+ /**
11
+ * getAccessToken 的返回值类型,兼容旧版纯字符串返回。
12
+ */
13
+ type AccessTokenResult = string | AccessTokenDetail | null;
14
+ //#endregion
15
+ //#region src/http-client/token-refresh-manager.d.ts
16
+ /** @internal 冷却期内跳过刷新时的哨兵值,不对业务侧开放。 */
17
+ declare const REFRESH_SKIPPED: unique symbol;
18
+ declare class TokenRefreshManager {
19
+ private refreshingPromise;
20
+ private lastRefreshTime;
21
+ private readonly cooldownMs;
22
+ constructor(cooldownMs?: number);
23
+ /** @internal 供 createHttpClient 内部调用。 */
24
+ runRefresh(task: () => Promise<AccessTokenResult>): Promise<AccessTokenResult | typeof REFRESH_SKIPPED>;
25
+ }
26
+ //#endregion
27
+ //#region src/http-client/types/common.d.ts
28
+ /**
29
+ * onBusinessResponse 的返回值类型。
30
+ * - void:继续正常流程(表示成功)
31
+ * - Error:抛出错误(表示业务失败)
32
+ * - AxiosResponse:用完整响应形态(status/data/headers/config/statusText)替换原响应,不会二次触发 onBusinessResponse
33
+ */
34
+ type BusinessResponseResult = void | Error | AxiosResponse;
35
+ /**
36
+ * 错误上下文,传递给 onError 钩子。
37
+ */
38
+ interface ErrorContext {
39
+ /**
40
+ * 错误来源类型。
41
+ */
42
+ type: 'request' | 'refresh';
43
+ }
44
+ /**
45
+ * 自定义错误消息。
46
+ */
47
+ interface ErrorMessages {
48
+ refreshTokenExpired?: string;
49
+ loginExpired?: string;
50
+ }
51
+ //#endregion
52
+ //#region src/http-client/types/http-client-options.d.ts
53
+ /**
54
+ * 请求合并配置。
55
+ */
56
+ interface DedupePolicy {
57
+ /** 是否启用请求合并。默认 false。仅合并 GET 请求。 */
58
+ enabled?: boolean;
59
+ /**
60
+ * 自定义合并 key 生成器。
61
+ * 默认:`method:baseURL:url:stableParams:headersProviderSnapshot`
62
+ *
63
+ * 默认实现仅纳入 `headersProvider()` 返回值快照(`config.__dedupeProviderHeaders`),
64
+ * 不纳入调用方 `config.headers` 或工厂注入的 token。
65
+ * params 稳定序列化支持 plain object / array / URLSearchParams / Date / Map / Set;
66
+ * 循环引用会抛错,避免误合并。
67
+ */
68
+ generateKey?: (config: AxiosRequestConfig) => string;
69
+ }
70
+ /**
71
+ * 请求级合并配置。
72
+ * 仅允许覆盖 enabled;generateKey 只能在客户端级配置。
73
+ */
74
+ interface RequestDedupePolicy {
75
+ /** 是否启用请求合并。覆盖客户端级 enabled。 */
76
+ enabled?: boolean;
77
+ }
78
+ declare module 'axios' {
79
+ interface AxiosRequestConfig {
80
+ /**
81
+ * 请求级合并策略。
82
+ * 仅可覆盖 enabled,不能修改 generateKey。
83
+ * 仅 createHttpClient 创建的实例生效。
84
+ */
85
+ dedupePolicy?: RequestDedupePolicy;
86
+ }
87
+ }
88
+ /**
89
+ * 重试策略配置。
90
+ */
91
+ interface RetryPolicy {
92
+ /** 最大重试次数(不含首次请求)。默认 0(不重试)。 */
93
+ maxRetries?: number;
94
+ /**
95
+ * 判断是否需要重试。
96
+ * 默认:网络错误(无 response)或 5xx 状态码时重试;用户取消不重试。
97
+ */
98
+ shouldRetry?: (error: unknown, retryCount: number) => boolean;
99
+ /**
100
+ * 计算重试延迟(毫秒)。
101
+ * 默认:指数退避,公式 `1000 * 2^retryCount`,上限 30 秒。
102
+ * 返回非有限数(NaN/Infinity)或负数时,按 0ms 处理。
103
+ * shouldRetry / retryDelay 抛错会停止重试,并进入 onError({ type: "request" })。
104
+ */
105
+ retryDelay?: (retryCount: number) => number;
106
+ }
107
+ /**
108
+ * 创建 HTTP 客户端时可传入的配置。
109
+ *
110
+ * @typeParam T - getAccessToken / refreshAccessToken 的统一返回类型,
111
+ * 默认为 `AccessTokenResult`。当 T 为 `AccessTokenDetail` 时启用主动刷新。
112
+ */
113
+ interface HttpClientOptions<T extends AccessTokenResult = AccessTokenResult> {
114
+ /**
115
+ * 透传给 axios.create 的初始化配置。
116
+ */
117
+ axiosConfig: AxiosRequestConfig;
118
+ /**
119
+ * 获取当前 access token。
120
+ *
121
+ * 返回 `string` 时仅注入 token,不做主动刷新判断。
122
+ * 返回 `AccessTokenDetail` 时,会在 token 即将过期前主动触发刷新。
123
+ */
124
+ getAccessToken: () => T | Promise<T>;
125
+ /**
126
+ * access token 注入到请求头时使用的字段名。
127
+ * 默认 `Authorization`。
128
+ */
129
+ accessTokenHeaderName?: string;
130
+ /**
131
+ * access token 的前缀。
132
+ * 默认 `Bearer`。
133
+ */
134
+ accessTokenPrefix?: string;
135
+ /**
136
+ * 刷新失败判定的业务 code 列表。
137
+ *
138
+ * 仅用于 `defaultIsRefreshFailure`:当 refresh 请求响应体存在 `data.code`
139
+ * 且命中该列表时,视为刷新鉴权失败。
140
+ *
141
+ * 不会影响普通业务请求的鉴权识别;更通用的入口是 `isRefreshFailure`。
142
+ * 默认假设响应体形状为 `{ code?: number }`。
143
+ */
144
+ refreshFailureCodes?: number[];
145
+ /**
146
+ * 通用重试策略。
147
+ * 默认不重试(maxRetries: 0)。
148
+ */
149
+ retryPolicy?: RetryPolicy;
150
+ /**
151
+ * 请求合并策略。
152
+ * 默认关闭(enabled: false)。
153
+ */
154
+ dedupePolicy?: DedupePolicy;
155
+ /**
156
+ * 运行时动态 headers 提供者。
157
+ *
158
+ * 每次请求时调用,返回的 headers 会合并到请求中。
159
+ * 支持同步或异步返回。
160
+ *
161
+ * 典型场景:
162
+ * - 请求追踪:`{ 'x-trace-id': crypto.randomUUID() }`
163
+ * - 业务标识:从 store 读取 `{ 'x-tenant-id': tenantId }`
164
+ *
165
+ * 注意:
166
+ * - 首次请求时,返回的 headers 会覆盖默认注入的 headers(如 Authorization)
167
+ * - 鉴权刷新后的重试请求中,新 token 优先,不会被 headersProvider 的旧 Authorization 覆盖
168
+ */
169
+ headersProvider?: () => Record<string, string> | Promise<Record<string, string>>;
170
+ /**
171
+ * 触发刷新流程的 HTTP 状态码。
172
+ * 默认 `401`。
173
+ */
174
+ unauthorizedStatusCode?: number;
175
+ /**
176
+ * 自定义错误消息,覆盖内部默认值。
177
+ */
178
+ errorMessages?: ErrorMessages;
179
+ /**
180
+ * 判断刷新 token 请求本身是否已经失败到需要退出登录。
181
+ *
182
+ * 默认行为:
183
+ * - 非 AxiosError(编程错误 / 业务自定义 Error)不视为刷新鉴权失败
184
+ * - AxiosError 无 response(网络错误)或状态码 >= 500 时不视为刷新失败
185
+ * - 状态码 === unauthorizedStatusCode 时视为刷新失败
186
+ * - 响应 data.code 在 refreshFailureCodes 列表中时视为刷新鉴权失败
187
+ *
188
+ * 若业务需要“refresh 抛 Error 即登出”,请自定义该函数。
189
+ */
190
+ isRefreshFailure?: (error: unknown) => boolean;
191
+ /**
192
+ * 自定义刷新逻辑,返回类型必须与 getAccessToken 一致。
193
+ *
194
+ * 库不缓存 token。若返回 AccessTokenDetail,业务必须把新 token / expiresAt
195
+ * 写回 getAccessToken 使用的数据源,后续请求才会读到新过期时间。
196
+ */
197
+ refreshAccessToken?: () => T | Promise<T>;
198
+ /**
199
+ * 不触发 refresh token 流程的请求路径列表。
200
+ *
201
+ * 仅 exact / prefix 匹配,不是任意子串 includes,也不做中间段滑动匹配。
202
+ * 例如配置 `/auth` 会匹配 `/auth`、`/auth/login`,
203
+ * 但不会匹配 `/user/auth-history`、`/authorization`、`/api/auth/login`。
204
+ */
205
+ skipRefreshUrls?: string[];
206
+ /**
207
+ * 提前刷新的毫秒数,默认 0(不提前刷新)。
208
+ * 设置为大于 0 的值时,会在 token 即将过期前主动触发刷新。
209
+ */
210
+ refreshBufferMs?: number;
211
+ /**
212
+ * 刷新 token 后的冷却期(毫秒)。
213
+ * 在冷却期内收到的 401 请求会跳过刷新,直接使用新 token 重试。
214
+ * 用于处理刷新完成后,旧请求陆续返回 401 的并发场景。
215
+ * 默认 15000(15 秒)。
216
+ */
217
+ refreshCooldownMs?: number;
218
+ /**
219
+ * 外部传入的 TokenRefreshManager 实例。
220
+ * 用于多个客户端共享同一鉴权域的 refresh / 冷却状态。
221
+ * 不传则自动创建独立实例。
222
+ *
223
+ * 共享 manager 时:
224
+ * - 一次合并 refresh 事务的鉴权失败只会触发一次 onAuthFailure
225
+ * - 使用发起该次 refresh 的 client 回调
226
+ * - 建议共享 client 使用同一 token 数据源与等价 logout 行为
227
+ * - 外部 manager 的 cooldown 由构造参数决定;client 的 refreshCooldownMs 不会回写已有 manager
228
+ */
229
+ refreshManager?: TokenRefreshManager;
230
+ /**
231
+ * 通过业务响应判断是否需要刷新 token。
232
+ */
233
+ shouldRefreshByResponse?: (response: AxiosResponse<unknown>) => boolean;
234
+ /**
235
+ * 登录失效后的统一收尾回调。
236
+ */
237
+ onAuthFailure?: (error?: unknown) => void | Promise<void>;
238
+ /**
239
+ * 业务响应拦截器。
240
+ * - 返回 void:继续正常流程(表示成功)
241
+ * - 返回 Error:抛出错误(表示业务失败)
242
+ * - throw Error / 非 Error:与返回 Error 一样,统一进入 onError({ type: "request" })
243
+ * - 返回完整 AxiosResponse 形态:用新响应替换原响应,不会二次触发 onBusinessResponse
244
+ * - 可以是 async
245
+ */
246
+ onBusinessResponse?: (response: AxiosResponse<unknown>) => BusinessResponseResult | Promise<BusinessResponseResult>;
247
+ /**
248
+ * 全局错误钩子。
249
+ * - 返回 Error:用新错误替换原错误
250
+ * - 返回 void:继续抛出原错误
251
+ * - 可以是 async
252
+ *
253
+ * error 类型为 AxiosError | Error:
254
+ * - 请求失败时为 AxiosError,可通过 axios.isAxiosError(error) 收敛访问 response/config
255
+ * - 刷新失败时可能为 AxiosError 或业务侧抛出的任意 Error
256
+ * - 登录过期等内部构造的错误为普通 Error
257
+ */
258
+ onError?: (error: AxiosError | Error, context: ErrorContext) => AxiosError | Error | void | Promise<AxiosError | Error | void>;
259
+ }
260
+ //#endregion
261
+ //#region src/http-client/create-http-client.d.ts
262
+ declare const createHttpClient: <T extends AccessTokenResult = AccessTokenResult>(options: HttpClientOptions<T>) => AxiosInstance;
263
+ //#endregion
264
+ export { RetryPolicy as a, ErrorMessages as c, AccessTokenResult as d, RequestDedupePolicy as i, TokenRefreshManager as l, DedupePolicy as n, BusinessResponseResult as o, HttpClientOptions as r, ErrorContext as s, createHttpClient as t, AccessTokenDetail as u };
265
+ //# sourceMappingURL=create-http-client-BoM9-_Ty.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-http-client-BoM9-_Ty.d.mts","names":[],"sources":["../src/http-client/types/token.ts","../src/http-client/token-refresh-manager.ts","../src/http-client/types/common.ts","../src/http-client/types/http-client-options.ts","../src/http-client/create-http-client.ts"],"mappings":";;;;;UAGiB;EACf;EACA,WAAW;;;;;KAMD,6BAA6B;;;;cCR5B;cAEA;UACH;UACA;mBACS;cAEL;;EAKN,WACJ,YAAY,QAAQ,qBACnB,QAAQ,2BAA2B;;;;;;;;;;KCe5B,gCAAgC,QAAQ;;;;UAKnC;;;;EAIf;;;;;UAMe;EACf;EACA;;;;;;;UCzCe;;EAEf;;;;;;;;;;EAWA,eAAe,QAAQ;;;;;;UAOR;;EAEf;;;YAMU;;;;;;IAMR,eAAe;;;;;;UAOF;;EAEf;;;;;EAMA,eAAe,gBAAgB;;;;;;;EAQ/B,cAAc;;;;;;;;UASC,kBAAkB,UAAU,oBAAoB;;;;EAI/D,aAAa;;;;;;;EAUb,sBAAsB,IAAI,QAAQ;;;;;EAMlC;;;;;EAMA;;;;;;;;;;EAaA;;;;;EAMA,cAAc;;;;;EAMd,eAAe;;;;;;;;;;;;;;;EAgBf,wBAAwB,yBAAyB,QAAQ;;;;;EAMzD;;;;EAKA,gBAAgB;;;;;;;;;;;;EAahB,oBAAoB;;;;;;;EAUpB,2BAA2B,IAAI,QAAQ;;;;;;;;EASvC;;;;;EAMA;;;;;;;EAQA;;;;;;;;;;;;EAaA,iBAAiB;;;;EAKjB,2BAA2B,UAAU;;;;EAOrC,iBAAiB,2BAA2B;;;;;;;;;EAU5C,sBACE,UAAU,2BACP,yBAAyB,QAAQ;;;;;;;;;;;;EAatC,WACE,OAAO,aAAa,OACpB,SAAS,iBACN,aAAa,eAAe,QAAQ,aAAa;;;;cC7O3C,mBAAoB,UAAU,oBAAoB,mBAC7D,SAAS,kBAAkB,OAC1B"}