xxf_react 0.7.2 → 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 +151 -43
- package/dist/http/api/ApiBuilder.d.ts.map +1 -1
- package/dist/http/api/ApiBuilder.js +68 -29
- 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
|
@@ -4,39 +4,30 @@
|
|
|
4
4
|
* 使用 Builder 模式定义 API,类似 Android Retrofit。
|
|
5
5
|
* 采用泛型累积模式,每次 .get() / .post() 调用都返回带有累积类型的新 Builder。
|
|
6
6
|
*
|
|
7
|
-
* ##
|
|
7
|
+
* ## 类型安全特性
|
|
8
8
|
*
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
12
|
-
* - 调用不存在的端点会产生编译错误
|
|
9
|
+
* - **端点名称字面量**:每个端点名称保留字面量类型(如 `'getUser'`)
|
|
10
|
+
* - **泛型累积**:链式调用累积所有端点类型 `T & Record<K, ApiEndpoint<...>>`
|
|
11
|
+
* - **Body 类型约束**:POST/PUT/PATCH 支持指定请求体类型
|
|
13
12
|
*
|
|
14
13
|
* @example
|
|
15
14
|
* ```ts
|
|
16
|
-
*
|
|
15
|
+
* interface CreateUserDTO { name: string; email: string }
|
|
16
|
+
*
|
|
17
17
|
* const userApi = ApiBuilder.create({
|
|
18
18
|
* baseUrl: 'https://api.example.com',
|
|
19
19
|
* })
|
|
20
|
-
* .get<User>('getUser', {
|
|
21
|
-
* path: '/users/{id}',
|
|
22
|
-
* cache: { mode: CacheMode.IfCache, ttl: 60000 },
|
|
23
|
-
* })
|
|
20
|
+
* .get<User>('getUser', { path: '/users/{id}' })
|
|
24
21
|
* .get<User[]>('getUsers', { path: '/users' })
|
|
25
|
-
* .post<User>('createUser', { path: '/users' })
|
|
22
|
+
* .post<User, CreateUserDTO>('createUser', { path: '/users' })
|
|
26
23
|
* .build()
|
|
27
24
|
*
|
|
28
25
|
* // ✅ 类型正确推断
|
|
29
|
-
* userApi.getUser({ pathParams: { id: '123' } })
|
|
30
|
-
* userApi.
|
|
31
|
-
* userApi.createUser({ body: { name: 'John' } }) // ApiStream<User>
|
|
26
|
+
* userApi.getUser({ pathParams: { id: '123' } })
|
|
27
|
+
* userApi.createUser({ body: { name: 'John', email: 'x' } })
|
|
32
28
|
*
|
|
33
29
|
* // ❌ 类型错误会被捕获
|
|
34
|
-
* userApi.unknownMethod() // Error:
|
|
35
|
-
*
|
|
36
|
-
* // 多次响应
|
|
37
|
-
* for await (const { data, fromCache } of userApi.getUser({ pathParams: { id: '123' } })) {
|
|
38
|
-
* console.log(data, fromCache)
|
|
39
|
-
* }
|
|
30
|
+
* userApi.unknownMethod() // Error: 方法不存在
|
|
40
31
|
* ```
|
|
41
32
|
*/
|
|
42
33
|
import ky, { type Options as KyOptions } from 'ky';
|
|
@@ -44,19 +35,73 @@ import { HttpClient } from '../client/HttpClient';
|
|
|
44
35
|
import { ApiStream } from '../client/ApiStream';
|
|
45
36
|
import type { RequestConfig, ApiOptions, CacheConfig, HttpClientConfig } from '../types';
|
|
46
37
|
/**
|
|
47
|
-
* API
|
|
38
|
+
* 基础 API 调用选项(不含 body)
|
|
39
|
+
*/
|
|
40
|
+
type BaseApiOptions = Omit<ApiOptions, 'body'>;
|
|
41
|
+
/**
|
|
42
|
+
* 带 body 的 API 调用选项(body 必需)
|
|
43
|
+
*/
|
|
44
|
+
type WithBodyRequired<TBody> = BaseApiOptions & {
|
|
45
|
+
body: TBody;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* 带 body 的 API 调用选项(body 可选)
|
|
49
|
+
*/
|
|
50
|
+
type WithBodyOptional = BaseApiOptions & {
|
|
51
|
+
body?: unknown;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* 无 body 的 API 调用选项(GET/DELETE)
|
|
55
|
+
*/
|
|
56
|
+
type WithoutBodyOptions = BaseApiOptions & {
|
|
57
|
+
body?: never;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* API 端点函数类型(有 body 且必需 - POST/PUT/PATCH 指定了 TBody)
|
|
61
|
+
*/
|
|
62
|
+
export type ApiEndpointWithBodyRequired<TResponse, TBody> = {
|
|
63
|
+
(options: WithBodyRequired<TBody>): ApiStream<TResponse>;
|
|
64
|
+
/** 请求配置 */
|
|
65
|
+
config: RequestConfig;
|
|
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)
|
|
48
77
|
*/
|
|
49
|
-
export type
|
|
50
|
-
(options?:
|
|
78
|
+
export type ApiEndpointWithoutBody<TResponse> = {
|
|
79
|
+
(options?: WithoutBodyOptions): ApiStream<TResponse>;
|
|
51
80
|
/** 请求配置 */
|
|
52
81
|
config: RequestConfig;
|
|
53
82
|
};
|
|
54
83
|
/**
|
|
55
|
-
*
|
|
84
|
+
* 检测是否为 unknown 类型
|
|
85
|
+
*/
|
|
86
|
+
type IsUnknown<T> = [unknown] extends [T] ? ([T] extends [unknown] ? true : false) : false;
|
|
87
|
+
/**
|
|
88
|
+
* 检测是否为 void 类型
|
|
56
89
|
*/
|
|
57
|
-
|
|
90
|
+
type IsVoid<T> = [T] extends [void] ? ([void] extends [T] ? true : false) : false;
|
|
58
91
|
/**
|
|
59
|
-
*
|
|
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>;
|
|
99
|
+
/**
|
|
100
|
+
* API 定义类型(端点集合)
|
|
101
|
+
*/
|
|
102
|
+
export type ApiDefinition = Record<string, ApiEndpoint<unknown, unknown, boolean>>;
|
|
103
|
+
/**
|
|
104
|
+
* 端点选项(构建时配置)
|
|
60
105
|
*/
|
|
61
106
|
interface EndpointOptions {
|
|
62
107
|
/** 请求路径,支持 {param} 占位符 */
|
|
@@ -73,56 +118,119 @@ interface EndpointOptions {
|
|
|
73
118
|
cache?: CacheConfig;
|
|
74
119
|
}
|
|
75
120
|
/**
|
|
76
|
-
* API
|
|
121
|
+
* 空 API 类型(空记录,不允许任意属性访问)
|
|
122
|
+
*/
|
|
123
|
+
type EmptyApi = {};
|
|
124
|
+
/**
|
|
125
|
+
* API 构建器类
|
|
126
|
+
*
|
|
127
|
+
* 使用链式调用定义类型安全的 API 端点。
|
|
128
|
+
*
|
|
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
|
+
* ```
|
|
77
151
|
*/
|
|
78
|
-
export declare class ApiBuilder<T extends
|
|
152
|
+
export declare class ApiBuilder<T extends object = EmptyApi> {
|
|
79
153
|
private client;
|
|
80
154
|
private endpoints;
|
|
81
155
|
private constructor();
|
|
82
156
|
/**
|
|
83
157
|
* 创建 API 构建器
|
|
158
|
+
*
|
|
159
|
+
* @param config HTTP 客户端配置
|
|
160
|
+
* @returns 新的 ApiBuilder 实例
|
|
84
161
|
*/
|
|
85
|
-
static create(config: HttpClientConfig): ApiBuilder
|
|
162
|
+
static create(config: HttpClientConfig): ApiBuilder<object>;
|
|
86
163
|
/**
|
|
87
164
|
* 从现有 ky 实例创建
|
|
165
|
+
*
|
|
166
|
+
* @param kyInstance 已配置的 ky 实例
|
|
167
|
+
* @param config 可选的额外配置
|
|
168
|
+
* @returns 新的 ApiBuilder 实例
|
|
88
169
|
*/
|
|
89
|
-
static from(kyInstance: ReturnType<typeof ky.create>, config?: Partial<HttpClientConfig>): ApiBuilder
|
|
170
|
+
static from(kyInstance: ReturnType<typeof ky.create>, config?: Partial<HttpClientConfig>): ApiBuilder<object>;
|
|
90
171
|
/**
|
|
91
172
|
* 定义 GET 请求
|
|
92
|
-
*
|
|
173
|
+
*
|
|
174
|
+
* @template TResponse 响应数据类型
|
|
175
|
+
* @template K 端点名称(自动推断)
|
|
93
176
|
*/
|
|
94
|
-
get<K extends string
|
|
177
|
+
get<TResponse, const K extends string = string>(name: K, options: Omit<EndpointOptions, 'method'>): ApiBuilder<T & {
|
|
178
|
+
[P in K]: ApiEndpointWithoutBody<TResponse>;
|
|
179
|
+
}>;
|
|
95
180
|
/**
|
|
96
181
|
* 定义 POST 请求
|
|
97
|
-
*
|
|
182
|
+
*
|
|
183
|
+
* @template TResponse 响应数据类型
|
|
184
|
+
* @template TBody 请求体类型
|
|
185
|
+
* @template K 端点名称
|
|
98
186
|
*/
|
|
99
|
-
post<K extends string
|
|
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
|
+
}>;
|
|
100
190
|
/**
|
|
101
191
|
* 定义 PUT 请求
|
|
102
|
-
*
|
|
192
|
+
*
|
|
193
|
+
* @template TResponse 响应数据类型
|
|
194
|
+
* @template TBody 请求体类型
|
|
195
|
+
* @template K 端点名称
|
|
103
196
|
*/
|
|
104
|
-
put<K extends string
|
|
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
|
+
}>;
|
|
105
200
|
/**
|
|
106
201
|
* 定义 DELETE 请求
|
|
107
|
-
*
|
|
202
|
+
*
|
|
203
|
+
* @template TResponse 响应数据类型
|
|
204
|
+
* @template K 端点名称
|
|
108
205
|
*/
|
|
109
|
-
delete<K extends string
|
|
206
|
+
delete<TResponse, const K extends string = string>(name: K, options: Omit<EndpointOptions, 'method'>): ApiBuilder<T & {
|
|
207
|
+
[P in K]: ApiEndpointWithoutBody<TResponse>;
|
|
208
|
+
}>;
|
|
110
209
|
/**
|
|
111
210
|
* 定义 PATCH 请求
|
|
112
|
-
*
|
|
211
|
+
*
|
|
212
|
+
* @template TResponse 响应数据类型
|
|
213
|
+
* @template TBody 请求体类型
|
|
214
|
+
* @template K 端点名称
|
|
113
215
|
*/
|
|
114
|
-
patch<K extends string
|
|
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
|
+
}>;
|
|
115
219
|
/**
|
|
116
|
-
*
|
|
117
|
-
* K 放在前面,从 name 参数自动推断;TResponse 需要手动指定
|
|
220
|
+
* 通用端点定义(内部方法)
|
|
118
221
|
*/
|
|
119
222
|
private endpoint;
|
|
120
223
|
/**
|
|
121
224
|
* 扩展 ky 实例(添加 hooks 等)
|
|
225
|
+
*
|
|
226
|
+
* @param options Ky 配置选项
|
|
227
|
+
* @returns 当前 Builder 实例(保留累积的类型)
|
|
122
228
|
*/
|
|
123
|
-
extend(options: KyOptions):
|
|
229
|
+
extend(options: KyOptions): ApiBuilder<T>;
|
|
124
230
|
/**
|
|
125
231
|
* 构建 API 服务
|
|
232
|
+
*
|
|
233
|
+
* @returns 类型安全的 API 对象
|
|
126
234
|
*/
|
|
127
235
|
build(): T;
|
|
128
236
|
/**
|
|
@@ -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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"}
|
|
@@ -4,44 +4,60 @@
|
|
|
4
4
|
* 使用 Builder 模式定义 API,类似 Android Retrofit。
|
|
5
5
|
* 采用泛型累积模式,每次 .get() / .post() 调用都返回带有累积类型的新 Builder。
|
|
6
6
|
*
|
|
7
|
-
* ##
|
|
7
|
+
* ## 类型安全特性
|
|
8
8
|
*
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
12
|
-
* - 调用不存在的端点会产生编译错误
|
|
9
|
+
* - **端点名称字面量**:每个端点名称保留字面量类型(如 `'getUser'`)
|
|
10
|
+
* - **泛型累积**:链式调用累积所有端点类型 `T & Record<K, ApiEndpoint<...>>`
|
|
11
|
+
* - **Body 类型约束**:POST/PUT/PATCH 支持指定请求体类型
|
|
13
12
|
*
|
|
14
13
|
* @example
|
|
15
14
|
* ```ts
|
|
16
|
-
*
|
|
15
|
+
* interface CreateUserDTO { name: string; email: string }
|
|
16
|
+
*
|
|
17
17
|
* const userApi = ApiBuilder.create({
|
|
18
18
|
* baseUrl: 'https://api.example.com',
|
|
19
19
|
* })
|
|
20
|
-
* .get<User>('getUser', {
|
|
21
|
-
* path: '/users/{id}',
|
|
22
|
-
* cache: { mode: CacheMode.IfCache, ttl: 60000 },
|
|
23
|
-
* })
|
|
20
|
+
* .get<User>('getUser', { path: '/users/{id}' })
|
|
24
21
|
* .get<User[]>('getUsers', { path: '/users' })
|
|
25
|
-
* .post<User>('createUser', { path: '/users' })
|
|
22
|
+
* .post<User, CreateUserDTO>('createUser', { path: '/users' })
|
|
26
23
|
* .build()
|
|
27
24
|
*
|
|
28
25
|
* // ✅ 类型正确推断
|
|
29
|
-
* userApi.getUser({ pathParams: { id: '123' } })
|
|
30
|
-
* userApi.
|
|
31
|
-
* userApi.createUser({ body: { name: 'John' } }) // ApiStream<User>
|
|
26
|
+
* userApi.getUser({ pathParams: { id: '123' } })
|
|
27
|
+
* userApi.createUser({ body: { name: 'John', email: 'x' } })
|
|
32
28
|
*
|
|
33
29
|
* // ❌ 类型错误会被捕获
|
|
34
|
-
* userApi.unknownMethod() // Error:
|
|
35
|
-
*
|
|
36
|
-
* // 多次响应
|
|
37
|
-
* for await (const { data, fromCache } of userApi.getUser({ pathParams: { id: '123' } })) {
|
|
38
|
-
* console.log(data, fromCache)
|
|
39
|
-
* }
|
|
30
|
+
* userApi.unknownMethod() // Error: 方法不存在
|
|
40
31
|
* ```
|
|
41
32
|
*/
|
|
42
33
|
import { HttpClient } from '../client/HttpClient';
|
|
43
34
|
/**
|
|
44
|
-
* API
|
|
35
|
+
* API 构建器类
|
|
36
|
+
*
|
|
37
|
+
* 使用链式调用定义类型安全的 API 端点。
|
|
38
|
+
*
|
|
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
|
+
* ```
|
|
45
61
|
*/
|
|
46
62
|
export class ApiBuilder {
|
|
47
63
|
constructor(config) {
|
|
@@ -50,12 +66,19 @@ export class ApiBuilder {
|
|
|
50
66
|
}
|
|
51
67
|
/**
|
|
52
68
|
* 创建 API 构建器
|
|
69
|
+
*
|
|
70
|
+
* @param config HTTP 客户端配置
|
|
71
|
+
* @returns 新的 ApiBuilder 实例
|
|
53
72
|
*/
|
|
54
73
|
static create(config) {
|
|
55
74
|
return new ApiBuilder(config);
|
|
56
75
|
}
|
|
57
76
|
/**
|
|
58
77
|
* 从现有 ky 实例创建
|
|
78
|
+
*
|
|
79
|
+
* @param kyInstance 已配置的 ky 实例
|
|
80
|
+
* @param config 可选的额外配置
|
|
81
|
+
* @returns 新的 ApiBuilder 实例
|
|
59
82
|
*/
|
|
60
83
|
static from(kyInstance, config) {
|
|
61
84
|
const builder = new ApiBuilder({
|
|
@@ -67,42 +90,54 @@ export class ApiBuilder {
|
|
|
67
90
|
}
|
|
68
91
|
/**
|
|
69
92
|
* 定义 GET 请求
|
|
70
|
-
*
|
|
93
|
+
*
|
|
94
|
+
* @template TResponse 响应数据类型
|
|
95
|
+
* @template K 端点名称(自动推断)
|
|
71
96
|
*/
|
|
72
97
|
get(name, options) {
|
|
73
98
|
return this.endpoint(name, { ...options, method: 'GET' });
|
|
74
99
|
}
|
|
75
100
|
/**
|
|
76
101
|
* 定义 POST 请求
|
|
77
|
-
*
|
|
102
|
+
*
|
|
103
|
+
* @template TResponse 响应数据类型
|
|
104
|
+
* @template TBody 请求体类型
|
|
105
|
+
* @template K 端点名称
|
|
78
106
|
*/
|
|
79
107
|
post(name, options) {
|
|
80
108
|
return this.endpoint(name, { ...options, method: 'POST' });
|
|
81
109
|
}
|
|
82
110
|
/**
|
|
83
111
|
* 定义 PUT 请求
|
|
84
|
-
*
|
|
112
|
+
*
|
|
113
|
+
* @template TResponse 响应数据类型
|
|
114
|
+
* @template TBody 请求体类型
|
|
115
|
+
* @template K 端点名称
|
|
85
116
|
*/
|
|
86
117
|
put(name, options) {
|
|
87
118
|
return this.endpoint(name, { ...options, method: 'PUT' });
|
|
88
119
|
}
|
|
89
120
|
/**
|
|
90
121
|
* 定义 DELETE 请求
|
|
91
|
-
*
|
|
122
|
+
*
|
|
123
|
+
* @template TResponse 响应数据类型
|
|
124
|
+
* @template K 端点名称
|
|
92
125
|
*/
|
|
93
126
|
delete(name, options) {
|
|
94
127
|
return this.endpoint(name, { ...options, method: 'DELETE' });
|
|
95
128
|
}
|
|
96
129
|
/**
|
|
97
130
|
* 定义 PATCH 请求
|
|
98
|
-
*
|
|
131
|
+
*
|
|
132
|
+
* @template TResponse 响应数据类型
|
|
133
|
+
* @template TBody 请求体类型
|
|
134
|
+
* @template K 端点名称
|
|
99
135
|
*/
|
|
100
136
|
patch(name, options) {
|
|
101
137
|
return this.endpoint(name, { ...options, method: 'PATCH' });
|
|
102
138
|
}
|
|
103
139
|
/**
|
|
104
|
-
*
|
|
105
|
-
* K 放在前面,从 name 参数自动推断;TResponse 需要手动指定
|
|
140
|
+
* 通用端点定义(内部方法)
|
|
106
141
|
*/
|
|
107
142
|
endpoint(name, options) {
|
|
108
143
|
var _a;
|
|
@@ -115,11 +150,13 @@ export class ApiBuilder {
|
|
|
115
150
|
cache: options.cache,
|
|
116
151
|
};
|
|
117
152
|
this.endpoints.set(name, config);
|
|
118
|
-
// 关键:返回 this,但类型已经累积
|
|
119
153
|
return this;
|
|
120
154
|
}
|
|
121
155
|
/**
|
|
122
156
|
* 扩展 ky 实例(添加 hooks 等)
|
|
157
|
+
*
|
|
158
|
+
* @param options Ky 配置选项
|
|
159
|
+
* @returns 当前 Builder 实例(保留累积的类型)
|
|
123
160
|
*/
|
|
124
161
|
extend(options) {
|
|
125
162
|
this.client.extendKy(options);
|
|
@@ -127,6 +164,8 @@ export class ApiBuilder {
|
|
|
127
164
|
}
|
|
128
165
|
/**
|
|
129
166
|
* 构建 API 服务
|
|
167
|
+
*
|
|
168
|
+
* @returns 类型安全的 API 对象
|
|
130
169
|
*/
|
|
131
170
|
build() {
|
|
132
171
|
const api = {};
|
|
@@ -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 };
|