@longzai-intelligence-transport/http-adapter-react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # @longzai-intelligence-transport/http-adapter-react
2
+
3
+ React Hooks 适配器,基于 SWR 提供类型安全的 Query、Mutation、Infinite Hook 工厂。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ bun add @longzai-intelligence-transport/http-adapter-react
9
+ ```
10
+
11
+ ## 功能特性
12
+
13
+ - **Query Hook 工厂** - 创建类型安全的数据查询 Hook
14
+ - **Mutation Hook 工厂** - 创建类型安全的数据变更 Hook
15
+ - **Infinite Hook 工厂** - 创建类型安全的无限滚动 Hook
16
+ - **完整的 TypeScript 支持** - 自动推断参数和返回类型
17
+
18
+ ## 依赖
19
+
20
+ - `react` - React 框架(peer dependency,>=18.0.0)
21
+ - `swr` - SWR 数据请求库(peer dependency,>=2.0.0)
22
+ - `zod` - Schema 验证(peer dependency)
23
+ - `@longzai-intelligence-transport/http-core` - 核心模块
24
+
25
+ ## 使用示例
26
+
27
+ ### Query Hook
28
+
29
+ ```typescript
30
+ import { createQueryHook } from '@longzai-intelligence-transport/http-adapter-react';
31
+
32
+ const useGetUser = createQueryHook(getUserApi, 'user');
33
+ const { data, error, isLoading } = useGetUser({ id: '123' });
34
+ ```
35
+
36
+ ### Mutation Hook
37
+
38
+ ```typescript
39
+ import { createMutationHook } from '@longzai-intelligence-transport/http-adapter-react';
40
+
41
+ const useCreateUser = createMutationHook(createUserApi);
42
+ const { trigger, isMutating } = useCreateUser();
43
+ await trigger({ body: { name: 'John' } });
44
+ ```
45
+
46
+ ### Infinite Hook
47
+
48
+ ```typescript
49
+ import { createInfiniteHook } from '@longzai-intelligence-transport/http-adapter-react';
50
+
51
+ const useUserList = createInfiniteHook(getUserListApi);
52
+ const { data, size, setSize, isLoadingMore } = useUserList();
53
+ ```
54
+
55
+ ## 导出模块
56
+
57
+ ### Query Hook
58
+
59
+ - `createQueryHook` - 创建查询 Hook 工厂函数
60
+ - `QueryHookFunction` - 查询 Hook 函数类型
61
+ - `QueryHookReturn` - 查询 Hook 返回类型
62
+ - `QueryHookNoParams` - 无参数查询 Hook 类型
63
+ - `QueryHookWithParams` - 带路径参数查询 Hook 类型
64
+ - `QueryHookWithQuery` - 带查询参数 Hook 类型
65
+ - `QueryHookWithParamsAndQuery` - 带路径和查询参数 Hook 类型
66
+
67
+ ### Mutation Hook
68
+
69
+ - `createMutationHook` - 创建变更 Hook 工厂函数
70
+ - `MutationHookFunction` - 变更 Hook 函数类型
71
+ - `MutationHookReturn` - 变更 Hook 返回类型
72
+ - `MutationTriggerArgs` - 触发器参数类型
73
+ - `MutationTriggerNoArgs` - 无参数触发器类型
74
+ - `MutationTriggerWithParams` - 带路径参数触发器类型
75
+ - `MutationTriggerWithBody` - 带请求体触发器类型
76
+ - `MutationTriggerWithParamsAndBody` - 带路径参数和请求体触发器类型
77
+
78
+ ### Infinite Hook
79
+
80
+ - `createInfiniteHook` - 创建无限滚动 Hook 工厂函数
81
+ - `InfiniteHookFunction` - 无限滚动 Hook 函数类型
82
+ - `InfiniteHookReturn` - 无限滚动 Hook 返回类型
83
+ - `ExtractPageItem` - 提取分页项类型
84
+
85
+ ## 许可证
86
+
87
+ UNLICENSED
@@ -0,0 +1,268 @@
1
+ import { SWRConfiguration, SWRResponse } from "swr";
2
+ import { ApiPaginatedListData, ApiResponse, CursorPaginatedResult, HttpClient, InferRouteBody, InferRouteParams, InferRouteQuery, InferRouteResponse, MockRequest, MockRequestContext, RouteDefinition, createConfigurableMockHttpClient, createDelayedMockHttpClient, createErrorMockHttpClient, createMockHttpClient, createRouteMock } from "@longzai-intelligence-transport/http-core";
3
+ import { SWRInfiniteConfiguration, SWRInfiniteResponse } from "swr/infinite";
4
+
5
+ //#region src/create-query-hook.factory.d.ts
6
+ /**
7
+ * Query Hook 返回类型
8
+ *
9
+ * @typeParam TRoute - 路由定义类型
10
+ */
11
+ type QueryHookReturn<TRoute extends RouteDefinition> = SWRResponse<ApiResponse<InferRouteResponse<TRoute>>>;
12
+ /**
13
+ * Query Hook 函数类型 - 无参数
14
+ *
15
+ * @typeParam TRoute - 路由定义类型
16
+ */
17
+ type QueryHookNoParams<TRoute extends RouteDefinition> = (config?: SWRConfiguration<ApiResponse<InferRouteResponse<TRoute>>>) => QueryHookReturn<TRoute>;
18
+ /**
19
+ * Query Hook 函数类型 - 只有 params
20
+ *
21
+ * @typeParam TRoute - 路由定义类型
22
+ */
23
+ type QueryHookWithParams<TRoute extends RouteDefinition> = (params: InferRouteParams<TRoute>, config?: SWRConfiguration<ApiResponse<InferRouteResponse<TRoute>>>) => QueryHookReturn<TRoute>;
24
+ /**
25
+ * Query Hook 函数类型 - 只有 query
26
+ *
27
+ * @typeParam TRoute - 路由定义类型
28
+ */
29
+ type QueryHookWithQuery<TRoute extends RouteDefinition> = (query?: InferRouteQuery<TRoute>, config?: SWRConfiguration<ApiResponse<InferRouteResponse<TRoute>>>) => QueryHookReturn<TRoute>;
30
+ /**
31
+ * Query Hook 函数类型 - params + query
32
+ *
33
+ * @typeParam TRoute - 路由定义类型
34
+ */
35
+ type QueryHookWithParamsAndQuery<TRoute extends RouteDefinition> = (params: InferRouteParams<TRoute>, query?: InferRouteQuery<TRoute>, config?: SWRConfiguration<ApiResponse<InferRouteResponse<TRoute>>>) => QueryHookReturn<TRoute>;
36
+ /**
37
+ * 根据路由定义推断 Query Hook 函数类型
38
+ *
39
+ * 根据路由是否包含 params 和 query,自动推断对应的 Hook 签名
40
+ *
41
+ * @typeParam TRoute - 路由定义类型
42
+ */
43
+ type QueryHookFunction<TRoute extends RouteDefinition> = InferRouteParams<TRoute> extends undefined ? InferRouteQuery<TRoute> extends undefined ? QueryHookNoParams<TRoute> : QueryHookWithQuery<TRoute> : InferRouteQuery<TRoute> extends undefined ? QueryHookWithParams<TRoute> : QueryHookWithParamsAndQuery<TRoute>;
44
+ /**
45
+ * 创建 Query Hook 工厂
46
+ *
47
+ * 从路由定义自动创建类型安全的 SWR Query Hook。仅适用于 GET 请求。
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * import { createQueryHook } from '@longzai-intelligence-transport/http-adapter-react';
52
+ * import { categoryRoutes } from './routes';
53
+ * import { httpClient } from './http-client';
54
+ *
55
+ * const useCategoryList = createQueryHook(categoryRoutes.list, httpClient);
56
+ * const useCategoryDetail = createQueryHook(categoryRoutes.detail, httpClient);
57
+ *
58
+ * // 组件中使用
59
+ * function CategoryPage() {
60
+ * const { data, isLoading } = useCategoryList({ enabled: true });
61
+ * const categories = data?.data?.items ?? [];
62
+ * }
63
+ * ```;
64
+ *
65
+ * @typeParam TRoute - 路由定义类型
66
+ * @param route - 路由定义
67
+ * @param client - HTTP 客户端实例
68
+ * @returns 类型安全的 Query Hook
69
+ * @throws {@link Error} 当路由配置不合法时抛出错误
70
+ */
71
+ declare function createQueryHook<TRoute extends RouteDefinition>(route: TRoute, client: HttpClient): QueryHookFunction<TRoute>;
72
+ //#endregion
73
+ //#region src/create-mutation-hook.factory.types.d.ts
74
+ /**
75
+ * Mutation Hook 返回类型
76
+ *
77
+ * @typeParam TRoute - 路由定义类型
78
+ */
79
+ type MutationHookReturn<TRoute extends RouteDefinition> = {
80
+ /**
81
+ * 触发请求
82
+ */
83
+ trigger: (...args: MutationTriggerArgs<TRoute>) => Promise<ApiResponse<InferRouteResponse<TRoute>>>;
84
+ /**
85
+ * 是否正在触发
86
+ */
87
+ isMutating: boolean;
88
+ /**
89
+ * 响应数据
90
+ */
91
+ data: ApiResponse<InferRouteResponse<TRoute>> | undefined;
92
+ /**
93
+ * 错误信息
94
+ */
95
+ error: Error | undefined;
96
+ /**
97
+ * 重置状态
98
+ */
99
+ reset: () => void;
100
+ };
101
+ /**
102
+ * Mutation trigger 参数类型 - 无参数
103
+ */
104
+ type MutationTriggerNoArgs = [];
105
+ /**
106
+ * Mutation trigger 参数类型 - 只有 params
107
+ *
108
+ * @typeParam TRoute - 路由定义类型
109
+ */
110
+ type MutationTriggerWithParams<TRoute extends RouteDefinition> = [params: InferRouteParams<TRoute>];
111
+ /**
112
+ * Mutation trigger 参数类型 - 只有 body
113
+ *
114
+ * @typeParam TRoute - 路由定义类型
115
+ */
116
+ type MutationTriggerWithBody<TRoute extends RouteDefinition> = [body: InferRouteBody<TRoute>];
117
+ /**
118
+ * Mutation trigger 参数类型 - params + body
119
+ *
120
+ * @typeParam TRoute - 路由定义类型
121
+ */
122
+ type MutationTriggerWithParamsAndBody<TRoute extends RouteDefinition> = [params: InferRouteParams<TRoute>, body: InferRouteBody<TRoute>];
123
+ /**
124
+ * 根据路由定义推断 Mutation trigger 参数类型
125
+ *
126
+ * @typeParam TRoute - 路由定义类型
127
+ */
128
+ type MutationTriggerArgs<TRoute extends RouteDefinition> = InferRouteParams<TRoute> extends undefined ? InferRouteBody<TRoute> extends undefined ? MutationTriggerNoArgs : MutationTriggerWithBody<TRoute> : InferRouteBody<TRoute> extends undefined ? MutationTriggerWithParams<TRoute> : MutationTriggerWithParamsAndBody<TRoute>;
129
+ /**
130
+ * Mutation Hook 函数类型
131
+ *
132
+ * @typeParam TRoute - 路由定义类型
133
+ */
134
+ type MutationHookFunction<TRoute extends RouteDefinition> = (config?: SWRConfiguration<ApiResponse<InferRouteResponse<TRoute>>>) => MutationHookReturn<TRoute>;
135
+ //#endregion
136
+ //#region src/create-mutation-hook.factory.d.ts
137
+ /**
138
+ * 创建 Mutation Hook 工厂
139
+ *
140
+ * 从路由定义自动创建类型安全的 Mutation Hook。适用于 POST/PUT/DELETE/PATCH 请求。
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * import { createMutationHook } from '@longzai-intelligence-transport/http-adapter-react';
145
+ * import { categoryRoutes } from './routes';
146
+ * import { httpClient } from './http-client';
147
+ *
148
+ * const useCreateCategory = createMutationHook(categoryRoutes.create, httpClient);
149
+ * const useUpdateCategory = createMutationHook(categoryRoutes.update, httpClient);
150
+ * const useDeleteCategory = createMutationHook(categoryRoutes.delete, httpClient);
151
+ *
152
+ * // 组件中使用
153
+ * function CategoryForm() {
154
+ * const { trigger: create, isMutating } = useCreateCategory();
155
+ *
156
+ * const handleSubmit = async (data) => {
157
+ * const result = await create(data);
158
+ * if (result.success) {
159
+ * // 创建成功
160
+ * }
161
+ * };
162
+ * }
163
+ * ```;
164
+ *
165
+ * @typeParam TRoute - 路由定义类型
166
+ * @param route - 路由定义
167
+ * @param client - HTTP 客户端实例
168
+ * @returns 类型安全的 Mutation Hook
169
+ */
170
+ declare function createMutationHook<TRoute extends RouteDefinition>(route: TRoute, client: HttpClient): MutationHookFunction<TRoute>;
171
+ //#endregion
172
+ //#region src/create-infinite-hook.factory.d.ts
173
+ /**
174
+ * 无限滚动分页模式
175
+ */
176
+ type InfinitePaginationMode = 'page' | 'cursor';
177
+ /**
178
+ * 无限滚动 Hook 配置选项
179
+ */
180
+ type InfiniteHookOptions = {
181
+ /**
182
+ * 分页模式,默认 'page'
183
+ *
184
+ * - 'page': 使用 page/pageSize 分页,适用于需要跳页的场景
185
+ * - 'cursor': 使用 cursor 游标分页,适用于无限滚动场景
186
+ */
187
+ paginationMode?: InfinitePaginationMode;
188
+ /**
189
+ * 游标分页时的默认每页数量,默认 20
190
+ */
191
+ defaultPageSize?: number;
192
+ };
193
+ /**
194
+ * 无限滚动分页数据类型(支持 page 和 cursor 模式)
195
+ *
196
+ * @typeParam T - 列表项类型
197
+ */
198
+ type InfinitePageData<T> = ApiPaginatedListData<T> | CursorPaginatedResult<T>;
199
+ /**
200
+ * 无限滚动 Hook 返回类型
201
+ *
202
+ * @typeParam T - 列表项类型
203
+ */
204
+ type InfiniteHookReturn<T> = SWRInfiniteResponse<ApiResponse<InfinitePageData<T> | null>>;
205
+ /**
206
+ * 无限滚动 Hook 函数类型
207
+ *
208
+ * @typeParam TRoute - 路由定义类型
209
+ * @typeParam TItem - 列表项类型
210
+ */
211
+ type InfiniteHookFunction<TRoute extends RouteDefinition, TItem> = (params?: InferRouteParams<TRoute>, config?: SWRInfiniteConfiguration<ApiResponse<InfinitePageData<TItem> | null>>) => InfiniteHookReturn<TItem>;
212
+ /**
213
+ * 从路由响应中提取分页项类型
214
+ *
215
+ * @typeParam T - 响应数据类型
216
+ */
217
+ type ExtractPageItem<T> = T extends ApiPaginatedListData<infer Item> ? Item : T extends CursorPaginatedResult<infer Item> ? Item : never;
218
+ /**
219
+ * 创建无限滚动 Hook 工厂
220
+ *
221
+ * 从路由定义自动创建类型安全的 SWR Infinite Hook。用于分页加载、无限滚动场景。
222
+ *
223
+ * @example
224
+ * ```typescript
225
+ * import { createInfiniteHook } from '@longzai-intelligence-transport/http-adapter-react';
226
+ * import { productRoutes } from './routes';
227
+ * import { httpClient } from './http-client';
228
+ *
229
+ * const useInfiniteProducts = createInfiniteHook(productRoutes.list, httpClient);
230
+ *
231
+ * // 组件中使用
232
+ * function ProductList() {
233
+ * const { data, size, setSize, isLoading } = useInfiniteProducts();
234
+ *
235
+ * const products = data?.flatMap((page) => page.data?.items ?? []) ?? [];
236
+ *
237
+ * const loadMore = () => setSize(size + 1);
238
+ *
239
+ * return (
240
+ * <div>
241
+ * {products.map((product) => (
242
+ * <div key={product.id}>{product.name}</div>
243
+ * ))}
244
+ * <button onClick={loadMore}>加载更多</button>
245
+ * </div>
246
+ * );
247
+ * }
248
+ * ```;
249
+ *
250
+ * @example
251
+ * ```typescript
252
+ * // 使用游标分页模式
253
+ * const useInfiniteProducts = createInfiniteHook(productRoutes.list, httpClient, {
254
+ * paginationMode: 'cursor',
255
+ * defaultPageSize: 20,
256
+ * });
257
+ * ```;
258
+ *
259
+ * @typeParam TRoute - 路由定义类型
260
+ * @param route - 路由定义
261
+ * @param client - HTTP 客户端实例
262
+ * @param options - Hook 配置选项
263
+ * @returns 类型安全的 Infinite Hook
264
+ * @throws {@link Error} 当路由配置不合法时抛出错误
265
+ */
266
+ declare function createInfiniteHook<TRoute extends RouteDefinition>(route: TRoute, client: HttpClient, options?: InfiniteHookOptions): InfiniteHookFunction<TRoute, ExtractPageItem<InferRouteResponse<TRoute>>>;
267
+ //#endregion
268
+ export { type ExtractPageItem, type InfiniteHookFunction, type InfiniteHookOptions, type InfiniteHookReturn, type InfinitePageData, type InfinitePaginationMode, type MockRequest, type MockRequestContext, type MutationHookFunction, type MutationHookReturn, type MutationTriggerArgs, type MutationTriggerNoArgs, type MutationTriggerWithBody, type MutationTriggerWithParams, type MutationTriggerWithParamsAndBody, type QueryHookFunction, type QueryHookNoParams, type QueryHookReturn, type QueryHookWithParams, type QueryHookWithParamsAndQuery, type QueryHookWithQuery, createConfigurableMockHttpClient, createDelayedMockHttpClient, createErrorMockHttpClient, createInfiniteHook, createMockHttpClient, createMutationHook, createQueryHook, createRouteMock };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import e from"swr";import{createConfigurableMockHttpClient as t,createDelayedMockHttpClient as n,createErrorMockHttpClient as r,createMockHttpClient as i,createRouteMock as a,isType as o}from"@longzai-intelligence-transport/http-core";import{useCallback as s,useState as c}from"react";import l from"swr/infinite";function u(e,t,n){let r=[e.method,e.path];return t&&r.push(t),n&&r.push(n),r}function d(e){return`params`in e&&e.params!==void 0}function f(e){return`query`in e&&e.query!==void 0}function p(t,n,r,i,a){return e(u(t,o(r)?r:void 0,o(i)?i:void 0),()=>n.request({route:t,params:o(r)?r:void 0,query:o(i)?i:void 0}),a)}function m(t,n,r,i){return e(u(t,o(r)?r:void 0),()=>n.request({route:t,params:o(r)?r:void 0}),i)}function h(t,n,r,i){return e(r?u(t,void 0,o(r)?r:void 0):null,()=>n.request({route:t,query:o(r)?r:void 0}),i)}function g(t,n,r){return e(u(t),()=>n.request({route:t}),r)}function _(e,t){let n=d(e),r=f(e);if(n&&r){let n=(n,r,i)=>p(e,t,n,r,i);if(o(n))return n}if(n){let n=(n,r)=>m(e,t,n,r);if(o(n))return n}if(r){let n=(n,r)=>h(e,t,n,r);if(o(n))return n}let i=n=>g(e,t,n);if(o(i))return i;throw Error(`无法创建 Query Hook:路由配置不合法`)}function v(e){return`params`in e&&e.params!==void 0}function y(e){return`body`in e&&e.body!==void 0}function b(e,t,n,r,i){if(n&&r){let[n,r]=i;return t.request({route:e,params:o(n)?n:void 0,body:o(r)?r:void 0})}if(n){let[n]=i;return t.request({route:e,params:o(n)?n:void 0})}if(r){let[n]=i;return t.request({route:e,body:o(n)?n:void 0})}return t.request({route:e})}function x(e,t){let n=v(e),r=y(e);return()=>{let[i,a]=c(!1),[o,l]=c(void 0),[u,d]=c(void 0);return{trigger:s(async(...i)=>{a(!0),d(void 0);try{let a=await b(e,t,n,r,i);return l(a),a}catch(e){let t=e instanceof Error?e:Error(String(e));throw d(t),t}finally{a(!1)}},[t]),isMutating:i,data:o,error:u,reset:s(()=>{a(!1),l(void 0),d(void 0)},[])}}}function S(e,t,n,r){let i=[e.method,e.path];return t&&i.push(t),r&&r.data&&`hasNextPage`in r.data&&!r.data.hasNextPage?null:(i.push({page:n+1}),i)}function C(e,t,n,r){let i=[e.method,e.path];if(t&&i.push(t),n&&n.data&&`hasMore`in n.data&&!n.data.hasMore)return null;let a=n?.data&&`nextCursor`in n.data?n.data.nextCursor:void 0;return i.push(a?{cursor:a,limit:r}:{limit:r}),i}function w(e,t,n,r){return async i=>{let a=i[n?2:1],s=await t.request({route:e,params:n&&o(r)?r:void 0,query:o(a)?a:void 0});return o(s)?s:{success:!1,data:null,message:`分页数据类型不匹配`,timestamp:Date.now()}}}function T(e,t,n){let r=`params`in e&&e.params!==void 0,i=n?.paginationMode??`page`,a=n?.defaultPageSize??20,s=(n,o)=>l((t,r)=>i===`page`?S(e,n,t,r):i===`cursor`?C(e,n,r,a):null,w(e,t,r,n),o);if(o(s))return s;throw Error(`无法创建 Infinite Hook:路由配置不合法`)}export{t as createConfigurableMockHttpClient,n as createDelayedMockHttpClient,r as createErrorMockHttpClient,T as createInfiniteHook,i as createMockHttpClient,x as createMutationHook,_ as createQueryHook,a as createRouteMock};
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@longzai-intelligence-transport/http-adapter-react",
3
+ "version": "0.1.0",
4
+ "keywords": [
5
+ "adapter",
6
+ "hook",
7
+ "http",
8
+ "react",
9
+ "swr",
10
+ "utils"
11
+ ],
12
+ "license": "UNLICENSED",
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "type": "module",
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js"
23
+ }
24
+ },
25
+ "scripts": {
26
+ "build": "tsgo --build tsconfig/build.json && resolve-aliases -p tsconfig/build.json",
27
+ "build:prod": "NODE_ENV=production tsdown",
28
+ "prepublishOnly": "bun run build:prod",
29
+ "typecheck": "bun run typecheck:app && bun run typecheck:node && bun run typecheck:test",
30
+ "typecheck:app": "tsgo --noEmit -p tsconfig/app.json",
31
+ "typecheck:node": "tsgo --noEmit -p tsconfig/node.json",
32
+ "typecheck:test": "tsgo --noEmit -p tsconfig/test.json",
33
+ "lint": "oxlint && oxfmt --check",
34
+ "lint:fix": "oxlint --fix && oxfmt",
35
+ "test": "bun test",
36
+ "test:watch": "bun test --watch",
37
+ "test:coverage": "bun test --coverage",
38
+ "clean": "rimraf dist out .cache"
39
+ },
40
+ "dependencies": {
41
+ "@longzai-intelligence-transport/http-core": "0.1.0"
42
+ },
43
+ "devDependencies": {
44
+ "@testing-library/react": "^16.3.2",
45
+ "@types/react": "^19.2.17",
46
+ "jsdom": "^29.1.1",
47
+ "react": "^19.2.7",
48
+ "swr": "^2.4.1",
49
+ "zod": "^4.4.3"
50
+ },
51
+ "peerDependencies": {
52
+ "react": ">=18.0.0",
53
+ "swr": ">=2.0.0",
54
+ "zod": "^4.4.3"
55
+ }
56
+ }