xxf_react 0.7.3 → 0.7.5
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 +123 -211
- package/dist/http/api/ApiBuilder.d.ts.map +1 -1
- package/dist/http/api/ApiBuilder.js +41 -149
- package/dist/http/api/index.d.ts +1 -1
- package/dist/http/api/index.d.ts.map +1 -1
- package/dist/http/demo/api-builder.demo.d.ts +102 -0
- package/dist/http/demo/api-builder.demo.d.ts.map +1 -0
- package/dist/http/demo/api-builder.demo.js +343 -0
- package/dist/http/index.d.ts +1 -1
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/index.js +1 -1
- package/package.json +1 -1
|
@@ -8,110 +8,111 @@
|
|
|
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
|
+
* 强制 TypeScript 展开交叉类型为单一对象类型
|
|
43
|
+
* 用于用户自定义接口类型时使用
|
|
61
44
|
*/
|
|
62
|
-
type
|
|
45
|
+
export type Simplify<T> = {
|
|
46
|
+
[K in keyof T]: T[K];
|
|
47
|
+
} & {};
|
|
63
48
|
/**
|
|
64
|
-
*
|
|
49
|
+
* 带 body 的 API 调用选项(body 必需)
|
|
65
50
|
*/
|
|
66
|
-
type
|
|
51
|
+
type WithBodyRequired<TBody> = BaseApiOptions & {
|
|
52
|
+
body: TBody;
|
|
53
|
+
};
|
|
67
54
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* @template Path 路径字符串字面量
|
|
71
|
-
* @template TBody 请求体类型(用于 POST/PUT/PATCH)
|
|
72
|
-
* @template HasBody 是否需要 body
|
|
55
|
+
* 带 body 的 API 调用选项(body 可选)
|
|
73
56
|
*/
|
|
74
|
-
type
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
} & (HasPathParams<Path> extends true ? {
|
|
82
|
-
pathParams: PathParamsType<Path>;
|
|
83
|
-
} : {
|
|
84
|
-
pathParams?: undefined;
|
|
85
|
-
}) & (HasBody extends true ? {
|
|
86
|
-
body?: TBody;
|
|
87
|
-
} : {
|
|
57
|
+
type WithBodyOptional = BaseApiOptions & {
|
|
58
|
+
body?: unknown;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* 无 body 的 API 调用选项(GET/DELETE)
|
|
62
|
+
*/
|
|
63
|
+
type WithoutBodyOptions = BaseApiOptions & {
|
|
88
64
|
body?: never;
|
|
89
|
-
}
|
|
65
|
+
};
|
|
90
66
|
/**
|
|
91
|
-
* API
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
67
|
+
* API 端点函数类型(有 body 且必需 - POST/PUT/PATCH 指定了 TBody)
|
|
68
|
+
*/
|
|
69
|
+
export type ApiEndpointWithBodyRequired<TResponse, TBody> = {
|
|
70
|
+
(options: WithBodyRequired<TBody>): ApiStream<TResponse>;
|
|
71
|
+
/** 请求配置 */
|
|
72
|
+
config: RequestConfig;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* API 端点函数类型(有 body 但可选 - POST/PUT/PATCH 未指定 TBody)
|
|
76
|
+
*/
|
|
77
|
+
export type ApiEndpointWithBodyOptional<TResponse> = {
|
|
78
|
+
(options?: WithBodyOptional): ApiStream<TResponse>;
|
|
79
|
+
/** 请求配置 */
|
|
80
|
+
config: RequestConfig;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* API 端点函数类型(无 body - GET/DELETE)
|
|
97
84
|
*/
|
|
98
|
-
export type
|
|
99
|
-
(options?:
|
|
85
|
+
export type ApiEndpointWithoutBody<TResponse> = {
|
|
86
|
+
(options?: WithoutBodyOptions): ApiStream<TResponse>;
|
|
100
87
|
/** 请求配置 */
|
|
101
88
|
config: RequestConfig;
|
|
102
89
|
};
|
|
90
|
+
/**
|
|
91
|
+
* 检测是否为 unknown 类型
|
|
92
|
+
*/
|
|
93
|
+
type IsUnknown<T> = [unknown] extends [T] ? ([T] extends [unknown] ? true : false) : false;
|
|
94
|
+
/**
|
|
95
|
+
* 检测是否为 void 类型
|
|
96
|
+
*/
|
|
97
|
+
type IsVoid<T> = [T] extends [void] ? ([void] extends [T] ? true : false) : false;
|
|
98
|
+
/**
|
|
99
|
+
* 有 body 的端点类型(根据 TBody 选择必需或可选)
|
|
100
|
+
*/
|
|
101
|
+
export type ApiEndpointWithBody<TResponse, TBody> = IsUnknown<TBody> extends true ? ApiEndpointWithBodyOptional<TResponse> : IsVoid<TBody> extends true ? ApiEndpointWithBodyOptional<TResponse> : ApiEndpointWithBodyRequired<TResponse, TBody>;
|
|
102
|
+
/**
|
|
103
|
+
* API 端点函数类型(泛型版本,用于内部和类型导出)
|
|
104
|
+
*/
|
|
105
|
+
export type ApiEndpoint<TResponse, TBody = unknown, HasBody extends boolean = false> = HasBody extends true ? ApiEndpointWithBody<TResponse, TBody> : ApiEndpointWithoutBody<TResponse>;
|
|
103
106
|
/**
|
|
104
107
|
* API 定义类型(端点集合)
|
|
105
108
|
*/
|
|
106
|
-
export type ApiDefinition = Record<string, ApiEndpoint<unknown,
|
|
109
|
+
export type ApiDefinition = Record<string, ApiEndpoint<unknown, unknown, boolean>>;
|
|
107
110
|
/**
|
|
108
111
|
* 端点选项(构建时配置)
|
|
109
|
-
*
|
|
110
|
-
* @template Path 路径字符串字面量,用于类型推断
|
|
111
112
|
*/
|
|
112
|
-
interface EndpointOptions
|
|
113
|
+
interface EndpointOptions {
|
|
113
114
|
/** 请求路径,支持 {param} 占位符 */
|
|
114
|
-
path:
|
|
115
|
+
path: string;
|
|
115
116
|
/** 请求方法 */
|
|
116
117
|
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
117
118
|
/** 请求头 */
|
|
@@ -123,12 +124,41 @@ interface EndpointOptions<Path extends string = string> {
|
|
|
123
124
|
/** 缓存配置 */
|
|
124
125
|
cache?: CacheConfig;
|
|
125
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* 空 API 类型(空记录,不允许任意属性访问)
|
|
129
|
+
*/
|
|
130
|
+
type EmptyApi = {};
|
|
126
131
|
/**
|
|
127
132
|
* API 构建器类
|
|
128
133
|
*
|
|
134
|
+
* 使用链式调用定义类型安全的 API 端点。
|
|
135
|
+
*
|
|
129
136
|
* @template T 当前已定义的端点类型集合,通过链式调用累积
|
|
137
|
+
*
|
|
138
|
+
* @remarks
|
|
139
|
+
* **类型深度限制**:由于 TypeScript 对深层交叉类型的处理限制,
|
|
140
|
+
* 当单个 builder 链式调用超过 4 个端点时,类型推断可能不够精确。
|
|
141
|
+
*
|
|
142
|
+
* 建议:
|
|
143
|
+
* - 保持单个 builder 的端点数量在 4 个以内以获得最佳类型推断
|
|
144
|
+
* - 如需更多端点,功能仍然正常,但部分类型检查可能被放宽
|
|
145
|
+
* - 可以使用导出的 `ApiEndpointWithBody`、`ApiEndpointWithoutBody` 等类型
|
|
146
|
+
* 来定义自己的接口类型,获得更精确的类型检查
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* const userApi = ApiBuilder.create({ baseUrl: '/api' })
|
|
151
|
+
* .get<User>('getUser', { path: '/users/{id}' })
|
|
152
|
+
* .post<User, CreateUserDTO>('createUser', { path: '/users' })
|
|
153
|
+
* .put<User, UpdateUserDTO>('updateUser', { path: '/users/{id}' })
|
|
154
|
+
* .build()
|
|
155
|
+
*
|
|
156
|
+
* // 类型安全调用
|
|
157
|
+
* const user = await userApi.getUser({ pathParams: { id: '1' } })
|
|
158
|
+
* const newUser = await userApi.createUser({ body: { name: 'John', email: 'j@test.com' } })
|
|
159
|
+
* ```
|
|
130
160
|
*/
|
|
131
|
-
export declare class ApiBuilder<T extends
|
|
161
|
+
export declare class ApiBuilder<T extends object = EmptyApi> {
|
|
132
162
|
private client;
|
|
133
163
|
private endpoints;
|
|
134
164
|
private constructor();
|
|
@@ -137,203 +167,85 @@ export declare class ApiBuilder<T extends ApiDefinition = Record<string, never>>
|
|
|
137
167
|
*
|
|
138
168
|
* @param config HTTP 客户端配置
|
|
139
169
|
* @returns 新的 ApiBuilder 实例
|
|
140
|
-
*
|
|
141
|
-
* @example
|
|
142
|
-
* ```ts
|
|
143
|
-
* const api = ApiBuilder.create({
|
|
144
|
-
* baseUrl: 'https://api.example.com',
|
|
145
|
-
* timeout: 10000,
|
|
146
|
-
* })
|
|
147
|
-
* ```
|
|
148
170
|
*/
|
|
149
|
-
static create(config: HttpClientConfig): ApiBuilder
|
|
171
|
+
static create(config: HttpClientConfig): ApiBuilder<object>;
|
|
150
172
|
/**
|
|
151
173
|
* 从现有 ky 实例创建
|
|
152
174
|
*
|
|
153
175
|
* @param kyInstance 已配置的 ky 实例
|
|
154
176
|
* @param config 可选的额外配置
|
|
155
177
|
* @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
178
|
*/
|
|
163
|
-
static from(kyInstance: ReturnType<typeof ky.create>, config?: Partial<HttpClientConfig>): ApiBuilder
|
|
179
|
+
static from(kyInstance: ReturnType<typeof ky.create>, config?: Partial<HttpClientConfig>): ApiBuilder<object>;
|
|
164
180
|
/**
|
|
165
181
|
* 定义 GET 请求
|
|
166
182
|
*
|
|
167
183
|
* @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
|
-
* ```
|
|
184
|
+
* @template K 端点名称(自动推断)
|
|
182
185
|
*/
|
|
183
|
-
get<TResponse, K extends string = string
|
|
184
|
-
|
|
186
|
+
get<TResponse, const K extends string = string>(name: K, options: Omit<EndpointOptions, 'method'>): ApiBuilder<T & {
|
|
187
|
+
[P in K]: ApiEndpointWithoutBody<TResponse>;
|
|
188
|
+
}>;
|
|
185
189
|
/**
|
|
186
190
|
* 定义 POST 请求
|
|
187
191
|
*
|
|
188
192
|
* @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
|
-
* ```
|
|
193
|
+
* @template TBody 请求体类型
|
|
194
|
+
* @template K 端点名称
|
|
206
195
|
*/
|
|
207
|
-
post<TResponse, TBody =
|
|
196
|
+
post<TResponse, TBody = void, const K extends string = string>(name: K, options: Omit<EndpointOptions, 'method'>): ApiBuilder<T & {
|
|
197
|
+
[P in K]: ApiEndpointWithBody<TResponse, TBody>;
|
|
198
|
+
}>;
|
|
208
199
|
/**
|
|
209
200
|
* 定义 PUT 请求
|
|
210
201
|
*
|
|
211
202
|
* @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
|
-
* ```
|
|
203
|
+
* @template TBody 请求体类型
|
|
204
|
+
* @template K 端点名称
|
|
226
205
|
*/
|
|
227
|
-
put<TResponse, TBody =
|
|
206
|
+
put<TResponse, TBody = void, const K extends string = string>(name: K, options: Omit<EndpointOptions, 'method'>): ApiBuilder<T & {
|
|
207
|
+
[P in K]: ApiEndpointWithBody<TResponse, TBody>;
|
|
208
|
+
}>;
|
|
228
209
|
/**
|
|
229
210
|
* 定义 DELETE 请求
|
|
230
211
|
*
|
|
231
212
|
* @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
|
-
* ```
|
|
213
|
+
* @template K 端点名称
|
|
245
214
|
*/
|
|
246
|
-
delete<TResponse, K extends string = string
|
|
215
|
+
delete<TResponse, const K extends string = string>(name: K, options: Omit<EndpointOptions, 'method'>): ApiBuilder<T & {
|
|
216
|
+
[P in K]: ApiEndpointWithoutBody<TResponse>;
|
|
217
|
+
}>;
|
|
247
218
|
/**
|
|
248
219
|
* 定义 PATCH 请求
|
|
249
220
|
*
|
|
250
221
|
* @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
|
-
* ```
|
|
222
|
+
* @template TBody 请求体类型
|
|
223
|
+
* @template K 端点名称
|
|
265
224
|
*/
|
|
266
|
-
patch<TResponse, TBody =
|
|
225
|
+
patch<TResponse, TBody = void, const K extends string = string>(name: K, options: Omit<EndpointOptions, 'method'>): ApiBuilder<T & {
|
|
226
|
+
[P in K]: ApiEndpointWithBody<TResponse, TBody>;
|
|
227
|
+
}>;
|
|
267
228
|
/**
|
|
268
229
|
* 通用端点定义(内部方法)
|
|
269
|
-
*
|
|
270
|
-
* @template TResponse 响应数据类型
|
|
271
|
-
* @template K 端点名称
|
|
272
|
-
* @template Path 路径字符串
|
|
273
|
-
* @template TBody 请求体类型
|
|
274
|
-
* @template HasBody 是否有请求体
|
|
275
230
|
*/
|
|
276
231
|
private endpoint;
|
|
277
232
|
/**
|
|
278
233
|
* 扩展 ky 实例(添加 hooks 等)
|
|
279
234
|
*
|
|
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
|
-
* ```
|
|
235
|
+
* @param options Ky 配置选项
|
|
236
|
+
* @returns 当前 Builder 实例(保留累积的类型)
|
|
293
237
|
*/
|
|
294
|
-
extend(options: KyOptions):
|
|
238
|
+
extend(options: KyOptions): ApiBuilder<T>;
|
|
295
239
|
/**
|
|
296
240
|
* 构建 API 服务
|
|
297
241
|
*
|
|
298
|
-
* 将所有定义的端点转换为可调用的 API 对象。
|
|
299
|
-
* 每个端点都带有完整的类型信息(响应类型、路径参数、请求体类型)。
|
|
300
|
-
*
|
|
301
242
|
* @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
243
|
*/
|
|
315
244
|
build(): T;
|
|
316
245
|
/**
|
|
317
246
|
* 获取 HTTP 客户端实例(用于高级操作)
|
|
318
|
-
*
|
|
319
|
-
* @returns HttpClient 实例
|
|
320
|
-
*
|
|
321
|
-
* @example
|
|
322
|
-
* ```ts
|
|
323
|
-
* const client = api.getClient()
|
|
324
|
-
* await client.clearCache()
|
|
325
|
-
* ```
|
|
326
247
|
*/
|
|
327
248
|
getClient(): HttpClient;
|
|
328
249
|
}
|
|
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 };
|
|
250
|
+
export {};
|
|
339
251
|
//# 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,58 @@
|
|
|
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
|
+
* - 可以使用导出的 `ApiEndpointWithBody`、`ApiEndpointWithoutBody` 等类型
|
|
49
|
+
* 来定义自己的接口类型,获得更精确的类型检查
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* const userApi = ApiBuilder.create({ baseUrl: '/api' })
|
|
54
|
+
* .get<User>('getUser', { path: '/users/{id}' })
|
|
55
|
+
* .post<User, CreateUserDTO>('createUser', { path: '/users' })
|
|
56
|
+
* .put<User, UpdateUserDTO>('updateUser', { path: '/users/{id}' })
|
|
57
|
+
* .build()
|
|
58
|
+
*
|
|
59
|
+
* // 类型安全调用
|
|
60
|
+
* const user = await userApi.getUser({ pathParams: { id: '1' } })
|
|
61
|
+
* const newUser = await userApi.createUser({ body: { name: 'John', email: 'j@test.com' } })
|
|
62
|
+
* ```
|
|
50
63
|
*/
|
|
51
64
|
export class ApiBuilder {
|
|
52
65
|
constructor(config) {
|
|
@@ -58,14 +71,6 @@ export class ApiBuilder {
|
|
|
58
71
|
*
|
|
59
72
|
* @param config HTTP 客户端配置
|
|
60
73
|
* @returns 新的 ApiBuilder 实例
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
* ```ts
|
|
64
|
-
* const api = ApiBuilder.create({
|
|
65
|
-
* baseUrl: 'https://api.example.com',
|
|
66
|
-
* timeout: 10000,
|
|
67
|
-
* })
|
|
68
|
-
* ```
|
|
69
74
|
*/
|
|
70
75
|
static create(config) {
|
|
71
76
|
return new ApiBuilder(config);
|
|
@@ -76,12 +81,6 @@ export class ApiBuilder {
|
|
|
76
81
|
* @param kyInstance 已配置的 ky 实例
|
|
77
82
|
* @param config 可选的额外配置
|
|
78
83
|
* @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
84
|
*/
|
|
86
85
|
static from(kyInstance, config) {
|
|
87
86
|
const builder = new ApiBuilder({
|
|
@@ -95,46 +94,17 @@ export class ApiBuilder {
|
|
|
95
94
|
* 定义 GET 请求
|
|
96
95
|
*
|
|
97
96
|
* @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
|
-
* ```
|
|
97
|
+
* @template K 端点名称(自动推断)
|
|
112
98
|
*/
|
|
113
|
-
get(name,
|
|
114
|
-
options) {
|
|
99
|
+
get(name, options) {
|
|
115
100
|
return this.endpoint(name, { ...options, method: 'GET' });
|
|
116
101
|
}
|
|
117
102
|
/**
|
|
118
103
|
* 定义 POST 请求
|
|
119
104
|
*
|
|
120
105
|
* @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
|
-
* ```
|
|
106
|
+
* @template TBody 请求体类型
|
|
107
|
+
* @template K 端点名称
|
|
138
108
|
*/
|
|
139
109
|
post(name, options) {
|
|
140
110
|
return this.endpoint(name, { ...options, method: 'POST' });
|
|
@@ -143,20 +113,8 @@ export class ApiBuilder {
|
|
|
143
113
|
* 定义 PUT 请求
|
|
144
114
|
*
|
|
145
115
|
* @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
|
-
* ```
|
|
116
|
+
* @template TBody 请求体类型
|
|
117
|
+
* @template K 端点名称
|
|
160
118
|
*/
|
|
161
119
|
put(name, options) {
|
|
162
120
|
return this.endpoint(name, { ...options, method: 'PUT' });
|
|
@@ -165,19 +123,7 @@ export class ApiBuilder {
|
|
|
165
123
|
* 定义 DELETE 请求
|
|
166
124
|
*
|
|
167
125
|
* @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
|
-
* ```
|
|
126
|
+
* @template K 端点名称
|
|
181
127
|
*/
|
|
182
128
|
delete(name, options) {
|
|
183
129
|
return this.endpoint(name, { ...options, method: 'DELETE' });
|
|
@@ -186,32 +132,14 @@ export class ApiBuilder {
|
|
|
186
132
|
* 定义 PATCH 请求
|
|
187
133
|
*
|
|
188
134
|
* @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
|
-
* ```
|
|
135
|
+
* @template TBody 请求体类型
|
|
136
|
+
* @template K 端点名称
|
|
203
137
|
*/
|
|
204
138
|
patch(name, options) {
|
|
205
139
|
return this.endpoint(name, { ...options, method: 'PATCH' });
|
|
206
140
|
}
|
|
207
141
|
/**
|
|
208
142
|
* 通用端点定义(内部方法)
|
|
209
|
-
*
|
|
210
|
-
* @template TResponse 响应数据类型
|
|
211
|
-
* @template K 端点名称
|
|
212
|
-
* @template Path 路径字符串
|
|
213
|
-
* @template TBody 请求体类型
|
|
214
|
-
* @template HasBody 是否有请求体
|
|
215
143
|
*/
|
|
216
144
|
endpoint(name, options) {
|
|
217
145
|
var _a;
|
|
@@ -224,25 +152,13 @@ export class ApiBuilder {
|
|
|
224
152
|
cache: options.cache,
|
|
225
153
|
};
|
|
226
154
|
this.endpoints.set(name, config);
|
|
227
|
-
// 关键:返回 this,但类型已经累积
|
|
228
155
|
return this;
|
|
229
156
|
}
|
|
230
157
|
/**
|
|
231
158
|
* 扩展 ky 实例(添加 hooks 等)
|
|
232
159
|
*
|
|
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
|
-
* ```
|
|
160
|
+
* @param options Ky 配置选项
|
|
161
|
+
* @returns 当前 Builder 实例(保留累积的类型)
|
|
246
162
|
*/
|
|
247
163
|
extend(options) {
|
|
248
164
|
this.client.extendKy(options);
|
|
@@ -251,27 +167,11 @@ export class ApiBuilder {
|
|
|
251
167
|
/**
|
|
252
168
|
* 构建 API 服务
|
|
253
169
|
*
|
|
254
|
-
* 将所有定义的端点转换为可调用的 API 对象。
|
|
255
|
-
* 每个端点都带有完整的类型信息(响应类型、路径参数、请求体类型)。
|
|
256
|
-
*
|
|
257
170
|
* @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
171
|
*/
|
|
271
172
|
build() {
|
|
272
173
|
const api = {};
|
|
273
174
|
for (const [name, config] of this.endpoints) {
|
|
274
|
-
// 运行时不需要类型检查,类型安全由编译时保证
|
|
275
175
|
const endpoint = ((options) => {
|
|
276
176
|
return this.client.request(config, options);
|
|
277
177
|
});
|
|
@@ -282,14 +182,6 @@ export class ApiBuilder {
|
|
|
282
182
|
}
|
|
283
183
|
/**
|
|
284
184
|
* 获取 HTTP 客户端实例(用于高级操作)
|
|
285
|
-
*
|
|
286
|
-
* @returns HttpClient 实例
|
|
287
|
-
*
|
|
288
|
-
* @example
|
|
289
|
-
* ```ts
|
|
290
|
-
* const client = api.getClient()
|
|
291
|
-
* await client.clearCache()
|
|
292
|
-
* ```
|
|
293
185
|
*/
|
|
294
186
|
getClient() {
|
|
295
187
|
return this.client;
|
package/dist/http/api/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { ApiBuilder, type ApiEndpoint, type ApiDefinition, type
|
|
1
|
+
export { ApiBuilder, type ApiEndpoint, type ApiDefinition, type ApiEndpointWithBody, type ApiEndpointWithoutBody, type ApiEndpointWithBodyRequired, type ApiEndpointWithBodyOptional, type Simplify, } 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,EACH,UAAU,EACV,KAAK,WAAW,EAChB,KAAK,aAAa,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/http/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,UAAU,EACV,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B,EAChC,KAAK,2BAA2B,EAChC,KAAK,QAAQ,GAChB,MAAM,cAAc,CAAA"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ApiBuilder 使用示例
|
|
3
|
+
*
|
|
4
|
+
* 此文件演示 ApiBuilder 的核心特性:
|
|
5
|
+
* 1. 泛型累积 - 链式调用自动累积端点类型
|
|
6
|
+
* 2. Body 类型约束 - POST/PUT/PATCH 请求体类型检查
|
|
7
|
+
* 3. 端点名称字面量 - 调用不存在的端点会编译报错
|
|
8
|
+
* 4. 缓存策略 - CacheMode 控制缓存行为
|
|
9
|
+
* 5. 多次响应 - 支持缓存优先场景下的两次数据返回
|
|
10
|
+
*/
|
|
11
|
+
/** 用户实体 */
|
|
12
|
+
interface User {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
email: string;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
}
|
|
18
|
+
/** 创建用户 DTO */
|
|
19
|
+
interface CreateUserDTO {
|
|
20
|
+
name: string;
|
|
21
|
+
email: string;
|
|
22
|
+
}
|
|
23
|
+
/** 更新用户 DTO */
|
|
24
|
+
interface UpdateUserDTO {
|
|
25
|
+
name?: string;
|
|
26
|
+
email?: string;
|
|
27
|
+
}
|
|
28
|
+
/** 分页响应 */
|
|
29
|
+
interface PaginatedResponse<T> {
|
|
30
|
+
data: T[];
|
|
31
|
+
total: number;
|
|
32
|
+
page: number;
|
|
33
|
+
pageSize: number;
|
|
34
|
+
}
|
|
35
|
+
export declare const userApi: object & {
|
|
36
|
+
[x: string]: ApiEndpointWithoutBody<User[]>;
|
|
37
|
+
} & {
|
|
38
|
+
[x: string]: ApiEndpointWithoutBody<User>;
|
|
39
|
+
} & {
|
|
40
|
+
[x: string]: ApiEndpointWithoutBody<PaginatedResponse<User>>;
|
|
41
|
+
} & {
|
|
42
|
+
[x: string]: import("..").ApiEndpointWithBodyRequired<User, CreateUserDTO>;
|
|
43
|
+
} & {
|
|
44
|
+
[x: string]: import("..").ApiEndpointWithBodyOptional<void>;
|
|
45
|
+
} & {
|
|
46
|
+
[x: string]: import("..").ApiEndpointWithBodyRequired<User, UpdateUserDTO>;
|
|
47
|
+
} & {
|
|
48
|
+
[x: string]: import("..").ApiEndpointWithBodyRequired<User, Partial<UpdateUserDTO>>;
|
|
49
|
+
} & {
|
|
50
|
+
[x: string]: ApiEndpointWithoutBody<void>;
|
|
51
|
+
};
|
|
52
|
+
declare function demo(): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* CacheMode 枚举值说明:
|
|
55
|
+
*
|
|
56
|
+
* - OnlyRemote: 只请求网络,不使用缓存(适用于实时性要求高的数据)
|
|
57
|
+
* - OnlyCache: 只读取缓存,不请求网络(适用于离线场景)
|
|
58
|
+
* - FirstCache: 先返回缓存,再请求网络更新(可能返回两次数据)
|
|
59
|
+
* - FirstRemote: 先请求网络,失败时回退到缓存(网络优先)
|
|
60
|
+
* - IfCache: 有缓存则用缓存,无缓存则请求网络(只返回一次)
|
|
61
|
+
*/
|
|
62
|
+
/**
|
|
63
|
+
* 缓存模式使用示例
|
|
64
|
+
*/
|
|
65
|
+
declare function cacheModeDemo(): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* FirstCache 模式会返回两次数据:
|
|
68
|
+
* 1. 第一次:从缓存返回(如果有缓存),fromCache = true
|
|
69
|
+
* 2. 第二次:从网络返回最新数据,fromCache = false
|
|
70
|
+
*
|
|
71
|
+
* 这种模式非常适合:
|
|
72
|
+
* - 列表页快速展示 + 后台刷新
|
|
73
|
+
* - 详情页先显示旧数据,再更新为最新数据
|
|
74
|
+
* - 提升用户体验,减少白屏时间
|
|
75
|
+
*/
|
|
76
|
+
/**
|
|
77
|
+
* 方式一:使用 for await 处理多次响应
|
|
78
|
+
* 推荐用于需要处理每次响应的场景
|
|
79
|
+
*/
|
|
80
|
+
declare function multiResponseWithForAwait(): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* 方式二:使用 subscribe 处理多次响应
|
|
83
|
+
* 推荐用于 React/Vue 等框架中的响应式更新
|
|
84
|
+
*/
|
|
85
|
+
declare function multiResponseWithSubscribe(): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* 方式三:只需要最终结果时使用 await
|
|
88
|
+
* 注意:await 只返回最后一次响应(网络数据)
|
|
89
|
+
*/
|
|
90
|
+
declare function singleResponseWithAwait(): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* React 组件中的典型用法示例(伪代码)
|
|
93
|
+
*/
|
|
94
|
+
declare function reactUsageExample(): void;
|
|
95
|
+
declare function typeErrors(): void;
|
|
96
|
+
import type { ApiEndpointWithoutBody } from '../api/ApiBuilder';
|
|
97
|
+
/**
|
|
98
|
+
* 类型兼容性验证示例
|
|
99
|
+
*/
|
|
100
|
+
declare function verifyTypeCompatibility(): void;
|
|
101
|
+
export { demo, typeErrors, verifyTypeCompatibility, cacheModeDemo, multiResponseWithForAwait, multiResponseWithSubscribe, singleResponseWithAwait, reactUsageExample, };
|
|
102
|
+
//# 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;;;;;;;;;GASG;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,kBAqDlB;AAMD;;;;;;;;GAQG;AAEH;;GAEG;AACH,iBAAe,aAAa,kBAmC3B;AAMD;;;;;;;;;GASG;AAEH;;;GAGG;AACH,iBAAe,yBAAyB,kBAiBvC;AAED;;;GAGG;AACH,iBAAe,0BAA0B,kBA0BxC;AAED;;;GAGG;AACH,iBAAe,uBAAuB,kBAUrC;AAED;;GAEG;AACH,iBAAS,iBAAiB,SA4CzB;AAoBD,iBAAS,UAAU,SAYlB;AAMD,OAAO,KAAK,EAAuB,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAcpF;;GAEG;AACH,iBAAS,uBAAuB,SAkB/B;AAMD,OAAO,EACH,IAAI,EACJ,UAAU,EACV,uBAAuB,EACvB,aAAa,EACb,yBAAyB,EACzB,0BAA0B,EAC1B,uBAAuB,EACvB,iBAAiB,GACpB,CAAA"}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ApiBuilder 使用示例
|
|
3
|
+
*
|
|
4
|
+
* 此文件演示 ApiBuilder 的核心特性:
|
|
5
|
+
* 1. 泛型累积 - 链式调用自动累积端点类型
|
|
6
|
+
* 2. Body 类型约束 - POST/PUT/PATCH 请求体类型检查
|
|
7
|
+
* 3. 端点名称字面量 - 调用不存在的端点会编译报错
|
|
8
|
+
* 4. 缓存策略 - CacheMode 控制缓存行为
|
|
9
|
+
* 5. 多次响应 - 支持缓存优先场景下的两次数据返回
|
|
10
|
+
*/
|
|
11
|
+
import { ApiBuilder } from '../api/ApiBuilder';
|
|
12
|
+
import { CacheMode } from '../models/CacheMode';
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// 创建 API 服务
|
|
15
|
+
// ============================================================================
|
|
16
|
+
export const userApi = ApiBuilder.create({
|
|
17
|
+
baseUrl: 'https://api.example.com',
|
|
18
|
+
timeout: 10000,
|
|
19
|
+
defaultCache: {
|
|
20
|
+
mode: CacheMode.FirstRemote,
|
|
21
|
+
ttl: 5 * 60 * 1000,
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
// GET 请求 - 无路径参数
|
|
25
|
+
.get('getUsers', {
|
|
26
|
+
path: '/users',
|
|
27
|
+
cache: { mode: CacheMode.FirstCache },
|
|
28
|
+
})
|
|
29
|
+
// GET 请求 - 有路径参数
|
|
30
|
+
.get('getUser', {
|
|
31
|
+
path: '/users/{id}',
|
|
32
|
+
cache: { mode: CacheMode.IfCache, ttl: 60000 },
|
|
33
|
+
})
|
|
34
|
+
// GET 请求 - 分页
|
|
35
|
+
.get('getUsersPaginated', {
|
|
36
|
+
path: '/users/paginated',
|
|
37
|
+
})
|
|
38
|
+
// POST 请求 - 有 body 类型
|
|
39
|
+
.post('createUser', {
|
|
40
|
+
path: '/users',
|
|
41
|
+
})
|
|
42
|
+
// POST 请求 - 无 body 类型(触发器)
|
|
43
|
+
.post('activateUser', {
|
|
44
|
+
path: '/users/{id}/activate',
|
|
45
|
+
})
|
|
46
|
+
// PUT 请求 - 完整更新
|
|
47
|
+
.put('updateUser', {
|
|
48
|
+
path: '/users/{id}',
|
|
49
|
+
})
|
|
50
|
+
// PATCH 请求 - 部分更新
|
|
51
|
+
.patch('patchUser', {
|
|
52
|
+
path: '/users/{id}',
|
|
53
|
+
})
|
|
54
|
+
// DELETE 请求
|
|
55
|
+
.delete('deleteUser', {
|
|
56
|
+
path: '/users/{id}',
|
|
57
|
+
})
|
|
58
|
+
// 扩展 ky 实例(添加拦截器)
|
|
59
|
+
.extend({
|
|
60
|
+
hooks: {
|
|
61
|
+
beforeRequest: [
|
|
62
|
+
(request) => {
|
|
63
|
+
const token = 'mock-token';
|
|
64
|
+
request.headers.set('Authorization', `Bearer ${token}`);
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
.build();
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// 使用示例 - 类型安全调用
|
|
72
|
+
// ============================================================================
|
|
73
|
+
async function demo() {
|
|
74
|
+
// ✅ GET 无路径参数
|
|
75
|
+
const users = await userApi.getUsers();
|
|
76
|
+
console.log('Users:', users);
|
|
77
|
+
// ✅ GET 有路径参数
|
|
78
|
+
const user = await userApi.getUser({
|
|
79
|
+
pathParams: { id: '123' },
|
|
80
|
+
});
|
|
81
|
+
console.log('User:', user);
|
|
82
|
+
// ✅ GET 带 query 参数
|
|
83
|
+
const paginatedUsers = await userApi.getUsersPaginated({
|
|
84
|
+
query: { page: 1, pageSize: 10 },
|
|
85
|
+
});
|
|
86
|
+
console.log('Paginated:', paginatedUsers);
|
|
87
|
+
// ✅ POST 有 body 类型约束
|
|
88
|
+
const newUser = await userApi.createUser({
|
|
89
|
+
body: { name: 'John', email: 'john@example.com' },
|
|
90
|
+
});
|
|
91
|
+
console.log('Created:', newUser);
|
|
92
|
+
// ✅ POST 无 body(触发器)
|
|
93
|
+
await userApi.activateUser({
|
|
94
|
+
pathParams: { id: '123' },
|
|
95
|
+
});
|
|
96
|
+
// ✅ PUT 完整更新
|
|
97
|
+
const updatedUser = await userApi.updateUser({
|
|
98
|
+
pathParams: { id: '123' },
|
|
99
|
+
body: { name: 'John Updated', email: 'john.updated@example.com' },
|
|
100
|
+
});
|
|
101
|
+
console.log('Updated:', updatedUser);
|
|
102
|
+
// ✅ PATCH 部分更新
|
|
103
|
+
const patchedUser = await userApi.patchUser({
|
|
104
|
+
pathParams: { id: '123' },
|
|
105
|
+
body: { name: 'John Patched' },
|
|
106
|
+
});
|
|
107
|
+
console.log('Patched:', patchedUser);
|
|
108
|
+
// ✅ DELETE
|
|
109
|
+
await userApi.deleteUser({
|
|
110
|
+
pathParams: { id: '123' },
|
|
111
|
+
});
|
|
112
|
+
// ✅ 覆盖缓存配置
|
|
113
|
+
const freshUser = await userApi.getUser({
|
|
114
|
+
pathParams: { id: '123' },
|
|
115
|
+
cache: { mode: CacheMode.OnlyRemote },
|
|
116
|
+
});
|
|
117
|
+
console.log('Fresh:', freshUser);
|
|
118
|
+
}
|
|
119
|
+
// ============================================================================
|
|
120
|
+
// CacheMode 缓存模式详解
|
|
121
|
+
// ============================================================================
|
|
122
|
+
/**
|
|
123
|
+
* CacheMode 枚举值说明:
|
|
124
|
+
*
|
|
125
|
+
* - OnlyRemote: 只请求网络,不使用缓存(适用于实时性要求高的数据)
|
|
126
|
+
* - OnlyCache: 只读取缓存,不请求网络(适用于离线场景)
|
|
127
|
+
* - FirstCache: 先返回缓存,再请求网络更新(可能返回两次数据)
|
|
128
|
+
* - FirstRemote: 先请求网络,失败时回退到缓存(网络优先)
|
|
129
|
+
* - IfCache: 有缓存则用缓存,无缓存则请求网络(只返回一次)
|
|
130
|
+
*/
|
|
131
|
+
/**
|
|
132
|
+
* 缓存模式使用示例
|
|
133
|
+
*/
|
|
134
|
+
async function cacheModeDemo() {
|
|
135
|
+
// 1. OnlyRemote - 始终请求网络,忽略缓存
|
|
136
|
+
// 适用场景:提交表单、实时数据
|
|
137
|
+
const realTimeData = await userApi.getUser({
|
|
138
|
+
pathParams: { id: '123' },
|
|
139
|
+
cache: { mode: CacheMode.OnlyRemote },
|
|
140
|
+
});
|
|
141
|
+
console.log('实时数据:', realTimeData);
|
|
142
|
+
// 2. OnlyCache - 只读缓存,不请求网络
|
|
143
|
+
// 适用场景:离线模式、快速展示历史数据
|
|
144
|
+
try {
|
|
145
|
+
const cachedData = await userApi.getUser({
|
|
146
|
+
pathParams: { id: '123' },
|
|
147
|
+
cache: { mode: CacheMode.OnlyCache },
|
|
148
|
+
});
|
|
149
|
+
console.log('缓存数据:', cachedData);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
console.log('无缓存数据');
|
|
153
|
+
}
|
|
154
|
+
// 3. IfCache - 有缓存用缓存,无缓存请求网络(只返回一次)
|
|
155
|
+
// 适用场景:静态资源、不常变化的配置
|
|
156
|
+
const configData = await userApi.getUsers({
|
|
157
|
+
cache: { mode: CacheMode.IfCache, ttl: 60 * 60 * 1000 }, // 1小时
|
|
158
|
+
});
|
|
159
|
+
console.log('配置数据:', configData);
|
|
160
|
+
// 4. FirstRemote - 优先网络,失败回退缓存(只返回一次)
|
|
161
|
+
// 适用场景:希望获取最新数据,但网络不稳定时有兜底
|
|
162
|
+
const freshData = await userApi.getUser({
|
|
163
|
+
pathParams: { id: '123' },
|
|
164
|
+
cache: { mode: CacheMode.FirstRemote },
|
|
165
|
+
});
|
|
166
|
+
console.log('最新数据:', freshData);
|
|
167
|
+
}
|
|
168
|
+
// ============================================================================
|
|
169
|
+
// 多次响应处理 - FirstCache 模式的核心特性
|
|
170
|
+
// ============================================================================
|
|
171
|
+
/**
|
|
172
|
+
* FirstCache 模式会返回两次数据:
|
|
173
|
+
* 1. 第一次:从缓存返回(如果有缓存),fromCache = true
|
|
174
|
+
* 2. 第二次:从网络返回最新数据,fromCache = false
|
|
175
|
+
*
|
|
176
|
+
* 这种模式非常适合:
|
|
177
|
+
* - 列表页快速展示 + 后台刷新
|
|
178
|
+
* - 详情页先显示旧数据,再更新为最新数据
|
|
179
|
+
* - 提升用户体验,减少白屏时间
|
|
180
|
+
*/
|
|
181
|
+
/**
|
|
182
|
+
* 方式一:使用 for await 处理多次响应
|
|
183
|
+
* 推荐用于需要处理每次响应的场景
|
|
184
|
+
*/
|
|
185
|
+
async function multiResponseWithForAwait() {
|
|
186
|
+
console.log('=== for await 方式 ===');
|
|
187
|
+
for await (const { data, fromCache } of userApi.getUser({
|
|
188
|
+
pathParams: { id: '123' },
|
|
189
|
+
cache: { mode: CacheMode.FirstCache },
|
|
190
|
+
})) {
|
|
191
|
+
if (fromCache) {
|
|
192
|
+
console.log('📦 缓存数据(快速展示):', data);
|
|
193
|
+
// 可以先用缓存数据渲染 UI
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
console.log('🌐 网络数据(最新):', data);
|
|
197
|
+
// 用最新数据更新 UI
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
console.log('✅ 请求完成');
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* 方式二:使用 subscribe 处理多次响应
|
|
204
|
+
* 推荐用于 React/Vue 等框架中的响应式更新
|
|
205
|
+
*/
|
|
206
|
+
async function multiResponseWithSubscribe() {
|
|
207
|
+
console.log('=== subscribe 方式 ===');
|
|
208
|
+
await userApi
|
|
209
|
+
.getUser({
|
|
210
|
+
pathParams: { id: '123' },
|
|
211
|
+
cache: { mode: CacheMode.FirstCache },
|
|
212
|
+
})
|
|
213
|
+
.subscribe({
|
|
214
|
+
onData: (data, fromCache) => {
|
|
215
|
+
if (fromCache) {
|
|
216
|
+
console.log('📦 缓存数据:', data);
|
|
217
|
+
// setState({ user: data, loading: false })
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
console.log('🌐 网络数据:', data);
|
|
221
|
+
// setState({ user: data, loading: false, isStale: false })
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
onError: (error) => {
|
|
225
|
+
console.error('❌ 请求失败:', error);
|
|
226
|
+
// setState({ error, loading: false })
|
|
227
|
+
},
|
|
228
|
+
onComplete: () => {
|
|
229
|
+
console.log('✅ 请求完成');
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* 方式三:只需要最终结果时使用 await
|
|
235
|
+
* 注意:await 只返回最后一次响应(网络数据)
|
|
236
|
+
*/
|
|
237
|
+
async function singleResponseWithAwait() {
|
|
238
|
+
console.log('=== await 方式(只获取最终结果)===');
|
|
239
|
+
// 直接 await 只会返回最终的网络数据
|
|
240
|
+
const user = await userApi.getUser({
|
|
241
|
+
pathParams: { id: '123' },
|
|
242
|
+
cache: { mode: CacheMode.FirstCache },
|
|
243
|
+
});
|
|
244
|
+
console.log('最终数据:', user);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* React 组件中的典型用法示例(伪代码)
|
|
248
|
+
*/
|
|
249
|
+
function reactUsageExample() {
|
|
250
|
+
/*
|
|
251
|
+
// React 组件示例
|
|
252
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
253
|
+
const [user, setUser] = useState<User | null>(null)
|
|
254
|
+
const [isStale, setIsStale] = useState(false)
|
|
255
|
+
const [loading, setLoading] = useState(true)
|
|
256
|
+
|
|
257
|
+
useEffect(() => {
|
|
258
|
+
let cancelled = false
|
|
259
|
+
|
|
260
|
+
// 使用 subscribe 处理多次响应
|
|
261
|
+
userApi.getUser({
|
|
262
|
+
pathParams: { id: userId },
|
|
263
|
+
cache: { mode: CacheMode.FirstCache }
|
|
264
|
+
}).subscribe({
|
|
265
|
+
onData: (data, fromCache) => {
|
|
266
|
+
if (cancelled) return
|
|
267
|
+
setUser(data)
|
|
268
|
+
setIsStale(fromCache) // 标记是否为陈旧数据
|
|
269
|
+
setLoading(false)
|
|
270
|
+
},
|
|
271
|
+
onError: (error) => {
|
|
272
|
+
if (cancelled) return
|
|
273
|
+
console.error(error)
|
|
274
|
+
setLoading(false)
|
|
275
|
+
}
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
return () => { cancelled = true }
|
|
279
|
+
}, [userId])
|
|
280
|
+
|
|
281
|
+
if (loading) return <div>加载中...</div>
|
|
282
|
+
if (!user) return <div>用户不存在</div>
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<div>
|
|
286
|
+
{isStale && <span className="badge">数据更新中...</span>}
|
|
287
|
+
<h1>{user.name}</h1>
|
|
288
|
+
<p>{user.email}</p>
|
|
289
|
+
</div>
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
*/
|
|
293
|
+
}
|
|
294
|
+
// ============================================================================
|
|
295
|
+
// 类型错误示例
|
|
296
|
+
// ============================================================================
|
|
297
|
+
/**
|
|
298
|
+
* 类型安全验证
|
|
299
|
+
*
|
|
300
|
+
* 注意:由于 TypeScript 对深层交叉类型的处理限制,
|
|
301
|
+
* 当链式调用超过 4 个端点时,类型推断可能不够精确。
|
|
302
|
+
* 建议:保持单个 builder 的端点数量在 4 个以内,或分批构建。
|
|
303
|
+
*
|
|
304
|
+
* 以下是简化版本的类型安全验证(2-3 个端点)
|
|
305
|
+
*/
|
|
306
|
+
const simpleApi = ApiBuilder.create({ baseUrl: 'http://test' })
|
|
307
|
+
.get('getUser', { path: '/users/{id}' })
|
|
308
|
+
.post('createUser', { path: '/users' })
|
|
309
|
+
.build();
|
|
310
|
+
function typeErrors() {
|
|
311
|
+
// ❌ body 类型不匹配(缺少 email)
|
|
312
|
+
// @ts-expect-error email is required
|
|
313
|
+
simpleApi.createUser({ body: { name: 'John' } });
|
|
314
|
+
// ❌ body 类型不匹配(错误的属性)
|
|
315
|
+
// @ts-expect-error wrong property
|
|
316
|
+
simpleApi.createUser({ body: { wrong: 'x' } });
|
|
317
|
+
// ❌ GET 请求不应该有 body
|
|
318
|
+
// @ts-expect-error body not allowed for GET
|
|
319
|
+
simpleApi.getUser({ pathParams: { id: '1' }, body: {} });
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* 类型兼容性验证示例
|
|
323
|
+
*/
|
|
324
|
+
function verifyTypeCompatibility() {
|
|
325
|
+
const api = ApiBuilder.create({ baseUrl: 'http://test' })
|
|
326
|
+
.get('getUser', { path: '/users/{id}' })
|
|
327
|
+
.get('getUsers', { path: '/users' })
|
|
328
|
+
.post('createUser', { path: '/users' })
|
|
329
|
+
.put('updateUser', { path: '/users/{id}' })
|
|
330
|
+
.delete('deleteUser', { path: '/users/{id}' })
|
|
331
|
+
.build();
|
|
332
|
+
// ✅ 使用类型断言确保 API 符合定义
|
|
333
|
+
// 注意:由于 TypeScript 深层交叉类型限制,需要通过 unknown 进行断言
|
|
334
|
+
const typedApi = api;
|
|
335
|
+
// 现在可以获得精确的类型检查
|
|
336
|
+
typedApi.createUser({ body: { name: 'John', email: 'john@test.com' } });
|
|
337
|
+
typedApi.getUser({ pathParams: { id: '1' } });
|
|
338
|
+
console.log('Typed API:', typedApi);
|
|
339
|
+
}
|
|
340
|
+
// ============================================================================
|
|
341
|
+
// 导出
|
|
342
|
+
// ============================================================================
|
|
343
|
+
export { demo, typeErrors, verifyTypeCompatibility, cacheModeDemo, multiResponseWithForAwait, multiResponseWithSubscribe, singleResponseWithAwait, reactUsageExample, };
|
package/dist/http/index.d.ts
CHANGED
|
@@ -38,5 +38,5 @@
|
|
|
38
38
|
export { CacheMode, type CacheEntry, type ApiResult, type ApiCallback, type CacheConfig, type CacheKeyRequest, type HttpClientConfig, type RequestConfig, type ApiOptions, type DiskLruCacheConfig, type HttpCacheConfig, type CacheInterceptor, type CacheInterceptorContext, type CacheInterceptorResult, } from './types';
|
|
39
39
|
export { DiskLruCache, HttpCache } from './cache';
|
|
40
40
|
export { HttpClient, ApiStream } from './client';
|
|
41
|
-
export { ApiBuilder, type ApiEndpoint, type ApiDefinition } from './api';
|
|
41
|
+
export { ApiBuilder, type ApiEndpoint, type ApiDefinition, type ApiEndpointWithBody, type ApiEndpointWithoutBody, type ApiEndpointWithBodyRequired, type ApiEndpointWithBodyOptional, type Simplify, } from './api';
|
|
42
42
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/http/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/http/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAGH,OAAO,EACH,SAAS,EACT,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,eAAe,EAEpB,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,GAC9B,MAAM,SAAS,CAAA;AAGhB,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAGjD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAGhD,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/http/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAGH,OAAO,EACH,SAAS,EACT,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,eAAe,EAEpB,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,GAC9B,MAAM,SAAS,CAAA;AAGhB,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAGjD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAGhD,OAAO,EACH,UAAU,EACV,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B,EAChC,KAAK,2BAA2B,EAChC,KAAK,QAAQ,GAChB,MAAM,OAAO,CAAA"}
|
package/dist/http/index.js
CHANGED