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.
@@ -4,39 +4,30 @@
4
4
  * 使用 Builder 模式定义 API,类似 Android Retrofit。
5
5
  * 采用泛型累积模式,每次 .get() / .post() 调用都返回带有累积类型的新 Builder。
6
6
  *
7
- * ## 类型安全
7
+ * ## 类型安全特性
8
8
  *
9
- * - 每个端点名称保留字面量类型(如 `'getUser'`)
10
- * - 链式调用累积所有端点类型:`T & Record<K, ApiEndpoint<TResponse>>`
11
- * - `build()` 后返回完整类型安全的 API 对象
12
- * - 调用不存在的端点会产生编译错误
9
+ * - **端点名称字面量**:每个端点名称保留字面量类型(如 `'getUser'`)
10
+ * - **泛型累积**:链式调用累积所有端点类型 `T & Record<K, ApiEndpoint<...>>`
11
+ * - **Body 类型约束**:POST/PUT/PATCH 支持指定请求体类型
13
12
  *
14
13
  * @example
15
14
  * ```ts
16
- * // 定义 API
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' } }) // ApiStream<User>
30
- * userApi.getUsers() // ApiStream<User[]>
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: Property 'unknownMethod' does not exist
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 ApiEndpoint<TResponse> = {
50
- (options?: ApiOptions): ApiStream<TResponse>;
78
+ export type ApiEndpointWithoutBody<TResponse> = {
79
+ (options?: WithoutBodyOptions): ApiStream<TResponse>;
51
80
  /** 请求配置 */
52
81
  config: RequestConfig;
53
82
  };
54
83
  /**
55
- * API 定义类型
84
+ * 检测是否为 unknown 类型
85
+ */
86
+ type IsUnknown<T> = [unknown] extends [T] ? ([T] extends [unknown] ? true : false) : false;
87
+ /**
88
+ * 检测是否为 void 类型
56
89
  */
57
- export type ApiDefinition = Record<string, ApiEndpoint<unknown>>;
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 ApiDefinition = Record<string, never>> {
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
- * @param name 端点名称(K 从参数自动推断,无需手动指定)
173
+ *
174
+ * @template TResponse 响应数据类型
175
+ * @template K 端点名称(自动推断)
93
176
  */
94
- get<K extends string, TResponse>(name: K, options: Omit<EndpointOptions, 'method'>): ApiBuilder<T & Record<K, ApiEndpoint<TResponse>>>;
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
- * @param name 端点名称(K 从参数自动推断,无需手动指定)
182
+ *
183
+ * @template TResponse 响应数据类型
184
+ * @template TBody 请求体类型
185
+ * @template K 端点名称
98
186
  */
99
- post<K extends string, TResponse>(name: K, options: Omit<EndpointOptions, 'method'>): ApiBuilder<T & Record<K, ApiEndpoint<TResponse>>>;
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
- * @param name 端点名称(K 从参数自动推断,无需手动指定)
192
+ *
193
+ * @template TResponse 响应数据类型
194
+ * @template TBody 请求体类型
195
+ * @template K 端点名称
103
196
  */
104
- put<K extends string, TResponse>(name: K, options: Omit<EndpointOptions, 'method'>): ApiBuilder<T & Record<K, ApiEndpoint<TResponse>>>;
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
- * @param name 端点名称(K 从参数自动推断,无需手动指定)
202
+ *
203
+ * @template TResponse 响应数据类型
204
+ * @template K 端点名称
108
205
  */
109
- delete<K extends string, TResponse>(name: K, options: Omit<EndpointOptions, 'method'>): ApiBuilder<T & Record<K, ApiEndpoint<TResponse>>>;
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
- * @param name 端点名称(K 从参数自动推断,无需手动指定)
211
+ *
212
+ * @template TResponse 响应数据类型
213
+ * @template TBody 请求体类型
214
+ * @template K 端点名称
113
215
  */
114
- patch<K extends string, TResponse>(name: K, options: Omit<EndpointOptions, 'method'>): ApiBuilder<T & Record<K, ApiEndpoint<TResponse>>>;
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): this;
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":"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"}
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
- * - 每个端点名称保留字面量类型(如 `'getUser'`)
10
- * - 链式调用累积所有端点类型:`T & Record<K, ApiEndpoint<TResponse>>`
11
- * - `build()` 后返回完整类型安全的 API 对象
12
- * - 调用不存在的端点会产生编译错误
9
+ * - **端点名称字面量**:每个端点名称保留字面量类型(如 `'getUser'`)
10
+ * - **泛型累积**:链式调用累积所有端点类型 `T & Record<K, ApiEndpoint<...>>`
11
+ * - **Body 类型约束**:POST/PUT/PATCH 支持指定请求体类型
13
12
  *
14
13
  * @example
15
14
  * ```ts
16
- * // 定义 API
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' } }) // ApiStream<User>
30
- * userApi.getUsers() // ApiStream<User[]>
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: Property 'unknownMethod' does not exist
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
- * @param name 端点名称(K 从参数自动推断,无需手动指定)
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
- * @param name 端点名称(K 从参数自动推断,无需手动指定)
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
- * @param name 端点名称(K 从参数自动推断,无需手动指定)
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
- * @param name 端点名称(K 从参数自动推断,无需手动指定)
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
- * @param name 端点名称(K 从参数自动推断,无需手动指定)
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xxf_react",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "dist/index.js",