xxf_react 0.7.3 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/http/api/ApiBuilder.d.ts +114 -211
- package/dist/http/api/ApiBuilder.d.ts.map +1 -1
- package/dist/http/api/ApiBuilder.js +39 -149
- package/dist/http/api/index.d.ts +1 -1
- package/dist/http/api/index.d.ts.map +1 -1
- package/dist/http/api/index.js +1 -1
- package/dist/http/demo/api-builder.demo.d.ts +53 -0
- package/dist/http/demo/api-builder.demo.d.ts.map +1 -0
- package/dist/http/demo/api-builder.demo.js +163 -0
- package/package.json +1 -1
|
@@ -8,110 +8,104 @@
|
|
|
8
8
|
*
|
|
9
9
|
* - **端点名称字面量**:每个端点名称保留字面量类型(如 `'getUser'`)
|
|
10
10
|
* - **泛型累积**:链式调用累积所有端点类型 `T & Record<K, ApiEndpoint<...>>`
|
|
11
|
-
* - **路径参数推断**:从 path 字符串自动提取 `{param}` 并约束 `pathParams` 类型
|
|
12
11
|
* - **Body 类型约束**:POST/PUT/PATCH 支持指定请求体类型
|
|
13
|
-
* - **防止重复端点**:添加已存在的端点名称会编译报错
|
|
14
12
|
*
|
|
15
13
|
* @example
|
|
16
14
|
* ```ts
|
|
17
15
|
* interface CreateUserDTO { name: string; email: string }
|
|
18
16
|
*
|
|
19
|
-
* // 定义 API
|
|
20
17
|
* const userApi = ApiBuilder.create({
|
|
21
18
|
* baseUrl: 'https://api.example.com',
|
|
22
19
|
* })
|
|
23
|
-
* .get<User>('getUser', {
|
|
24
|
-
* path: '/users/{id}', // pathParams 自动约束为 { id: string | number }
|
|
25
|
-
* cache: { mode: CacheMode.IfCache, ttl: 60000 },
|
|
26
|
-
* })
|
|
20
|
+
* .get<User>('getUser', { path: '/users/{id}' })
|
|
27
21
|
* .get<User[]>('getUsers', { path: '/users' })
|
|
28
|
-
* .post<User, CreateUserDTO>('createUser', { path: '/users' })
|
|
22
|
+
* .post<User, CreateUserDTO>('createUser', { path: '/users' })
|
|
29
23
|
* .build()
|
|
30
24
|
*
|
|
31
25
|
* // ✅ 类型正确推断
|
|
32
|
-
* userApi.getUser({ pathParams: { id: '123' } })
|
|
33
|
-
* userApi.
|
|
34
|
-
* userApi.createUser({ body: { name: 'John', email: 'x' } }) // body 类型检查
|
|
26
|
+
* userApi.getUser({ pathParams: { id: '123' } })
|
|
27
|
+
* userApi.createUser({ body: { name: 'John', email: 'x' } })
|
|
35
28
|
*
|
|
36
29
|
* // ❌ 类型错误会被捕获
|
|
37
|
-
* userApi.
|
|
38
|
-
* userApi.createUser({ body: { wrong: 'x' } }) // Error: body 类型不匹配
|
|
39
|
-
* userApi.unknownMethod() // Error: 方法不存在
|
|
30
|
+
* userApi.unknownMethod() // Error: 方法不存在
|
|
40
31
|
* ```
|
|
41
32
|
*/
|
|
42
33
|
import ky, { type Options as KyOptions } from 'ky';
|
|
43
34
|
import { HttpClient } from '../client/HttpClient';
|
|
44
35
|
import { ApiStream } from '../client/ApiStream';
|
|
45
|
-
import type { RequestConfig, CacheConfig, HttpClientConfig } from '../types';
|
|
36
|
+
import type { RequestConfig, ApiOptions, CacheConfig, HttpClientConfig } from '../types';
|
|
46
37
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* ExtractPathParams<'/users/{id}'> = 'id'
|
|
51
|
-
* ExtractPathParams<'/users/{userId}/posts/{postId}'> = 'userId' | 'postId'
|
|
52
|
-
* ExtractPathParams<'/users'> = never
|
|
38
|
+
* 基础 API 调用选项(不含 body)
|
|
53
39
|
*/
|
|
54
|
-
type
|
|
40
|
+
type BaseApiOptions = Omit<ApiOptions, 'body'>;
|
|
55
41
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* @example
|
|
59
|
-
* PathParamsType<'/users/{id}'> = { id: string | number }
|
|
60
|
-
* PathParamsType<'/users'> = undefined (无需路径参数)
|
|
42
|
+
* 带 body 的 API 调用选项(body 必需)
|
|
61
43
|
*/
|
|
62
|
-
type
|
|
44
|
+
type WithBodyRequired<TBody> = BaseApiOptions & {
|
|
45
|
+
body: TBody;
|
|
46
|
+
};
|
|
63
47
|
/**
|
|
64
|
-
*
|
|
48
|
+
* 带 body 的 API 调用选项(body 可选)
|
|
65
49
|
*/
|
|
66
|
-
type
|
|
50
|
+
type WithBodyOptional = BaseApiOptions & {
|
|
51
|
+
body?: unknown;
|
|
52
|
+
};
|
|
67
53
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* @template Path 路径字符串字面量
|
|
71
|
-
* @template TBody 请求体类型(用于 POST/PUT/PATCH)
|
|
72
|
-
* @template HasBody 是否需要 body
|
|
54
|
+
* 无 body 的 API 调用选项(GET/DELETE)
|
|
73
55
|
*/
|
|
74
|
-
type
|
|
75
|
-
/** 查询参数,会自动过滤 undefined 和 null */
|
|
76
|
-
query?: Record<string, string | number | boolean | undefined>;
|
|
77
|
-
/** 覆盖缓存配置(优先级最高) */
|
|
78
|
-
cache?: Partial<CacheConfig>;
|
|
79
|
-
/** 覆盖请求头 */
|
|
80
|
-
headers?: Record<string, string>;
|
|
81
|
-
} & (HasPathParams<Path> extends true ? {
|
|
82
|
-
pathParams: PathParamsType<Path>;
|
|
83
|
-
} : {
|
|
84
|
-
pathParams?: undefined;
|
|
85
|
-
}) & (HasBody extends true ? {
|
|
86
|
-
body?: TBody;
|
|
87
|
-
} : {
|
|
56
|
+
type WithoutBodyOptions = BaseApiOptions & {
|
|
88
57
|
body?: never;
|
|
89
|
-
}
|
|
58
|
+
};
|
|
90
59
|
/**
|
|
91
|
-
* API
|
|
92
|
-
*
|
|
93
|
-
* @template TResponse 响应数据类型
|
|
94
|
-
* @template Path 路径字符串字面量
|
|
95
|
-
* @template TBody 请求体类型
|
|
96
|
-
* @template HasBody 是否需要 body
|
|
60
|
+
* API 端点函数类型(有 body 且必需 - POST/PUT/PATCH 指定了 TBody)
|
|
97
61
|
*/
|
|
98
|
-
export type
|
|
99
|
-
(options
|
|
62
|
+
export type ApiEndpointWithBodyRequired<TResponse, TBody> = {
|
|
63
|
+
(options: WithBodyRequired<TBody>): ApiStream<TResponse>;
|
|
100
64
|
/** 请求配置 */
|
|
101
65
|
config: RequestConfig;
|
|
102
66
|
};
|
|
67
|
+
/**
|
|
68
|
+
* API 端点函数类型(有 body 但可选 - POST/PUT/PATCH 未指定 TBody)
|
|
69
|
+
*/
|
|
70
|
+
export type ApiEndpointWithBodyOptional<TResponse> = {
|
|
71
|
+
(options?: WithBodyOptional): ApiStream<TResponse>;
|
|
72
|
+
/** 请求配置 */
|
|
73
|
+
config: RequestConfig;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* API 端点函数类型(无 body - GET/DELETE)
|
|
77
|
+
*/
|
|
78
|
+
export type ApiEndpointWithoutBody<TResponse> = {
|
|
79
|
+
(options?: WithoutBodyOptions): ApiStream<TResponse>;
|
|
80
|
+
/** 请求配置 */
|
|
81
|
+
config: RequestConfig;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* 检测是否为 unknown 类型
|
|
85
|
+
*/
|
|
86
|
+
type IsUnknown<T> = [unknown] extends [T] ? ([T] extends [unknown] ? true : false) : false;
|
|
87
|
+
/**
|
|
88
|
+
* 检测是否为 void 类型
|
|
89
|
+
*/
|
|
90
|
+
type IsVoid<T> = [T] extends [void] ? ([void] extends [T] ? true : false) : false;
|
|
91
|
+
/**
|
|
92
|
+
* 有 body 的端点类型(根据 TBody 选择必需或可选)
|
|
93
|
+
*/
|
|
94
|
+
export type ApiEndpointWithBody<TResponse, TBody> = IsUnknown<TBody> extends true ? ApiEndpointWithBodyOptional<TResponse> : IsVoid<TBody> extends true ? ApiEndpointWithBodyOptional<TResponse> : ApiEndpointWithBodyRequired<TResponse, TBody>;
|
|
95
|
+
/**
|
|
96
|
+
* API 端点函数类型(泛型版本,用于内部和类型导出)
|
|
97
|
+
*/
|
|
98
|
+
export type ApiEndpoint<TResponse, TBody = unknown, HasBody extends boolean = false> = HasBody extends true ? ApiEndpointWithBody<TResponse, TBody> : ApiEndpointWithoutBody<TResponse>;
|
|
103
99
|
/**
|
|
104
100
|
* API 定义类型(端点集合)
|
|
105
101
|
*/
|
|
106
|
-
export type ApiDefinition = Record<string, ApiEndpoint<unknown,
|
|
102
|
+
export type ApiDefinition = Record<string, ApiEndpoint<unknown, unknown, boolean>>;
|
|
107
103
|
/**
|
|
108
104
|
* 端点选项(构建时配置)
|
|
109
|
-
*
|
|
110
|
-
* @template Path 路径字符串字面量,用于类型推断
|
|
111
105
|
*/
|
|
112
|
-
interface EndpointOptions
|
|
106
|
+
interface EndpointOptions {
|
|
113
107
|
/** 请求路径,支持 {param} 占位符 */
|
|
114
|
-
path:
|
|
108
|
+
path: string;
|
|
115
109
|
/** 请求方法 */
|
|
116
110
|
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
117
111
|
/** 请求头 */
|
|
@@ -123,12 +117,39 @@ interface EndpointOptions<Path extends string = string> {
|
|
|
123
117
|
/** 缓存配置 */
|
|
124
118
|
cache?: CacheConfig;
|
|
125
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* 空 API 类型(空记录,不允许任意属性访问)
|
|
122
|
+
*/
|
|
123
|
+
type EmptyApi = {};
|
|
126
124
|
/**
|
|
127
125
|
* API 构建器类
|
|
128
126
|
*
|
|
127
|
+
* 使用链式调用定义类型安全的 API 端点。
|
|
128
|
+
*
|
|
129
129
|
* @template T 当前已定义的端点类型集合,通过链式调用累积
|
|
130
|
+
*
|
|
131
|
+
* @remarks
|
|
132
|
+
* **类型深度限制**:由于 TypeScript 对深层交叉类型的处理限制,
|
|
133
|
+
* 当单个 builder 链式调用超过 4 个端点时,类型推断可能不够精确。
|
|
134
|
+
*
|
|
135
|
+
* 建议:
|
|
136
|
+
* - 保持单个 builder 的端点数量在 4 个以内
|
|
137
|
+
* - 如需更多端点,功能仍然正常,但部分类型检查可能被放宽
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* const userApi = ApiBuilder.create({ baseUrl: '/api' })
|
|
142
|
+
* .get<User>('getUser', { path: '/users/{id}' })
|
|
143
|
+
* .post<User, CreateUserDTO>('createUser', { path: '/users' })
|
|
144
|
+
* .put<User, UpdateUserDTO>('updateUser', { path: '/users/{id}' })
|
|
145
|
+
* .build()
|
|
146
|
+
*
|
|
147
|
+
* // 类型安全调用
|
|
148
|
+
* const user = await userApi.getUser({ pathParams: { id: '1' } })
|
|
149
|
+
* const newUser = await userApi.createUser({ body: { name: 'John', email: 'j@test.com' } })
|
|
150
|
+
* ```
|
|
130
151
|
*/
|
|
131
|
-
export declare class ApiBuilder<T extends
|
|
152
|
+
export declare class ApiBuilder<T extends object = EmptyApi> {
|
|
132
153
|
private client;
|
|
133
154
|
private endpoints;
|
|
134
155
|
private constructor();
|
|
@@ -137,203 +158,85 @@ export declare class ApiBuilder<T extends ApiDefinition = Record<string, never>>
|
|
|
137
158
|
*
|
|
138
159
|
* @param config HTTP 客户端配置
|
|
139
160
|
* @returns 新的 ApiBuilder 实例
|
|
140
|
-
*
|
|
141
|
-
* @example
|
|
142
|
-
* ```ts
|
|
143
|
-
* const api = ApiBuilder.create({
|
|
144
|
-
* baseUrl: 'https://api.example.com',
|
|
145
|
-
* timeout: 10000,
|
|
146
|
-
* })
|
|
147
|
-
* ```
|
|
148
161
|
*/
|
|
149
|
-
static create(config: HttpClientConfig): ApiBuilder
|
|
162
|
+
static create(config: HttpClientConfig): ApiBuilder<object>;
|
|
150
163
|
/**
|
|
151
164
|
* 从现有 ky 实例创建
|
|
152
165
|
*
|
|
153
166
|
* @param kyInstance 已配置的 ky 实例
|
|
154
167
|
* @param config 可选的额外配置
|
|
155
168
|
* @returns 新的 ApiBuilder 实例
|
|
156
|
-
*
|
|
157
|
-
* @example
|
|
158
|
-
* ```ts
|
|
159
|
-
* const kyInstance = ky.create({ prefixUrl: 'https://api.example.com' })
|
|
160
|
-
* const api = ApiBuilder.from(kyInstance)
|
|
161
|
-
* ```
|
|
162
169
|
*/
|
|
163
|
-
static from(kyInstance: ReturnType<typeof ky.create>, config?: Partial<HttpClientConfig>): ApiBuilder
|
|
170
|
+
static from(kyInstance: ReturnType<typeof ky.create>, config?: Partial<HttpClientConfig>): ApiBuilder<object>;
|
|
164
171
|
/**
|
|
165
172
|
* 定义 GET 请求
|
|
166
173
|
*
|
|
167
174
|
* @template TResponse 响应数据类型
|
|
168
|
-
* @template K
|
|
169
|
-
* @template Path 路径字符串(自动推断,用于提取路径参数)
|
|
170
|
-
*
|
|
171
|
-
* @param name 端点名称
|
|
172
|
-
* @param options 端点配置
|
|
173
|
-
* @returns 累积了新端点类型的 ApiBuilder
|
|
174
|
-
*
|
|
175
|
-
* @example
|
|
176
|
-
* ```ts
|
|
177
|
-
* .get<User>('getUser', {
|
|
178
|
-
* path: '/users/{id}', // pathParams 自动约束为 { id: string | number }
|
|
179
|
-
* cache: { mode: CacheMode.IfCache },
|
|
180
|
-
* })
|
|
181
|
-
* ```
|
|
175
|
+
* @template K 端点名称(自动推断)
|
|
182
176
|
*/
|
|
183
|
-
get<TResponse, K extends string = string
|
|
184
|
-
|
|
177
|
+
get<TResponse, const K extends string = string>(name: K, options: Omit<EndpointOptions, 'method'>): ApiBuilder<T & {
|
|
178
|
+
[P in K]: ApiEndpointWithoutBody<TResponse>;
|
|
179
|
+
}>;
|
|
185
180
|
/**
|
|
186
181
|
* 定义 POST 请求
|
|
187
182
|
*
|
|
188
183
|
* @template TResponse 响应数据类型
|
|
189
|
-
* @template TBody
|
|
190
|
-
* @template K
|
|
191
|
-
* @template Path 路径字符串(自动推断)
|
|
192
|
-
*
|
|
193
|
-
* @param name 端点名称
|
|
194
|
-
* @param options 端点配置
|
|
195
|
-
* @returns 累积了新端点类型的 ApiBuilder
|
|
196
|
-
*
|
|
197
|
-
* @example
|
|
198
|
-
* ```ts
|
|
199
|
-
* interface CreateUserDTO { name: string; email: string }
|
|
200
|
-
*
|
|
201
|
-
* .post<User, CreateUserDTO>('createUser', {
|
|
202
|
-
* path: '/users',
|
|
203
|
-
* })
|
|
204
|
-
* // body 类型约束为 CreateUserDTO
|
|
205
|
-
* ```
|
|
184
|
+
* @template TBody 请求体类型
|
|
185
|
+
* @template K 端点名称
|
|
206
186
|
*/
|
|
207
|
-
post<TResponse, TBody =
|
|
187
|
+
post<TResponse, TBody = void, const K extends string = string>(name: K, options: Omit<EndpointOptions, 'method'>): ApiBuilder<T & {
|
|
188
|
+
[P in K]: ApiEndpointWithBody<TResponse, TBody>;
|
|
189
|
+
}>;
|
|
208
190
|
/**
|
|
209
191
|
* 定义 PUT 请求
|
|
210
192
|
*
|
|
211
193
|
* @template TResponse 响应数据类型
|
|
212
|
-
* @template TBody
|
|
213
|
-
* @template K
|
|
214
|
-
* @template Path 路径字符串(自动推断)
|
|
215
|
-
*
|
|
216
|
-
* @param name 端点名称
|
|
217
|
-
* @param options 端点配置
|
|
218
|
-
* @returns 累积了新端点类型的 ApiBuilder
|
|
219
|
-
*
|
|
220
|
-
* @example
|
|
221
|
-
* ```ts
|
|
222
|
-
* .put<User, UpdateUserDTO>('updateUser', {
|
|
223
|
-
* path: '/users/{id}',
|
|
224
|
-
* })
|
|
225
|
-
* ```
|
|
194
|
+
* @template TBody 请求体类型
|
|
195
|
+
* @template K 端点名称
|
|
226
196
|
*/
|
|
227
|
-
put<TResponse, TBody =
|
|
197
|
+
put<TResponse, TBody = void, const K extends string = string>(name: K, options: Omit<EndpointOptions, 'method'>): ApiBuilder<T & {
|
|
198
|
+
[P in K]: ApiEndpointWithBody<TResponse, TBody>;
|
|
199
|
+
}>;
|
|
228
200
|
/**
|
|
229
201
|
* 定义 DELETE 请求
|
|
230
202
|
*
|
|
231
203
|
* @template TResponse 响应数据类型
|
|
232
|
-
* @template K
|
|
233
|
-
* @template Path 路径字符串(自动推断)
|
|
234
|
-
*
|
|
235
|
-
* @param name 端点名称
|
|
236
|
-
* @param options 端点配置
|
|
237
|
-
* @returns 累积了新端点类型的 ApiBuilder
|
|
238
|
-
*
|
|
239
|
-
* @example
|
|
240
|
-
* ```ts
|
|
241
|
-
* .delete<void>('deleteUser', {
|
|
242
|
-
* path: '/users/{id}',
|
|
243
|
-
* })
|
|
244
|
-
* ```
|
|
204
|
+
* @template K 端点名称
|
|
245
205
|
*/
|
|
246
|
-
delete<TResponse, K extends string = string
|
|
206
|
+
delete<TResponse, const K extends string = string>(name: K, options: Omit<EndpointOptions, 'method'>): ApiBuilder<T & {
|
|
207
|
+
[P in K]: ApiEndpointWithoutBody<TResponse>;
|
|
208
|
+
}>;
|
|
247
209
|
/**
|
|
248
210
|
* 定义 PATCH 请求
|
|
249
211
|
*
|
|
250
212
|
* @template TResponse 响应数据类型
|
|
251
|
-
* @template TBody
|
|
252
|
-
* @template K
|
|
253
|
-
* @template Path 路径字符串(自动推断)
|
|
254
|
-
*
|
|
255
|
-
* @param name 端点名称
|
|
256
|
-
* @param options 端点配置
|
|
257
|
-
* @returns 累积了新端点类型的 ApiBuilder
|
|
258
|
-
*
|
|
259
|
-
* @example
|
|
260
|
-
* ```ts
|
|
261
|
-
* .patch<User, Partial<UpdateUserDTO>>('patchUser', {
|
|
262
|
-
* path: '/users/{id}',
|
|
263
|
-
* })
|
|
264
|
-
* ```
|
|
213
|
+
* @template TBody 请求体类型
|
|
214
|
+
* @template K 端点名称
|
|
265
215
|
*/
|
|
266
|
-
patch<TResponse, TBody =
|
|
216
|
+
patch<TResponse, TBody = void, const K extends string = string>(name: K, options: Omit<EndpointOptions, 'method'>): ApiBuilder<T & {
|
|
217
|
+
[P in K]: ApiEndpointWithBody<TResponse, TBody>;
|
|
218
|
+
}>;
|
|
267
219
|
/**
|
|
268
220
|
* 通用端点定义(内部方法)
|
|
269
|
-
*
|
|
270
|
-
* @template TResponse 响应数据类型
|
|
271
|
-
* @template K 端点名称
|
|
272
|
-
* @template Path 路径字符串
|
|
273
|
-
* @template TBody 请求体类型
|
|
274
|
-
* @template HasBody 是否有请求体
|
|
275
221
|
*/
|
|
276
222
|
private endpoint;
|
|
277
223
|
/**
|
|
278
224
|
* 扩展 ky 实例(添加 hooks 等)
|
|
279
225
|
*
|
|
280
|
-
* @param options
|
|
281
|
-
* @returns
|
|
282
|
-
*
|
|
283
|
-
* @example
|
|
284
|
-
* ```ts
|
|
285
|
-
* .extend({
|
|
286
|
-
* hooks: {
|
|
287
|
-
* beforeRequest: [(req) => {
|
|
288
|
-
* req.headers.set('Authorization', `Bearer ${token}`)
|
|
289
|
-
* }],
|
|
290
|
-
* },
|
|
291
|
-
* })
|
|
292
|
-
* ```
|
|
226
|
+
* @param options Ky 配置选项
|
|
227
|
+
* @returns 当前 Builder 实例(保留累积的类型)
|
|
293
228
|
*/
|
|
294
|
-
extend(options: KyOptions):
|
|
229
|
+
extend(options: KyOptions): ApiBuilder<T>;
|
|
295
230
|
/**
|
|
296
231
|
* 构建 API 服务
|
|
297
232
|
*
|
|
298
|
-
* 将所有定义的端点转换为可调用的 API 对象。
|
|
299
|
-
* 每个端点都带有完整的类型信息(响应类型、路径参数、请求体类型)。
|
|
300
|
-
*
|
|
301
233
|
* @returns 类型安全的 API 对象
|
|
302
|
-
*
|
|
303
|
-
* @example
|
|
304
|
-
* ```ts
|
|
305
|
-
* const api = ApiBuilder.create({ baseUrl: '...' })
|
|
306
|
-
* .get<User>('getUser', { path: '/users/{id}' })
|
|
307
|
-
* .post<User, CreateUserDTO>('createUser', { path: '/users' })
|
|
308
|
-
* .build()
|
|
309
|
-
*
|
|
310
|
-
* // 类型安全调用
|
|
311
|
-
* await api.getUser({ pathParams: { id: '1' } })
|
|
312
|
-
* await api.createUser({ body: { name: 'John', email: 'x@x.com' } })
|
|
313
|
-
* ```
|
|
314
234
|
*/
|
|
315
235
|
build(): T;
|
|
316
236
|
/**
|
|
317
237
|
* 获取 HTTP 客户端实例(用于高级操作)
|
|
318
|
-
*
|
|
319
|
-
* @returns HttpClient 实例
|
|
320
|
-
*
|
|
321
|
-
* @example
|
|
322
|
-
* ```ts
|
|
323
|
-
* const client = api.getClient()
|
|
324
|
-
* await client.clearCache()
|
|
325
|
-
* ```
|
|
326
238
|
*/
|
|
327
239
|
getClient(): HttpClient;
|
|
328
240
|
}
|
|
329
|
-
|
|
330
|
-
* 从路径字符串中提取参数名称
|
|
331
|
-
*
|
|
332
|
-
* @example
|
|
333
|
-
* ```ts
|
|
334
|
-
* type Params = ExtractPathParams<'/users/{userId}/posts/{postId}'>
|
|
335
|
-
* // Params = 'userId' | 'postId'
|
|
336
|
-
* ```
|
|
337
|
-
*/
|
|
338
|
-
export type { ExtractPathParams, PathParamsType, HasPathParams };
|
|
241
|
+
export {};
|
|
339
242
|
//# sourceMappingURL=ApiBuilder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiBuilder.d.ts","sourceRoot":"","sources":["../../../src/http/api/ApiBuilder.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ApiBuilder.d.ts","sourceRoot":"","sources":["../../../src/http/api/ApiBuilder.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,45 +8,56 @@
|
|
|
8
8
|
*
|
|
9
9
|
* - **端点名称字面量**:每个端点名称保留字面量类型(如 `'getUser'`)
|
|
10
10
|
* - **泛型累积**:链式调用累积所有端点类型 `T & Record<K, ApiEndpoint<...>>`
|
|
11
|
-
* - **路径参数推断**:从 path 字符串自动提取 `{param}` 并约束 `pathParams` 类型
|
|
12
11
|
* - **Body 类型约束**:POST/PUT/PATCH 支持指定请求体类型
|
|
13
|
-
* - **防止重复端点**:添加已存在的端点名称会编译报错
|
|
14
12
|
*
|
|
15
13
|
* @example
|
|
16
14
|
* ```ts
|
|
17
15
|
* interface CreateUserDTO { name: string; email: string }
|
|
18
16
|
*
|
|
19
|
-
* // 定义 API
|
|
20
17
|
* const userApi = ApiBuilder.create({
|
|
21
18
|
* baseUrl: 'https://api.example.com',
|
|
22
19
|
* })
|
|
23
|
-
* .get<User>('getUser', {
|
|
24
|
-
* path: '/users/{id}', // pathParams 自动约束为 { id: string | number }
|
|
25
|
-
* cache: { mode: CacheMode.IfCache, ttl: 60000 },
|
|
26
|
-
* })
|
|
20
|
+
* .get<User>('getUser', { path: '/users/{id}' })
|
|
27
21
|
* .get<User[]>('getUsers', { path: '/users' })
|
|
28
|
-
* .post<User, CreateUserDTO>('createUser', { path: '/users' })
|
|
22
|
+
* .post<User, CreateUserDTO>('createUser', { path: '/users' })
|
|
29
23
|
* .build()
|
|
30
24
|
*
|
|
31
25
|
* // ✅ 类型正确推断
|
|
32
|
-
* userApi.getUser({ pathParams: { id: '123' } })
|
|
33
|
-
* userApi.
|
|
34
|
-
* userApi.createUser({ body: { name: 'John', email: 'x' } }) // body 类型检查
|
|
26
|
+
* userApi.getUser({ pathParams: { id: '123' } })
|
|
27
|
+
* userApi.createUser({ body: { name: 'John', email: 'x' } })
|
|
35
28
|
*
|
|
36
29
|
* // ❌ 类型错误会被捕获
|
|
37
|
-
* userApi.
|
|
38
|
-
* userApi.createUser({ body: { wrong: 'x' } }) // Error: body 类型不匹配
|
|
39
|
-
* userApi.unknownMethod() // Error: 方法不存在
|
|
30
|
+
* userApi.unknownMethod() // Error: 方法不存在
|
|
40
31
|
* ```
|
|
41
32
|
*/
|
|
42
33
|
import { HttpClient } from '../client/HttpClient';
|
|
43
|
-
// ============================================================================
|
|
44
|
-
// API 构建器
|
|
45
|
-
// ============================================================================
|
|
46
34
|
/**
|
|
47
35
|
* API 构建器类
|
|
48
36
|
*
|
|
37
|
+
* 使用链式调用定义类型安全的 API 端点。
|
|
38
|
+
*
|
|
49
39
|
* @template T 当前已定义的端点类型集合,通过链式调用累积
|
|
40
|
+
*
|
|
41
|
+
* @remarks
|
|
42
|
+
* **类型深度限制**:由于 TypeScript 对深层交叉类型的处理限制,
|
|
43
|
+
* 当单个 builder 链式调用超过 4 个端点时,类型推断可能不够精确。
|
|
44
|
+
*
|
|
45
|
+
* 建议:
|
|
46
|
+
* - 保持单个 builder 的端点数量在 4 个以内
|
|
47
|
+
* - 如需更多端点,功能仍然正常,但部分类型检查可能被放宽
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* const userApi = ApiBuilder.create({ baseUrl: '/api' })
|
|
52
|
+
* .get<User>('getUser', { path: '/users/{id}' })
|
|
53
|
+
* .post<User, CreateUserDTO>('createUser', { path: '/users' })
|
|
54
|
+
* .put<User, UpdateUserDTO>('updateUser', { path: '/users/{id}' })
|
|
55
|
+
* .build()
|
|
56
|
+
*
|
|
57
|
+
* // 类型安全调用
|
|
58
|
+
* const user = await userApi.getUser({ pathParams: { id: '1' } })
|
|
59
|
+
* const newUser = await userApi.createUser({ body: { name: 'John', email: 'j@test.com' } })
|
|
60
|
+
* ```
|
|
50
61
|
*/
|
|
51
62
|
export class ApiBuilder {
|
|
52
63
|
constructor(config) {
|
|
@@ -58,14 +69,6 @@ export class ApiBuilder {
|
|
|
58
69
|
*
|
|
59
70
|
* @param config HTTP 客户端配置
|
|
60
71
|
* @returns 新的 ApiBuilder 实例
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
* ```ts
|
|
64
|
-
* const api = ApiBuilder.create({
|
|
65
|
-
* baseUrl: 'https://api.example.com',
|
|
66
|
-
* timeout: 10000,
|
|
67
|
-
* })
|
|
68
|
-
* ```
|
|
69
72
|
*/
|
|
70
73
|
static create(config) {
|
|
71
74
|
return new ApiBuilder(config);
|
|
@@ -76,12 +79,6 @@ export class ApiBuilder {
|
|
|
76
79
|
* @param kyInstance 已配置的 ky 实例
|
|
77
80
|
* @param config 可选的额外配置
|
|
78
81
|
* @returns 新的 ApiBuilder 实例
|
|
79
|
-
*
|
|
80
|
-
* @example
|
|
81
|
-
* ```ts
|
|
82
|
-
* const kyInstance = ky.create({ prefixUrl: 'https://api.example.com' })
|
|
83
|
-
* const api = ApiBuilder.from(kyInstance)
|
|
84
|
-
* ```
|
|
85
82
|
*/
|
|
86
83
|
static from(kyInstance, config) {
|
|
87
84
|
const builder = new ApiBuilder({
|
|
@@ -95,46 +92,17 @@ export class ApiBuilder {
|
|
|
95
92
|
* 定义 GET 请求
|
|
96
93
|
*
|
|
97
94
|
* @template TResponse 响应数据类型
|
|
98
|
-
* @template K
|
|
99
|
-
* @template Path 路径字符串(自动推断,用于提取路径参数)
|
|
100
|
-
*
|
|
101
|
-
* @param name 端点名称
|
|
102
|
-
* @param options 端点配置
|
|
103
|
-
* @returns 累积了新端点类型的 ApiBuilder
|
|
104
|
-
*
|
|
105
|
-
* @example
|
|
106
|
-
* ```ts
|
|
107
|
-
* .get<User>('getUser', {
|
|
108
|
-
* path: '/users/{id}', // pathParams 自动约束为 { id: string | number }
|
|
109
|
-
* cache: { mode: CacheMode.IfCache },
|
|
110
|
-
* })
|
|
111
|
-
* ```
|
|
95
|
+
* @template K 端点名称(自动推断)
|
|
112
96
|
*/
|
|
113
|
-
get(name,
|
|
114
|
-
options) {
|
|
97
|
+
get(name, options) {
|
|
115
98
|
return this.endpoint(name, { ...options, method: 'GET' });
|
|
116
99
|
}
|
|
117
100
|
/**
|
|
118
101
|
* 定义 POST 请求
|
|
119
102
|
*
|
|
120
103
|
* @template TResponse 响应数据类型
|
|
121
|
-
* @template TBody
|
|
122
|
-
* @template K
|
|
123
|
-
* @template Path 路径字符串(自动推断)
|
|
124
|
-
*
|
|
125
|
-
* @param name 端点名称
|
|
126
|
-
* @param options 端点配置
|
|
127
|
-
* @returns 累积了新端点类型的 ApiBuilder
|
|
128
|
-
*
|
|
129
|
-
* @example
|
|
130
|
-
* ```ts
|
|
131
|
-
* interface CreateUserDTO { name: string; email: string }
|
|
132
|
-
*
|
|
133
|
-
* .post<User, CreateUserDTO>('createUser', {
|
|
134
|
-
* path: '/users',
|
|
135
|
-
* })
|
|
136
|
-
* // body 类型约束为 CreateUserDTO
|
|
137
|
-
* ```
|
|
104
|
+
* @template TBody 请求体类型
|
|
105
|
+
* @template K 端点名称
|
|
138
106
|
*/
|
|
139
107
|
post(name, options) {
|
|
140
108
|
return this.endpoint(name, { ...options, method: 'POST' });
|
|
@@ -143,20 +111,8 @@ export class ApiBuilder {
|
|
|
143
111
|
* 定义 PUT 请求
|
|
144
112
|
*
|
|
145
113
|
* @template TResponse 响应数据类型
|
|
146
|
-
* @template TBody
|
|
147
|
-
* @template K
|
|
148
|
-
* @template Path 路径字符串(自动推断)
|
|
149
|
-
*
|
|
150
|
-
* @param name 端点名称
|
|
151
|
-
* @param options 端点配置
|
|
152
|
-
* @returns 累积了新端点类型的 ApiBuilder
|
|
153
|
-
*
|
|
154
|
-
* @example
|
|
155
|
-
* ```ts
|
|
156
|
-
* .put<User, UpdateUserDTO>('updateUser', {
|
|
157
|
-
* path: '/users/{id}',
|
|
158
|
-
* })
|
|
159
|
-
* ```
|
|
114
|
+
* @template TBody 请求体类型
|
|
115
|
+
* @template K 端点名称
|
|
160
116
|
*/
|
|
161
117
|
put(name, options) {
|
|
162
118
|
return this.endpoint(name, { ...options, method: 'PUT' });
|
|
@@ -165,19 +121,7 @@ export class ApiBuilder {
|
|
|
165
121
|
* 定义 DELETE 请求
|
|
166
122
|
*
|
|
167
123
|
* @template TResponse 响应数据类型
|
|
168
|
-
* @template K
|
|
169
|
-
* @template Path 路径字符串(自动推断)
|
|
170
|
-
*
|
|
171
|
-
* @param name 端点名称
|
|
172
|
-
* @param options 端点配置
|
|
173
|
-
* @returns 累积了新端点类型的 ApiBuilder
|
|
174
|
-
*
|
|
175
|
-
* @example
|
|
176
|
-
* ```ts
|
|
177
|
-
* .delete<void>('deleteUser', {
|
|
178
|
-
* path: '/users/{id}',
|
|
179
|
-
* })
|
|
180
|
-
* ```
|
|
124
|
+
* @template K 端点名称
|
|
181
125
|
*/
|
|
182
126
|
delete(name, options) {
|
|
183
127
|
return this.endpoint(name, { ...options, method: 'DELETE' });
|
|
@@ -186,32 +130,14 @@ export class ApiBuilder {
|
|
|
186
130
|
* 定义 PATCH 请求
|
|
187
131
|
*
|
|
188
132
|
* @template TResponse 响应数据类型
|
|
189
|
-
* @template TBody
|
|
190
|
-
* @template K
|
|
191
|
-
* @template Path 路径字符串(自动推断)
|
|
192
|
-
*
|
|
193
|
-
* @param name 端点名称
|
|
194
|
-
* @param options 端点配置
|
|
195
|
-
* @returns 累积了新端点类型的 ApiBuilder
|
|
196
|
-
*
|
|
197
|
-
* @example
|
|
198
|
-
* ```ts
|
|
199
|
-
* .patch<User, Partial<UpdateUserDTO>>('patchUser', {
|
|
200
|
-
* path: '/users/{id}',
|
|
201
|
-
* })
|
|
202
|
-
* ```
|
|
133
|
+
* @template TBody 请求体类型
|
|
134
|
+
* @template K 端点名称
|
|
203
135
|
*/
|
|
204
136
|
patch(name, options) {
|
|
205
137
|
return this.endpoint(name, { ...options, method: 'PATCH' });
|
|
206
138
|
}
|
|
207
139
|
/**
|
|
208
140
|
* 通用端点定义(内部方法)
|
|
209
|
-
*
|
|
210
|
-
* @template TResponse 响应数据类型
|
|
211
|
-
* @template K 端点名称
|
|
212
|
-
* @template Path 路径字符串
|
|
213
|
-
* @template TBody 请求体类型
|
|
214
|
-
* @template HasBody 是否有请求体
|
|
215
141
|
*/
|
|
216
142
|
endpoint(name, options) {
|
|
217
143
|
var _a;
|
|
@@ -224,25 +150,13 @@ export class ApiBuilder {
|
|
|
224
150
|
cache: options.cache,
|
|
225
151
|
};
|
|
226
152
|
this.endpoints.set(name, config);
|
|
227
|
-
// 关键:返回 this,但类型已经累积
|
|
228
153
|
return this;
|
|
229
154
|
}
|
|
230
155
|
/**
|
|
231
156
|
* 扩展 ky 实例(添加 hooks 等)
|
|
232
157
|
*
|
|
233
|
-
* @param options
|
|
234
|
-
* @returns
|
|
235
|
-
*
|
|
236
|
-
* @example
|
|
237
|
-
* ```ts
|
|
238
|
-
* .extend({
|
|
239
|
-
* hooks: {
|
|
240
|
-
* beforeRequest: [(req) => {
|
|
241
|
-
* req.headers.set('Authorization', `Bearer ${token}`)
|
|
242
|
-
* }],
|
|
243
|
-
* },
|
|
244
|
-
* })
|
|
245
|
-
* ```
|
|
158
|
+
* @param options Ky 配置选项
|
|
159
|
+
* @returns 当前 Builder 实例(保留累积的类型)
|
|
246
160
|
*/
|
|
247
161
|
extend(options) {
|
|
248
162
|
this.client.extendKy(options);
|
|
@@ -251,27 +165,11 @@ export class ApiBuilder {
|
|
|
251
165
|
/**
|
|
252
166
|
* 构建 API 服务
|
|
253
167
|
*
|
|
254
|
-
* 将所有定义的端点转换为可调用的 API 对象。
|
|
255
|
-
* 每个端点都带有完整的类型信息(响应类型、路径参数、请求体类型)。
|
|
256
|
-
*
|
|
257
168
|
* @returns 类型安全的 API 对象
|
|
258
|
-
*
|
|
259
|
-
* @example
|
|
260
|
-
* ```ts
|
|
261
|
-
* const api = ApiBuilder.create({ baseUrl: '...' })
|
|
262
|
-
* .get<User>('getUser', { path: '/users/{id}' })
|
|
263
|
-
* .post<User, CreateUserDTO>('createUser', { path: '/users' })
|
|
264
|
-
* .build()
|
|
265
|
-
*
|
|
266
|
-
* // 类型安全调用
|
|
267
|
-
* await api.getUser({ pathParams: { id: '1' } })
|
|
268
|
-
* await api.createUser({ body: { name: 'John', email: 'x@x.com' } })
|
|
269
|
-
* ```
|
|
270
169
|
*/
|
|
271
170
|
build() {
|
|
272
171
|
const api = {};
|
|
273
172
|
for (const [name, config] of this.endpoints) {
|
|
274
|
-
// 运行时不需要类型检查,类型安全由编译时保证
|
|
275
173
|
const endpoint = ((options) => {
|
|
276
174
|
return this.client.request(config, options);
|
|
277
175
|
});
|
|
@@ -282,14 +180,6 @@ export class ApiBuilder {
|
|
|
282
180
|
}
|
|
283
181
|
/**
|
|
284
182
|
* 获取 HTTP 客户端实例(用于高级操作)
|
|
285
|
-
*
|
|
286
|
-
* @returns HttpClient 实例
|
|
287
|
-
*
|
|
288
|
-
* @example
|
|
289
|
-
* ```ts
|
|
290
|
-
* const client = api.getClient()
|
|
291
|
-
* await client.clearCache()
|
|
292
|
-
* ```
|
|
293
183
|
*/
|
|
294
184
|
getClient() {
|
|
295
185
|
return this.client;
|
package/dist/http/api/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { ApiBuilder, type ApiEndpoint, type ApiDefinition
|
|
1
|
+
export { ApiBuilder, type ApiEndpoint, type ApiDefinition } from './ApiBuilder';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/http/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/http/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/http/api/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { ApiBuilder
|
|
1
|
+
export { ApiBuilder } from './ApiBuilder';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ApiBuilder 使用示例
|
|
3
|
+
*
|
|
4
|
+
* 此文件演示 ApiBuilder 的类型安全特性:
|
|
5
|
+
* 1. 泛型累积 - 链式调用自动累积端点类型
|
|
6
|
+
* 2. Body 类型约束 - POST/PUT/PATCH 请求体类型检查
|
|
7
|
+
* 3. 端点名称字面量 - 调用不存在的端点会编译报错
|
|
8
|
+
*/
|
|
9
|
+
/** 用户实体 */
|
|
10
|
+
interface User {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
email: string;
|
|
14
|
+
createdAt: string;
|
|
15
|
+
}
|
|
16
|
+
/** 创建用户 DTO */
|
|
17
|
+
interface CreateUserDTO {
|
|
18
|
+
name: string;
|
|
19
|
+
email: string;
|
|
20
|
+
}
|
|
21
|
+
/** 更新用户 DTO */
|
|
22
|
+
interface UpdateUserDTO {
|
|
23
|
+
name?: string;
|
|
24
|
+
email?: string;
|
|
25
|
+
}
|
|
26
|
+
/** 分页响应 */
|
|
27
|
+
interface PaginatedResponse<T> {
|
|
28
|
+
data: T[];
|
|
29
|
+
total: number;
|
|
30
|
+
page: number;
|
|
31
|
+
pageSize: number;
|
|
32
|
+
}
|
|
33
|
+
export declare const userApi: object & {
|
|
34
|
+
[x: string]: import("../api/ApiBuilder").ApiEndpointWithoutBody<User[]>;
|
|
35
|
+
} & {
|
|
36
|
+
[x: string]: import("../api/ApiBuilder").ApiEndpointWithoutBody<User>;
|
|
37
|
+
} & {
|
|
38
|
+
[x: string]: import("../api/ApiBuilder").ApiEndpointWithoutBody<PaginatedResponse<User>>;
|
|
39
|
+
} & {
|
|
40
|
+
[x: string]: import("../api/ApiBuilder").ApiEndpointWithBodyRequired<User, CreateUserDTO>;
|
|
41
|
+
} & {
|
|
42
|
+
[x: string]: import("../api/ApiBuilder").ApiEndpointWithBodyOptional<void>;
|
|
43
|
+
} & {
|
|
44
|
+
[x: string]: import("../api/ApiBuilder").ApiEndpointWithBodyRequired<User, UpdateUserDTO>;
|
|
45
|
+
} & {
|
|
46
|
+
[x: string]: import("../api/ApiBuilder").ApiEndpointWithBodyRequired<User, Partial<UpdateUserDTO>>;
|
|
47
|
+
} & {
|
|
48
|
+
[x: string]: import("../api/ApiBuilder").ApiEndpointWithoutBody<void>;
|
|
49
|
+
};
|
|
50
|
+
declare function demo(): Promise<void>;
|
|
51
|
+
declare function typeErrors(): void;
|
|
52
|
+
export { demo, typeErrors };
|
|
53
|
+
//# sourceMappingURL=api-builder.demo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-builder.demo.d.ts","sourceRoot":"","sources":["../../../src/http/demo/api-builder.demo.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,WAAW;AACX,UAAU,IAAI;IACV,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;CACpB;AAED,eAAe;AACf,UAAU,aAAa;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;CAChB;AAED,eAAe;AACf,UAAU,aAAa;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,WAAW;AACX,UAAU,iBAAiB,CAAC,CAAC;IACzB,IAAI,EAAE,CAAC,EAAE,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;CACnB;AAMD,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;CA8DR,CAAA;AAMZ,iBAAe,IAAI,kBAuElB;AAoBD,iBAAS,UAAU,SAYlB;AAMD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAA"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ApiBuilder 使用示例
|
|
3
|
+
*
|
|
4
|
+
* 此文件演示 ApiBuilder 的类型安全特性:
|
|
5
|
+
* 1. 泛型累积 - 链式调用自动累积端点类型
|
|
6
|
+
* 2. Body 类型约束 - POST/PUT/PATCH 请求体类型检查
|
|
7
|
+
* 3. 端点名称字面量 - 调用不存在的端点会编译报错
|
|
8
|
+
*/
|
|
9
|
+
import { ApiBuilder } from '../api/ApiBuilder';
|
|
10
|
+
import { CacheMode } from '../models/CacheMode';
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// 创建 API 服务
|
|
13
|
+
// ============================================================================
|
|
14
|
+
export const userApi = ApiBuilder.create({
|
|
15
|
+
baseUrl: 'https://api.example.com',
|
|
16
|
+
timeout: 10000,
|
|
17
|
+
defaultCache: {
|
|
18
|
+
mode: CacheMode.FirstRemote,
|
|
19
|
+
ttl: 5 * 60 * 1000,
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
// GET 请求 - 无路径参数
|
|
23
|
+
.get('getUsers', {
|
|
24
|
+
path: '/users',
|
|
25
|
+
cache: { mode: CacheMode.FirstCache },
|
|
26
|
+
})
|
|
27
|
+
// GET 请求 - 有路径参数
|
|
28
|
+
.get('getUser', {
|
|
29
|
+
path: '/users/{id}',
|
|
30
|
+
cache: { mode: CacheMode.IfCache, ttl: 60000 },
|
|
31
|
+
})
|
|
32
|
+
// GET 请求 - 分页
|
|
33
|
+
.get('getUsersPaginated', {
|
|
34
|
+
path: '/users/paginated',
|
|
35
|
+
})
|
|
36
|
+
// POST 请求 - 有 body 类型
|
|
37
|
+
.post('createUser', {
|
|
38
|
+
path: '/users',
|
|
39
|
+
})
|
|
40
|
+
// POST 请求 - 无 body 类型(触发器)
|
|
41
|
+
.post('activateUser', {
|
|
42
|
+
path: '/users/{id}/activate',
|
|
43
|
+
})
|
|
44
|
+
// PUT 请求 - 完整更新
|
|
45
|
+
.put('updateUser', {
|
|
46
|
+
path: '/users/{id}',
|
|
47
|
+
})
|
|
48
|
+
// PATCH 请求 - 部分更新
|
|
49
|
+
.patch('patchUser', {
|
|
50
|
+
path: '/users/{id}',
|
|
51
|
+
})
|
|
52
|
+
// DELETE 请求
|
|
53
|
+
.delete('deleteUser', {
|
|
54
|
+
path: '/users/{id}',
|
|
55
|
+
})
|
|
56
|
+
// 扩展 ky 实例(添加拦截器)
|
|
57
|
+
.extend({
|
|
58
|
+
hooks: {
|
|
59
|
+
beforeRequest: [
|
|
60
|
+
(request) => {
|
|
61
|
+
const token = 'mock-token';
|
|
62
|
+
request.headers.set('Authorization', `Bearer ${token}`);
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
.build();
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// 使用示例 - 类型安全调用
|
|
70
|
+
// ============================================================================
|
|
71
|
+
async function demo() {
|
|
72
|
+
// ✅ GET 无路径参数
|
|
73
|
+
const users = await userApi.getUsers();
|
|
74
|
+
console.log('Users:', users);
|
|
75
|
+
// ✅ GET 有路径参数
|
|
76
|
+
const user = await userApi.getUser({
|
|
77
|
+
pathParams: { id: '123' },
|
|
78
|
+
});
|
|
79
|
+
console.log('User:', user);
|
|
80
|
+
// ✅ GET 带 query 参数
|
|
81
|
+
const paginatedUsers = await userApi.getUsersPaginated({
|
|
82
|
+
query: { page: 1, pageSize: 10 },
|
|
83
|
+
});
|
|
84
|
+
console.log('Paginated:', paginatedUsers);
|
|
85
|
+
// ✅ POST 有 body 类型约束
|
|
86
|
+
const newUser = await userApi.createUser({
|
|
87
|
+
body: { name: 'John', email: 'john@example.com' },
|
|
88
|
+
});
|
|
89
|
+
console.log('Created:', newUser);
|
|
90
|
+
// ✅ POST 无 body(触发器)
|
|
91
|
+
await userApi.activateUser({
|
|
92
|
+
pathParams: { id: '123' },
|
|
93
|
+
});
|
|
94
|
+
// ✅ PUT 完整更新
|
|
95
|
+
const updatedUser = await userApi.updateUser({
|
|
96
|
+
pathParams: { id: '123' },
|
|
97
|
+
body: { name: 'John Updated', email: 'john.updated@example.com' },
|
|
98
|
+
});
|
|
99
|
+
console.log('Updated:', updatedUser);
|
|
100
|
+
// ✅ PATCH 部分更新
|
|
101
|
+
const patchedUser = await userApi.patchUser({
|
|
102
|
+
pathParams: { id: '123' },
|
|
103
|
+
body: { name: 'John Patched' },
|
|
104
|
+
});
|
|
105
|
+
console.log('Patched:', patchedUser);
|
|
106
|
+
// ✅ DELETE
|
|
107
|
+
await userApi.deleteUser({
|
|
108
|
+
pathParams: { id: '123' },
|
|
109
|
+
});
|
|
110
|
+
// ✅ 覆盖缓存配置
|
|
111
|
+
const freshUser = await userApi.getUser({
|
|
112
|
+
pathParams: { id: '123' },
|
|
113
|
+
cache: { mode: CacheMode.OnlyRemote },
|
|
114
|
+
});
|
|
115
|
+
console.log('Fresh:', freshUser);
|
|
116
|
+
// ✅ 多次响应(for await)
|
|
117
|
+
for await (const { data, fromCache } of userApi.getUser({ pathParams: { id: '123' } })) {
|
|
118
|
+
console.log('Data:', data, 'FromCache:', fromCache);
|
|
119
|
+
}
|
|
120
|
+
// ✅ subscribe 方式
|
|
121
|
+
await userApi.getUser({ pathParams: { id: '123' } }).subscribe({
|
|
122
|
+
onData: (data, fromCache) => {
|
|
123
|
+
console.log('Subscribe data:', data, fromCache);
|
|
124
|
+
},
|
|
125
|
+
onError: (error) => {
|
|
126
|
+
console.error('Error:', error);
|
|
127
|
+
},
|
|
128
|
+
onComplete: () => {
|
|
129
|
+
console.log('Complete');
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// 类型错误示例
|
|
135
|
+
// ============================================================================
|
|
136
|
+
/**
|
|
137
|
+
* 类型安全验证
|
|
138
|
+
*
|
|
139
|
+
* 注意:由于 TypeScript 对深层交叉类型的处理限制,
|
|
140
|
+
* 当链式调用超过 4 个端点时,类型推断可能不够精确。
|
|
141
|
+
* 建议:保持单个 builder 的端点数量在 4 个以内,或分批构建。
|
|
142
|
+
*
|
|
143
|
+
* 以下是简化版本的类型安全验证(2-3 个端点)
|
|
144
|
+
*/
|
|
145
|
+
const simpleApi = ApiBuilder.create({ baseUrl: 'http://test' })
|
|
146
|
+
.get('getUser', { path: '/users/{id}' })
|
|
147
|
+
.post('createUser', { path: '/users' })
|
|
148
|
+
.build();
|
|
149
|
+
function typeErrors() {
|
|
150
|
+
// ❌ body 类型不匹配(缺少 email)
|
|
151
|
+
// @ts-expect-error email is required
|
|
152
|
+
simpleApi.createUser({ body: { name: 'John' } });
|
|
153
|
+
// ❌ body 类型不匹配(错误的属性)
|
|
154
|
+
// @ts-expect-error wrong property
|
|
155
|
+
simpleApi.createUser({ body: { wrong: 'x' } });
|
|
156
|
+
// ❌ GET 请求不应该有 body
|
|
157
|
+
// @ts-expect-error body not allowed for GET
|
|
158
|
+
simpleApi.getUser({ pathParams: { id: '1' }, body: {} });
|
|
159
|
+
}
|
|
160
|
+
// ============================================================================
|
|
161
|
+
// 导出
|
|
162
|
+
// ============================================================================
|
|
163
|
+
export { demo, typeErrors };
|