@gz-fronted/gz-pc 0.1.1-beta.4 → 0.1.1

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.
Files changed (2) hide show
  1. package/README.md +551 -551
  2. package/package.json +99 -97
package/README.md CHANGED
@@ -1,551 +1,551 @@
1
- # @gz-fronted/gz-pc
2
-
3
- 面向 GZ 前端项目的请求、Hooks 和轻量工具函数包。各子路径独立构建;只使用
4
- `utils` 时不会加载 Axios、React、ahooks 或 gg-ui。
5
-
6
- ## 安装
7
-
8
- ```bash
9
- npm install @gz-fronted/gz-pc react @chenhui996/gg-ui
10
- ```
11
-
12
- 宿主项目需要提供以下 peer dependencies:
13
-
14
- - React 18 或 19
15
- - `@chenhui996/gg-ui` 0.1
16
-
17
- Axios 和 ahooks 是 gz-pc 的普通 dependencies,安装 gz-pc 时会自动安装,宿主
18
- 无需手工添加。React 和 gg-ui 不会打入本包产物。
19
-
20
- 本包刻意不提供聚合根入口,请始终从 `/fetch`、`/hooks` 或 `/utils` 子路径导入,
21
- 避免加载无关模块。
22
-
23
- ## 请求协议
24
-
25
- gz-fetch 默认将所有 HTTP `2xx` 状态视为成功,包括 200、201、202、204 和 206。
26
- 非 `2xx` 状态进入统一错误处理并抛出 `GzFetchError`,错误中保留 `status` 和
27
- `responseData`。
28
-
29
- 成功时直接返回后端原始的 `response.data`,不会返回 `AxiosResponse`,也不会:
30
-
31
- - 判断业务 `code`
32
- - 自动解包 `data`
33
- - 转换分页、字段、日期或任何服务端数据
34
-
35
- 例如后端返回以下内容时,调用方取得的是包含 `code`、`data`、`message` 的完整
36
- 对象:
37
-
38
- ```ts
39
- interface CreateOrderResponse {
40
- code: number;
41
- data: {
42
- orderId: string;
43
- };
44
- message: string;
45
- }
46
-
47
- const response = await gzFetch<CreateOrderResponse, CreateOrderParams>({
48
- url: '/order/create',
49
- method: 'POST',
50
- params,
51
- });
52
-
53
- response.code;
54
- response.data.orderId;
55
- response.message;
56
- ```
57
-
58
- ```ts
59
- import { configureGzFetch, gzFetch } from '@gz-fronted/gz-pc/fetch';
60
-
61
- // 应用初始化入口只调用一次;微前端重新挂载时可以覆盖上一次配置。
62
- configureGzFetch({
63
- baseURL: '/api',
64
- timeout: 15_000,
65
- getToken: () => runtimeToken,
66
- showErrorMessage: true,
67
- });
68
-
69
- interface ListResult {
70
- records: Array<{ id: string }>;
71
- total: number;
72
- }
73
-
74
- const result = await gzFetch<ListResult>({
75
- url: '/users',
76
- method: 'GET',
77
- params: {
78
- page: 1,
79
- pageSize: 20,
80
- },
81
- });
82
- result.records;
83
- result.total;
84
-
85
- interface CreateUserParams {
86
- name: string;
87
- }
88
-
89
- await gzFetch<ListResult, CreateUserParams>({
90
- url: '/users',
91
- method: 'POST',
92
- params: {
93
- name: 'Alice',
94
- },
95
- });
96
- ```
97
-
98
- 业务模块直接导入并调用 `gzFetch`,不需要自行创建请求实例。gz-pc 统一使用
99
- 配置对象式请求调用,业务代码不得混用 `gzFetch(config)` 与
100
- `gzFetch.get/post/put/delete` 两种风格。快捷方法仅作为内部兼容层保留,不作为
101
- 业务开发规范。
102
-
103
- 如果在 `configureGzFetch` 之前调用 `gzFetch`,会明确抛出错误,不会静默使用
104
- 空配置,也不会读取 `localStorage`。
105
-
106
- ### 类型签名
107
-
108
- `gzFetch` 的第一个泛型是响应数据类型,第二个泛型是请求参数类型:
109
-
110
- ```ts
111
- function gzFetch<TResponse, TRequestParams = unknown>(
112
- config: GzRequestConfig<TRequestParams>,
113
- ): Promise<TResponse>;
114
- ```
115
-
116
- 成功时直接返回 `response.data`,因此调用结果类型是 `TResponse`,不是
117
- `AxiosResponse<TResponse>`。
118
-
119
- ### 单次请求配置
120
-
121
- | 字段 | 类型 | 必填 | 说明 |
122
- | ------------------ | -------------------------------------- | ---- | ------------------------------------------ |
123
- | `url` | `string` | 是 | 请求地址;配置了 `baseURL` 时会与其组合 |
124
- | `method` | `'GET' \| 'POST' \| 'PUT' \| 'DELETE'` | 是 | 请求方法,统一使用大写 |
125
- | `params` | `TRequestParams` | 否 | 统一请求入参,由第二个泛型约束 |
126
- | `headers` | `Record<string, string>` | 否 | 本次请求附加的请求头 |
127
- | `timeout` | `number` | 否 | 本次请求超时时间,单位毫秒;优先于实例配置 |
128
- | `showErrorMessage` | `boolean` | 否 | 是否调用 gg-ui 的 `message.error` |
129
- | `skipAuth` | `boolean` | 否 | 为 `true` 时跳过 Token 注入 |
130
- | `responseType` | `'json' \| 'blob' \| 'text'` | 否 | 响应数据类型,默认 `json` |
131
- | `signal` | `AbortSignal` | 否 | 取消请求的标准 AbortSignal |
132
- | `withCredentials` | `boolean` | 否 | 是否携带跨域 Cookie 等凭证 |
133
-
134
- 请求入参字段统一使用 `params`,业务代码不需要区分 Axios 的 `params` 和 `data`:
135
-
136
- - GET、DELETE:`params` 转为 URL Query。
137
- - POST、PUT:`params` 转为 Request Body。
138
-
139
- 业务请求配置不支持 `data`,也不得混用 `params` 和 `data`。
140
- POST、PUT 的 `params` 支持 JSON 对象、`FormData`、`URLSearchParams` 和 `Blob`;
141
- 自定义请求头通过 `headers` 传入。
142
-
143
- 完整示例:
144
-
145
- ```ts
146
- interface SaveParams {
147
- id: string;
148
- name: string;
149
- }
150
-
151
- interface SaveResult {
152
- success: boolean;
153
- }
154
-
155
- const controller = new AbortController();
156
-
157
- const result = await gzFetch<SaveResult, SaveParams>({
158
- url: '/user/save',
159
- method: 'POST',
160
- params: {
161
- id: '1001',
162
- name: '张三',
163
- },
164
- headers: {
165
- 'X-Request-Source': 'gz-pc',
166
- },
167
- timeout: 10_000,
168
- showErrorMessage: true,
169
- skipAuth: false,
170
- responseType: 'json',
171
- signal: controller.signal,
172
- });
173
- ```
174
-
175
- ### 应用初始化配置
176
-
177
- `configureGzFetch` 只在应用初始化层调用。它接收以下配置:
178
-
179
- | 字段 | 类型 | 默认值 | 说明 |
180
- | ------------------ | ----------------------------------------------------------- | ----------------- | -------------------------- |
181
- | `baseURL` | `string` | 无 | 所有业务请求的基础地址 |
182
- | `timeout` | `number` | `15000` | 默认请求超时时间,单位毫秒 |
183
- | `validateStatus` | `(status: number) => boolean` | 所有 `2xx` | 自定义 HTTP 成功状态范围 |
184
- | `getToken` | `() => string \| undefined \| Promise<string \| undefined>` | 无 | 每次请求前动态获取 Token |
185
- | `showErrorMessage` | `boolean` | `true` | 实例级错误提示开关 |
186
- | `auth.headerName` | `string` | `Authorization` | Token 请求头名称 |
187
- | `auth.formatToken` | `(token: string) => string` | `Bearer ${token}` | Token 格式化函数 |
188
- | `unauthorized` | `GzFetchUnauthorizedOptions` | 未开启 | 可选的 HTTP 401 统一处理 |
189
- | `middlewares` | `readonly GzFetchMiddleware[]` | `[]` | 请求、响应和错误中间件 |
190
-
191
- 需要覆盖默认成功状态时,只在应用初始化层配置:
192
-
193
- ```ts
194
- configureGzFetch({
195
- validateStatus: (status) => status === 200 || status === 304,
196
- });
197
- ```
198
-
199
- 当前稳定 API 没有开放 Axios 的 `paramsSerializer`、`onUploadProgress` 等配置,也
200
- 暂不支持 `PATCH`。业务代码只应传入上表列出的字段;如果后续确有通用场景,再
201
- 通过 gz-pc 统一扩展类型和请求核心。
202
-
203
- ### 跨域凭证
204
-
205
- 需要浏览器携带跨域 Cookie 等凭证时,在单次请求中开启 `withCredentials`:
206
-
207
- ```ts
208
- await gzFetch<QueryPresetResponse, QueryPresetParams>({
209
- url: '/queryFilterTemplate/list',
210
- method: 'POST',
211
- params,
212
- withCredentials: true,
213
- });
214
- ```
215
-
216
- ## Token
217
-
218
- `getToken` 会在每次请求前动态执行。gz-fetch 不读取 Props、React Context 或
219
- `localStorage`。
220
-
221
- 默认请求头为 `Authorization: Bearer <token>`:
222
-
223
- ```ts
224
- configureGzFetch({
225
- getToken: async () => tokenStore.get(),
226
- });
227
-
228
- await gzFetch<PublicConfig>({
229
- url: '/public/config',
230
- method: 'GET',
231
- skipAuth: true,
232
- });
233
- ```
234
-
235
- 也可以修改 Header 名称和格式:
236
-
237
- ```ts
238
- configureGzFetch({
239
- getToken: () => token,
240
- auth: {
241
- headerName: 'X-Token',
242
- formatToken: (value) => `Token ${value}`,
243
- },
244
- });
245
- ```
246
-
247
- ## 错误提示
248
-
249
- HTTP 非 `2xx` 时仅尝试读取响应体的 `msg`。没有有效 `msg` 时使用默认文案,最终
250
- 抛出 `GzFetchError`。
251
-
252
- 错误类型包括:
253
-
254
- - `HTTP_ERROR`
255
- - `NETWORK_ERROR`
256
- - `TIMEOUT_ERROR`
257
- - `CANCELED_ERROR`
258
- - `UNKNOWN_ERROR`
259
-
260
- 错误 Message 默认开启,优先级为“单请求配置 > 实例配置 > 默认值 true”:
261
-
262
- ```ts
263
- configureGzFetch({ showErrorMessage: true });
264
-
265
- await gzFetch<void, SaveParams>({
266
- url: '/save',
267
- method: 'POST',
268
- params,
269
- showErrorMessage: false,
270
- });
271
- ```
272
-
273
- 取消请求会转换成 `CANCELED_ERROR`,且默认不调用 `message.error`:
274
-
275
- ```ts
276
- const controller = new AbortController();
277
-
278
- const promise = gzFetch<UserDetail>({
279
- url: '/users',
280
- method: 'GET',
281
- signal: controller.signal,
282
- });
283
-
284
- controller.abort();
285
- await promise;
286
- ```
287
-
288
- ## HTTP 401 统一处理
289
-
290
- 401 统一处理默认关闭,未显式开启时继续执行原有 HTTP 错误提示和抛错逻辑,不弹窗、
291
- 不跳转。需要启用时,在应用初始化配置中传入 `unauthorized.enabled: true`:
292
-
293
- ```ts
294
- configureGzFetch({
295
- baseURL: '/api',
296
- getToken: () => runtimeToken,
297
- unauthorized: {
298
- enabled: true,
299
- loginUrl: '/login',
300
- modalTitle: '登录失效',
301
- modalMessage: '当前登录状态已失效,请重新登录。',
302
- },
303
- });
304
- ```
305
-
306
- 配置默认值如下:
307
-
308
- | 字段 | 类型 | 默认值 |
309
- | ---------------- | ------------ | --------------------------------------- |
310
- | `enabled` | `boolean` | `false` |
311
- | `loginUrl` | `string` | `/login` |
312
- | `modalTitle` | `string` | `登录失效` |
313
- | `modalMessage` | `string` | `当前登录状态已失效,请重新登录。` |
314
- | `onUnauthorized` | `() => void` | 无;确认后使用 `window.location.assign` |
315
-
316
- 为了让普通错误消息和 401 弹窗都继承宿主项目当前的 gg-ui 主题,需要在应用根部的
317
- `ConfigProvider` 内挂载一次 `GzFetchFeedbackProvider`:
318
-
319
- ```tsx
320
- import { GzFetchFeedbackProvider } from '@gz-fronted/gz-pc/fetch';
321
- import { ConfigProvider } from '@chenhui996/gg-ui';
322
-
323
- <ConfigProvider themeMode={themeMode}>
324
- <GzFetchFeedbackProvider>
325
- <App />
326
- </GzFetchFeedbackProvider>
327
- </ConfigProvider>;
328
- ```
329
-
330
- 该 Provider 使用 `message.useMessage()` 和受控 gg-ui `Modal`,不会在已接入的项目中
331
- 调用脱离 React 上下文的静态反馈 API。因此主题切换时,普通错误消息和 401 弹窗都会
332
- 继续读取宿主 `ConfigProvider` 的主题 Token,无需复制亮色或暗色样式。业务页面不需要
333
- 维护反馈状态。未挂载该 Provider 的已有项目仍会回退到原来的静态 `message.error`,
334
- 不会产生 breaking change。`GzFetchUnauthorizedModal` 继续保留为兼容 API,但新项目
335
- 优先使用统一 Provider。
336
-
337
- 所有由同一份 gz-pc 包创建的默认客户端和独立客户端共享一个 401 管理器。首个 401
338
- 打开弹窗,后续并发 401 不会创建新弹窗;取消或弹窗关闭动画结束后会释放状态,后续
339
- 独立发生的 401 可以再次打开。确认操作也有同一状态锁,不会重复执行登录跳转。
340
-
341
- 业务项目需要自定义 SSO、登出或状态清理时传入 `onUnauthorized`。用户确认后优先执行
342
- 该回调;只有未提供回调时才通过 `window.location.assign(loginUrl)` 跳转:
343
-
344
- ```ts
345
- configureGzFetch({
346
- unauthorized: {
347
- enabled: true,
348
- onUnauthorized: () => {
349
- authService.logout();
350
- },
351
- },
352
- });
353
- ```
354
-
355
- 开启该能力后,401 仍会作为 `GzFetchError` 抛给调用方,但请求核心不会再为同一个
356
- 401 额外调用普通 `message.error`。其他 HTTP 错误、网络错误、超时和取消请求行为不变。
357
-
358
- ## 中间件
359
-
360
- 中间件的 `onRequest`、`onResponse` 和 `onError` 都按注册顺序执行,不采用洋葱
361
- 模型:
362
-
363
- ```ts
364
- configureGzFetch({
365
- middlewares: [
366
- {
367
- onRequest(config) {
368
- return {
369
- ...config,
370
- headers: {
371
- ...config.headers,
372
- 'X-Trace-ID': crypto.randomUUID(),
373
- },
374
- };
375
- },
376
- onResponse(data, context) {
377
- console.debug(context.status);
378
- return data;
379
- },
380
- onError(error) {
381
- console.error(error.type);
382
- },
383
- },
384
- ],
385
- });
386
- ```
387
-
388
- ## 独立请求实例
389
-
390
- 普通业务模块禁止反复调用 `createGzFetch`。它只用于确实需要独立请求实例的高级
391
- 场景,例如多后端服务、独立 `baseURL`、独立 Token 或独立中间件链:
392
-
393
- ```ts
394
- import { createGzFetch } from '@gz-fronted/gz-pc/fetch';
395
-
396
- const reportingFetch = createGzFetch({
397
- baseURL: '/reporting-api',
398
- getToken: () => reportingToken,
399
- });
400
-
401
- const report = await reportingFetch<ReportResult>({
402
- url: '/reports/latest',
403
- method: 'GET',
404
- });
405
- ```
406
-
407
- 默认 `gzFetch` 和独立实例都复用同一套请求核心、错误处理、Token 注入和中间件
408
- 实现。
409
-
410
- ## Blob
411
-
412
- gz-fetch 只返回 Blob,不创建下载链接、不解析文件名:
413
-
414
- ```ts
415
- const file = await gzFetch<Blob, ExportParams>({
416
- url: '/export',
417
- method: 'POST',
418
- params,
419
- responseType: 'blob',
420
- });
421
- ```
422
-
423
- ## Hooks
424
-
425
- ```ts
426
- import {
427
- useDebounce,
428
- useDebounceFn,
429
- usePagination,
430
- useRequest,
431
- } from '@gz-fronted/gz-pc/hooks';
432
- ```
433
-
434
- `gz-pc/hooks` 通过 `export * from 'ahooks'` 完整透传 ahooks 的公开 API,作为团队
435
- 统一的 Hooks 使用入口。后续自定义 Hook 也从同一入口导出:
436
-
437
- ```ts
438
- export * from 'ahooks';
439
- export * from './use-table-height';
440
- ```
441
-
442
- 新增自定义 Hook 时不得与 ahooks 已有导出重名。
443
-
444
- ## formatDate
445
-
446
- ```ts
447
- import { formatDate } from '@gz-fronted/gz-pc/utils';
448
-
449
- formatDate(new Date()); // YYYY-MM-DD HH:mm:ss
450
- formatDate(Date.now(), 'YYYY-MM-DD');
451
- ```
452
-
453
- 支持 `string | number | Date | null | undefined`。`null`、`undefined`、空字符串和
454
- 无效日期统一返回 `--`。该入口不依赖 React、ahooks、Axios 或 gg-ui。
455
-
456
- ## Query 参数
457
-
458
- ```ts
459
- import { objectToQuery, queryToObject } from '@gz-fronted/gz-pc/utils';
460
-
461
- queryToObject('https://example.com/list?page=1&tag=a&tag=b');
462
- // { page: '1', tag: ['a', 'b'] }
463
-
464
- objectToQuery({
465
- page: 1,
466
- keyword: 'audit log',
467
- tag: ['a', 'b'],
468
- });
469
- // page=1&keyword=audit+log&tag=a&tag=b
470
- ```
471
-
472
- `queryToObject` 支持完整 URL、带 `?` 的 query 和纯 query 字符串;重复参数返回
473
- 字符串数组。`objectToQuery` 返回不带前导 `?` 的字符串,跳过 `undefined`,
474
- 将 `null` 转为空值,并将数组序列化为重复参数。
475
-
476
- ## MSW Mock
477
-
478
- 完整接入样例位于 [`examples/msw`](./examples/msw)。核心启动顺序如下:
479
-
480
- ```ts
481
- async function bootstrap(): Promise<void> {
482
- if (import.meta.env.VITE_USE_MOCK === 'true') {
483
- const { startMock } = await import('./mock/browser');
484
- await startMock();
485
- }
486
-
487
- renderApp();
488
- }
489
-
490
- void bootstrap();
491
- ```
492
-
493
- ```ts
494
- worker.start({
495
- onUnhandledRequest: 'bypass',
496
- });
497
- ```
498
-
499
- 必须等待 `worker.start()` 完成后再渲染应用,避免首次请求漏拦截。具体 Handler
500
- 和 Mock 数据由业务项目维护,gz-pc 不提供业务 Mock。
501
-
502
- ## 本地接入
503
-
504
- 先在本仓库构建并生成 tarball:
505
-
506
- ```bash
507
- npm run build
508
- npm pack
509
- ```
510
-
511
- 然后在业务项目安装生成的 `.tgz`:
512
-
513
- ```bash
514
- npm install ../gz-pc/gz-fronted-gz-pc-0.1.0.tgz
515
- ```
516
-
517
- 也可以在联调期间使用业务项目支持的 workspace 或本地 `file:` 依赖。发布前建议
518
- 优先用 tarball 验证,因为它与 npm 实际安装内容最接近。
519
-
520
- ## 提交规范
521
-
522
- 项目使用 Husky、lint-staged、Commitlint 和 Conventional Commits。安装依赖时
523
- `prepare` 脚本会初始化 Husky。
524
-
525
- `pre-commit` 在提交前只检查 Git 暂存区中的文件:
526
-
527
- - JavaScript、JSX、TypeScript、TSX:依次执行 `eslint --fix` 和
528
- `prettier --write`
529
- - JSON、Markdown、YAML、CSS、Less:执行 `prettier --write`
530
-
531
- 可以自动修复的问题会写回暂存文件;仍存在的 ESLint 错误会阻止提交。lint-staged
532
- 只负责提交前的快速增量检查,不替代完整的 `lint`、`typecheck`、`test` 和
533
- `build`。
534
-
535
- Prettier 对 JavaScript、TypeScript 和 JSX 统一使用单引号;JSON 按标准继续使用
536
- 双引号。
537
-
538
- `commit-msg` 使用 Commitlint 校验提交信息:
539
-
540
- ```text
541
- feat: add request client
542
- fix: handle canceled request
543
- ```
544
-
545
- 缺少合法 type、subject 或其他不符合 Conventional Commits 的提交信息会被拒绝。
546
-
547
- ## 第一版边界
548
-
549
- 第一版不包含 Token 自动刷新、自动重试、缓存、请求去重、并发控制、业务 code
550
- 判断、`data` 解包、分页/日期转换、错误上报、自动文件下载、内置业务 Mock、
551
- React Context、Zustand 或复杂洋葱中间件。
1
+ # @gz-fronted/gz-pc
2
+
3
+ 面向 GZ 前端项目的请求、Hooks 和轻量工具函数包。各子路径独立构建;只使用
4
+ `utils` 时不会加载 Axios、React、ahooks 或 gg-ui。
5
+
6
+ ## 安装
7
+
8
+ ```bash
9
+ npm install @gz-fronted/gz-pc react @chenhui996/gg-ui
10
+ ```
11
+
12
+ 宿主项目需要提供以下 peer dependencies:
13
+
14
+ - React 18 或 19
15
+ - `@chenhui996/gg-ui` 0.1
16
+
17
+ Axios 和 ahooks 是 gz-pc 的普通 dependencies,安装 gz-pc 时会自动安装,宿主
18
+ 无需手工添加。React 和 gg-ui 不会打入本包产物。
19
+
20
+ 本包刻意不提供聚合根入口,请始终从 `/fetch`、`/hooks` 或 `/utils` 子路径导入,
21
+ 避免加载无关模块。
22
+
23
+ ## 请求协议
24
+
25
+ gz-fetch 默认将所有 HTTP `2xx` 状态视为成功,包括 200、201、202、204 和 206。
26
+ 非 `2xx` 状态进入统一错误处理并抛出 `GzFetchError`,错误中保留 `status` 和
27
+ `responseData`。
28
+
29
+ 成功时直接返回后端原始的 `response.data`,不会返回 `AxiosResponse`,也不会:
30
+
31
+ - 判断业务 `code`
32
+ - 自动解包 `data`
33
+ - 转换分页、字段、日期或任何服务端数据
34
+
35
+ 例如后端返回以下内容时,调用方取得的是包含 `code`、`data`、`message` 的完整
36
+ 对象:
37
+
38
+ ```ts
39
+ interface CreateOrderResponse {
40
+ code: number;
41
+ data: {
42
+ orderId: string;
43
+ };
44
+ message: string;
45
+ }
46
+
47
+ const response = await gzFetch<CreateOrderResponse, CreateOrderParams>({
48
+ url: '/order/create',
49
+ method: 'POST',
50
+ params,
51
+ });
52
+
53
+ response.code;
54
+ response.data.orderId;
55
+ response.message;
56
+ ```
57
+
58
+ ```ts
59
+ import { configureGzFetch, gzFetch } from '@gz-fronted/gz-pc/fetch';
60
+
61
+ // 应用初始化入口只调用一次;微前端重新挂载时可以覆盖上一次配置。
62
+ configureGzFetch({
63
+ baseURL: '/api',
64
+ timeout: 15_000,
65
+ getToken: () => runtimeToken,
66
+ showErrorMessage: true,
67
+ });
68
+
69
+ interface ListResult {
70
+ records: Array<{ id: string }>;
71
+ total: number;
72
+ }
73
+
74
+ const result = await gzFetch<ListResult>({
75
+ url: '/users',
76
+ method: 'GET',
77
+ params: {
78
+ page: 1,
79
+ pageSize: 20,
80
+ },
81
+ });
82
+ result.records;
83
+ result.total;
84
+
85
+ interface CreateUserParams {
86
+ name: string;
87
+ }
88
+
89
+ await gzFetch<ListResult, CreateUserParams>({
90
+ url: '/users',
91
+ method: 'POST',
92
+ params: {
93
+ name: 'Alice',
94
+ },
95
+ });
96
+ ```
97
+
98
+ 业务模块直接导入并调用 `gzFetch`,不需要自行创建请求实例。gz-pc 统一使用
99
+ 配置对象式请求调用,业务代码不得混用 `gzFetch(config)` 与
100
+ `gzFetch.get/post/put/delete` 两种风格。快捷方法仅作为内部兼容层保留,不作为
101
+ 业务开发规范。
102
+
103
+ 如果在 `configureGzFetch` 之前调用 `gzFetch`,会明确抛出错误,不会静默使用
104
+ 空配置,也不会读取 `localStorage`。
105
+
106
+ ### 类型签名
107
+
108
+ `gzFetch` 的第一个泛型是响应数据类型,第二个泛型是请求参数类型:
109
+
110
+ ```ts
111
+ function gzFetch<TResponse, TRequestParams = unknown>(
112
+ config: GzRequestConfig<TRequestParams>,
113
+ ): Promise<TResponse>;
114
+ ```
115
+
116
+ 成功时直接返回 `response.data`,因此调用结果类型是 `TResponse`,不是
117
+ `AxiosResponse<TResponse>`。
118
+
119
+ ### 单次请求配置
120
+
121
+ | 字段 | 类型 | 必填 | 说明 |
122
+ | ------------------ | -------------------------------------- | ---- | ------------------------------------------ |
123
+ | `url` | `string` | 是 | 请求地址;配置了 `baseURL` 时会与其组合 |
124
+ | `method` | `'GET' \| 'POST' \| 'PUT' \| 'DELETE'` | 是 | 请求方法,统一使用大写 |
125
+ | `params` | `TRequestParams` | 否 | 统一请求入参,由第二个泛型约束 |
126
+ | `headers` | `Record<string, string>` | 否 | 本次请求附加的请求头 |
127
+ | `timeout` | `number` | 否 | 本次请求超时时间,单位毫秒;优先于实例配置 |
128
+ | `showErrorMessage` | `boolean` | 否 | 是否调用 gg-ui 的 `message.error` |
129
+ | `skipAuth` | `boolean` | 否 | 为 `true` 时跳过 Token 注入 |
130
+ | `responseType` | `'json' \| 'blob' \| 'text'` | 否 | 响应数据类型,默认 `json` |
131
+ | `signal` | `AbortSignal` | 否 | 取消请求的标准 AbortSignal |
132
+ | `withCredentials` | `boolean` | 否 | 是否携带跨域 Cookie 等凭证 |
133
+
134
+ 请求入参字段统一使用 `params`,业务代码不需要区分 Axios 的 `params` 和 `data`:
135
+
136
+ - GET、DELETE:`params` 转为 URL Query。
137
+ - POST、PUT:`params` 转为 Request Body。
138
+
139
+ 业务请求配置不支持 `data`,也不得混用 `params` 和 `data`。
140
+ POST、PUT 的 `params` 支持 JSON 对象、`FormData`、`URLSearchParams` 和 `Blob`;
141
+ 自定义请求头通过 `headers` 传入。
142
+
143
+ 完整示例:
144
+
145
+ ```ts
146
+ interface SaveParams {
147
+ id: string;
148
+ name: string;
149
+ }
150
+
151
+ interface SaveResult {
152
+ success: boolean;
153
+ }
154
+
155
+ const controller = new AbortController();
156
+
157
+ const result = await gzFetch<SaveResult, SaveParams>({
158
+ url: '/user/save',
159
+ method: 'POST',
160
+ params: {
161
+ id: '1001',
162
+ name: '张三',
163
+ },
164
+ headers: {
165
+ 'X-Request-Source': 'gz-pc',
166
+ },
167
+ timeout: 10_000,
168
+ showErrorMessage: true,
169
+ skipAuth: false,
170
+ responseType: 'json',
171
+ signal: controller.signal,
172
+ });
173
+ ```
174
+
175
+ ### 应用初始化配置
176
+
177
+ `configureGzFetch` 只在应用初始化层调用。它接收以下配置:
178
+
179
+ | 字段 | 类型 | 默认值 | 说明 |
180
+ | ------------------ | ----------------------------------------------------------- | ----------------- | -------------------------- |
181
+ | `baseURL` | `string` | 无 | 所有业务请求的基础地址 |
182
+ | `timeout` | `number` | `15000` | 默认请求超时时间,单位毫秒 |
183
+ | `validateStatus` | `(status: number) => boolean` | 所有 `2xx` | 自定义 HTTP 成功状态范围 |
184
+ | `getToken` | `() => string \| undefined \| Promise<string \| undefined>` | 无 | 每次请求前动态获取 Token |
185
+ | `showErrorMessage` | `boolean` | `true` | 实例级错误提示开关 |
186
+ | `auth.headerName` | `string` | `Authorization` | Token 请求头名称 |
187
+ | `auth.formatToken` | `(token: string) => string` | `Bearer ${token}` | Token 格式化函数 |
188
+ | `unauthorized` | `GzFetchUnauthorizedOptions` | 未开启 | 可选的 HTTP 401 统一处理 |
189
+ | `middlewares` | `readonly GzFetchMiddleware[]` | `[]` | 请求、响应和错误中间件 |
190
+
191
+ 需要覆盖默认成功状态时,只在应用初始化层配置:
192
+
193
+ ```ts
194
+ configureGzFetch({
195
+ validateStatus: (status) => status === 200 || status === 304,
196
+ });
197
+ ```
198
+
199
+ 当前稳定 API 没有开放 Axios 的 `paramsSerializer`、`onUploadProgress` 等配置,也
200
+ 暂不支持 `PATCH`。业务代码只应传入上表列出的字段;如果后续确有通用场景,再
201
+ 通过 gz-pc 统一扩展类型和请求核心。
202
+
203
+ ### 跨域凭证
204
+
205
+ 需要浏览器携带跨域 Cookie 等凭证时,在单次请求中开启 `withCredentials`:
206
+
207
+ ```ts
208
+ await gzFetch<QueryPresetResponse, QueryPresetParams>({
209
+ url: '/queryFilterTemplate/list',
210
+ method: 'POST',
211
+ params,
212
+ withCredentials: true,
213
+ });
214
+ ```
215
+
216
+ ## Token
217
+
218
+ `getToken` 会在每次请求前动态执行。gz-fetch 不读取 Props、React Context 或
219
+ `localStorage`。
220
+
221
+ 默认请求头为 `Authorization: Bearer <token>`:
222
+
223
+ ```ts
224
+ configureGzFetch({
225
+ getToken: async () => tokenStore.get(),
226
+ });
227
+
228
+ await gzFetch<PublicConfig>({
229
+ url: '/public/config',
230
+ method: 'GET',
231
+ skipAuth: true,
232
+ });
233
+ ```
234
+
235
+ 也可以修改 Header 名称和格式:
236
+
237
+ ```ts
238
+ configureGzFetch({
239
+ getToken: () => token,
240
+ auth: {
241
+ headerName: 'X-Token',
242
+ formatToken: (value) => `Token ${value}`,
243
+ },
244
+ });
245
+ ```
246
+
247
+ ## 错误提示
248
+
249
+ HTTP 非 `2xx` 时仅尝试读取响应体的 `msg`。没有有效 `msg` 时使用默认文案,最终
250
+ 抛出 `GzFetchError`。
251
+
252
+ 错误类型包括:
253
+
254
+ - `HTTP_ERROR`
255
+ - `NETWORK_ERROR`
256
+ - `TIMEOUT_ERROR`
257
+ - `CANCELED_ERROR`
258
+ - `UNKNOWN_ERROR`
259
+
260
+ 错误 Message 默认开启,优先级为“单请求配置 > 实例配置 > 默认值 true”:
261
+
262
+ ```ts
263
+ configureGzFetch({ showErrorMessage: true });
264
+
265
+ await gzFetch<void, SaveParams>({
266
+ url: '/save',
267
+ method: 'POST',
268
+ params,
269
+ showErrorMessage: false,
270
+ });
271
+ ```
272
+
273
+ 取消请求会转换成 `CANCELED_ERROR`,且默认不调用 `message.error`:
274
+
275
+ ```ts
276
+ const controller = new AbortController();
277
+
278
+ const promise = gzFetch<UserDetail>({
279
+ url: '/users',
280
+ method: 'GET',
281
+ signal: controller.signal,
282
+ });
283
+
284
+ controller.abort();
285
+ await promise;
286
+ ```
287
+
288
+ ## HTTP 401 统一处理
289
+
290
+ 401 统一处理默认关闭,未显式开启时继续执行原有 HTTP 错误提示和抛错逻辑,不弹窗、
291
+ 不跳转。需要启用时,在应用初始化配置中传入 `unauthorized.enabled: true`:
292
+
293
+ ```ts
294
+ configureGzFetch({
295
+ baseURL: '/api',
296
+ getToken: () => runtimeToken,
297
+ unauthorized: {
298
+ enabled: true,
299
+ loginUrl: '/login',
300
+ modalTitle: '登录失效',
301
+ modalMessage: '当前登录状态已失效,请重新登录。',
302
+ },
303
+ });
304
+ ```
305
+
306
+ 配置默认值如下:
307
+
308
+ | 字段 | 类型 | 默认值 |
309
+ | ---------------- | ------------ | --------------------------------------- |
310
+ | `enabled` | `boolean` | `false` |
311
+ | `loginUrl` | `string` | `/login` |
312
+ | `modalTitle` | `string` | `登录失效` |
313
+ | `modalMessage` | `string` | `当前登录状态已失效,请重新登录。` |
314
+ | `onUnauthorized` | `() => void` | 无;确认后使用 `window.location.assign` |
315
+
316
+ 为了让普通错误消息和 401 弹窗都继承宿主项目当前的 gg-ui 主题,需要在应用根部的
317
+ `ConfigProvider` 内挂载一次 `GzFetchFeedbackProvider`:
318
+
319
+ ```tsx
320
+ import { GzFetchFeedbackProvider } from '@gz-fronted/gz-pc/fetch';
321
+ import { ConfigProvider } from '@chenhui996/gg-ui';
322
+
323
+ <ConfigProvider themeMode={themeMode}>
324
+ <GzFetchFeedbackProvider>
325
+ <App />
326
+ </GzFetchFeedbackProvider>
327
+ </ConfigProvider>;
328
+ ```
329
+
330
+ 该 Provider 使用 `message.useMessage()` 和受控 gg-ui `Modal`,不会在已接入的项目中
331
+ 调用脱离 React 上下文的静态反馈 API。因此主题切换时,普通错误消息和 401 弹窗都会
332
+ 继续读取宿主 `ConfigProvider` 的主题 Token,无需复制亮色或暗色样式。业务页面不需要
333
+ 维护反馈状态。未挂载该 Provider 的已有项目仍会回退到原来的静态 `message.error`,
334
+ 不会产生 breaking change。`GzFetchUnauthorizedModal` 继续保留为兼容 API,但新项目
335
+ 优先使用统一 Provider。
336
+
337
+ 所有由同一份 gz-pc 包创建的默认客户端和独立客户端共享一个 401 管理器。首个 401
338
+ 打开弹窗,后续并发 401 不会创建新弹窗;取消或弹窗关闭动画结束后会释放状态,后续
339
+ 独立发生的 401 可以再次打开。确认操作也有同一状态锁,不会重复执行登录跳转。
340
+
341
+ 业务项目需要自定义 SSO、登出或状态清理时传入 `onUnauthorized`。用户确认后优先执行
342
+ 该回调;只有未提供回调时才通过 `window.location.assign(loginUrl)` 跳转:
343
+
344
+ ```ts
345
+ configureGzFetch({
346
+ unauthorized: {
347
+ enabled: true,
348
+ onUnauthorized: () => {
349
+ authService.logout();
350
+ },
351
+ },
352
+ });
353
+ ```
354
+
355
+ 开启该能力后,401 仍会作为 `GzFetchError` 抛给调用方,但请求核心不会再为同一个
356
+ 401 额外调用普通 `message.error`。其他 HTTP 错误、网络错误、超时和取消请求行为不变。
357
+
358
+ ## 中间件
359
+
360
+ 中间件的 `onRequest`、`onResponse` 和 `onError` 都按注册顺序执行,不采用洋葱
361
+ 模型:
362
+
363
+ ```ts
364
+ configureGzFetch({
365
+ middlewares: [
366
+ {
367
+ onRequest(config) {
368
+ return {
369
+ ...config,
370
+ headers: {
371
+ ...config.headers,
372
+ 'X-Trace-ID': crypto.randomUUID(),
373
+ },
374
+ };
375
+ },
376
+ onResponse(data, context) {
377
+ console.debug(context.status);
378
+ return data;
379
+ },
380
+ onError(error) {
381
+ console.error(error.type);
382
+ },
383
+ },
384
+ ],
385
+ });
386
+ ```
387
+
388
+ ## 独立请求实例
389
+
390
+ 普通业务模块禁止反复调用 `createGzFetch`。它只用于确实需要独立请求实例的高级
391
+ 场景,例如多后端服务、独立 `baseURL`、独立 Token 或独立中间件链:
392
+
393
+ ```ts
394
+ import { createGzFetch } from '@gz-fronted/gz-pc/fetch';
395
+
396
+ const reportingFetch = createGzFetch({
397
+ baseURL: '/reporting-api',
398
+ getToken: () => reportingToken,
399
+ });
400
+
401
+ const report = await reportingFetch<ReportResult>({
402
+ url: '/reports/latest',
403
+ method: 'GET',
404
+ });
405
+ ```
406
+
407
+ 默认 `gzFetch` 和独立实例都复用同一套请求核心、错误处理、Token 注入和中间件
408
+ 实现。
409
+
410
+ ## Blob
411
+
412
+ gz-fetch 只返回 Blob,不创建下载链接、不解析文件名:
413
+
414
+ ```ts
415
+ const file = await gzFetch<Blob, ExportParams>({
416
+ url: '/export',
417
+ method: 'POST',
418
+ params,
419
+ responseType: 'blob',
420
+ });
421
+ ```
422
+
423
+ ## Hooks
424
+
425
+ ```ts
426
+ import {
427
+ useDebounce,
428
+ useDebounceFn,
429
+ usePagination,
430
+ useRequest,
431
+ } from '@gz-fronted/gz-pc/hooks';
432
+ ```
433
+
434
+ `gz-pc/hooks` 通过 `export * from 'ahooks'` 完整透传 ahooks 的公开 API,作为团队
435
+ 统一的 Hooks 使用入口。后续自定义 Hook 也从同一入口导出:
436
+
437
+ ```ts
438
+ export * from 'ahooks';
439
+ export * from './use-table-height';
440
+ ```
441
+
442
+ 新增自定义 Hook 时不得与 ahooks 已有导出重名。
443
+
444
+ ## formatDate
445
+
446
+ ```ts
447
+ import { formatDate } from '@gz-fronted/gz-pc/utils';
448
+
449
+ formatDate(new Date()); // YYYY-MM-DD HH:mm:ss
450
+ formatDate(Date.now(), 'YYYY-MM-DD');
451
+ ```
452
+
453
+ 支持 `string | number | Date | null | undefined`。`null`、`undefined`、空字符串和
454
+ 无效日期统一返回 `--`。该入口不依赖 React、ahooks、Axios 或 gg-ui。
455
+
456
+ ## Query 参数
457
+
458
+ ```ts
459
+ import { objectToQuery, queryToObject } from '@gz-fronted/gz-pc/utils';
460
+
461
+ queryToObject('https://example.com/list?page=1&tag=a&tag=b');
462
+ // { page: '1', tag: ['a', 'b'] }
463
+
464
+ objectToQuery({
465
+ page: 1,
466
+ keyword: 'audit log',
467
+ tag: ['a', 'b'],
468
+ });
469
+ // page=1&keyword=audit+log&tag=a&tag=b
470
+ ```
471
+
472
+ `queryToObject` 支持完整 URL、带 `?` 的 query 和纯 query 字符串;重复参数返回
473
+ 字符串数组。`objectToQuery` 返回不带前导 `?` 的字符串,跳过 `undefined`,
474
+ 将 `null` 转为空值,并将数组序列化为重复参数。
475
+
476
+ ## MSW Mock
477
+
478
+ 完整接入样例位于 [`examples/msw`](./examples/msw)。核心启动顺序如下:
479
+
480
+ ```ts
481
+ async function bootstrap(): Promise<void> {
482
+ if (import.meta.env.VITE_USE_MOCK === 'true') {
483
+ const { startMock } = await import('./mock/browser');
484
+ await startMock();
485
+ }
486
+
487
+ renderApp();
488
+ }
489
+
490
+ void bootstrap();
491
+ ```
492
+
493
+ ```ts
494
+ worker.start({
495
+ onUnhandledRequest: 'bypass',
496
+ });
497
+ ```
498
+
499
+ 必须等待 `worker.start()` 完成后再渲染应用,避免首次请求漏拦截。具体 Handler
500
+ 和 Mock 数据由业务项目维护,gz-pc 不提供业务 Mock。
501
+
502
+ ## 本地接入
503
+
504
+ 先在本仓库构建并生成 tarball:
505
+
506
+ ```bash
507
+ npm run build
508
+ npm pack
509
+ ```
510
+
511
+ 然后在业务项目安装生成的 `.tgz`:
512
+
513
+ ```bash
514
+ npm install ../gz-pc/gz-fronted-gz-pc-0.1.0.tgz
515
+ ```
516
+
517
+ 也可以在联调期间使用业务项目支持的 workspace 或本地 `file:` 依赖。发布前建议
518
+ 优先用 tarball 验证,因为它与 npm 实际安装内容最接近。
519
+
520
+ ## 提交规范
521
+
522
+ 项目使用 Husky、lint-staged、Commitlint 和 Conventional Commits。安装依赖时
523
+ `prepare` 脚本会初始化 Husky。
524
+
525
+ `pre-commit` 在提交前只检查 Git 暂存区中的文件:
526
+
527
+ - JavaScript、JSX、TypeScript、TSX:依次执行 `eslint --fix` 和
528
+ `prettier --write`
529
+ - JSON、Markdown、YAML、CSS、Less:执行 `prettier --write`
530
+
531
+ 可以自动修复的问题会写回暂存文件;仍存在的 ESLint 错误会阻止提交。lint-staged
532
+ 只负责提交前的快速增量检查,不替代完整的 `lint`、`typecheck`、`test` 和
533
+ `build`。
534
+
535
+ Prettier 对 JavaScript、TypeScript 和 JSX 统一使用单引号;JSON 按标准继续使用
536
+ 双引号。
537
+
538
+ `commit-msg` 使用 Commitlint 校验提交信息:
539
+
540
+ ```text
541
+ feat: add request client
542
+ fix: handle canceled request
543
+ ```
544
+
545
+ 缺少合法 type、subject 或其他不符合 Conventional Commits 的提交信息会被拒绝。
546
+
547
+ ## 第一版边界
548
+
549
+ 第一版不包含 Token 自动刷新、自动重试、缓存、请求去重、并发控制、业务 code
550
+ 判断、`data` 解包、分页/日期转换、错误上报、自动文件下载、内置业务 Mock、
551
+ React Context、Zustand 或复杂洋葱中间件。
package/package.json CHANGED
@@ -1,97 +1,99 @@
1
- {
2
- "name": "@gz-fronted/gz-pc",
3
- "version": "0.1.1-beta.4",
4
- "description": "Shared fetch, hooks and utility primitives for GZ frontend applications.",
5
- "license": "MIT",
6
- "type": "module",
7
- "sideEffects": false,
8
- "files": [
9
- "dist",
10
- "README.md"
11
- ],
12
- "exports": {
13
- "./fetch": {
14
- "import": {
15
- "types": "./dist/fetch/index.d.ts",
16
- "default": "./dist/fetch/index.js"
17
- },
18
- "require": {
19
- "types": "./dist/fetch/index.d.cts",
20
- "default": "./dist/fetch/index.cjs"
21
- }
22
- },
23
- "./hooks": {
24
- "import": {
25
- "types": "./dist/hooks/index.d.ts",
26
- "default": "./dist/hooks/index.js"
27
- },
28
- "require": {
29
- "types": "./dist/hooks/index.d.cts",
30
- "default": "./dist/hooks/index.cjs"
31
- }
32
- },
33
- "./utils": {
34
- "import": {
35
- "types": "./dist/utils/index.d.ts",
36
- "default": "./dist/utils/index.js"
37
- },
38
- "require": {
39
- "types": "./dist/utils/index.d.cts",
40
- "default": "./dist/utils/index.cjs"
41
- }
42
- },
43
- "./package.json": "./package.json"
44
- },
45
- "dependencies": {
46
- "ahooks": "^3.9.7",
47
- "axios": "^1.13.6"
48
- },
49
- "peerDependencies": {
50
- "@chenhui996/gg-ui": "^0.1.1",
51
- "react": "^18.0.0 || ^19.0.0"
52
- },
53
- "devDependencies": {
54
- "@chenhui996/gg-ui": "0.1.1",
55
- "@commitlint/cli": "21.2.1",
56
- "@commitlint/config-conventional": "21.2.0",
57
- "@eslint/js": "9.39.4",
58
- "@types/node": "24.10.0",
59
- "@types/react": "19.2.14",
60
- "@types/react-dom": "19.2.3",
61
- "eslint": "9.39.4",
62
- "globals": "16.5.0",
63
- "husky": "9.1.7",
64
- "lint-staged": "17.2.0",
65
- "msw": "2.12.7",
66
- "prettier": "3.9.6",
67
- "react": "19.2.4",
68
- "react-dom": "19.2.4",
69
- "tsup": "8.5.1",
70
- "typescript": "5.9.3",
71
- "typescript-eslint": "8.56.1",
72
- "vitest": "4.1.4"
73
- },
74
- "engines": {
75
- "node": ">=20"
76
- },
77
- "publishConfig": {
78
- "access": "public"
79
- },
80
- "lint-staged": {
81
- "*.{js,jsx,ts,tsx}": [
82
- "eslint --fix",
83
- "prettier --write"
84
- ],
85
- "*.{json,md,yml,yaml,css,less}": [
86
- "prettier --write"
87
- ]
88
- },
89
- "scripts": {
90
- "build": "node scripts/build.mjs",
91
- "lint": "eslint .",
92
- "typecheck": "tsc --noEmit",
93
- "test": "vitest run",
94
- "test:watch": "vitest",
95
- "verify:exports": "node tests/exports/smoke.mjs"
96
- }
97
- }
1
+ {
2
+ "name": "@gz-fronted/gz-pc",
3
+ "version": "0.1.1",
4
+ "description": "Shared fetch, hooks and utility primitives for GZ frontend applications.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "files": [
9
+ "dist",
10
+ "README.md"
11
+ ],
12
+ "exports": {
13
+ "./fetch": {
14
+ "import": {
15
+ "types": "./dist/fetch/index.d.ts",
16
+ "default": "./dist/fetch/index.js"
17
+ },
18
+ "require": {
19
+ "types": "./dist/fetch/index.d.cts",
20
+ "default": "./dist/fetch/index.cjs"
21
+ }
22
+ },
23
+ "./hooks": {
24
+ "import": {
25
+ "types": "./dist/hooks/index.d.ts",
26
+ "default": "./dist/hooks/index.js"
27
+ },
28
+ "require": {
29
+ "types": "./dist/hooks/index.d.cts",
30
+ "default": "./dist/hooks/index.cjs"
31
+ }
32
+ },
33
+ "./utils": {
34
+ "import": {
35
+ "types": "./dist/utils/index.d.ts",
36
+ "default": "./dist/utils/index.js"
37
+ },
38
+ "require": {
39
+ "types": "./dist/utils/index.d.cts",
40
+ "default": "./dist/utils/index.cjs"
41
+ }
42
+ },
43
+ "./package.json": "./package.json"
44
+ },
45
+ "scripts": {
46
+ "build": "node scripts/build.mjs",
47
+ "lint": "eslint .",
48
+ "typecheck": "tsc --noEmit",
49
+ "test": "vitest run",
50
+ "test:watch": "vitest",
51
+ "verify:exports": "node tests/exports/smoke.mjs",
52
+ "prepublishOnly": "npm run build",
53
+ "prepare": "husky"
54
+ },
55
+ "dependencies": {
56
+ "ahooks": "^3.9.7",
57
+ "axios": "^1.13.6"
58
+ },
59
+ "peerDependencies": {
60
+ "@chenhui996/gg-ui": "^0.1.1",
61
+ "react": "^18.0.0 || ^19.0.0"
62
+ },
63
+ "devDependencies": {
64
+ "@chenhui996/gg-ui": "0.1.1",
65
+ "@commitlint/cli": "21.2.1",
66
+ "@commitlint/config-conventional": "21.2.0",
67
+ "@eslint/js": "9.39.4",
68
+ "@types/node": "24.10.0",
69
+ "@types/react": "19.2.14",
70
+ "@types/react-dom": "19.2.3",
71
+ "eslint": "9.39.4",
72
+ "globals": "16.5.0",
73
+ "husky": "9.1.7",
74
+ "lint-staged": "17.2.0",
75
+ "msw": "2.12.7",
76
+ "prettier": "3.9.6",
77
+ "react": "19.2.4",
78
+ "react-dom": "19.2.4",
79
+ "tsup": "8.5.1",
80
+ "typescript": "5.9.3",
81
+ "typescript-eslint": "8.56.1",
82
+ "vitest": "4.1.4"
83
+ },
84
+ "engines": {
85
+ "node": ">=20"
86
+ },
87
+ "publishConfig": {
88
+ "access": "public"
89
+ },
90
+ "lint-staged": {
91
+ "*.{js,jsx,ts,tsx}": [
92
+ "eslint --fix",
93
+ "prettier --write"
94
+ ],
95
+ "*.{json,md,yml,yaml,css,less}": [
96
+ "prettier --write"
97
+ ]
98
+ }
99
+ }