soon-fetch 4.0.0 → 4.0.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.
- package/README.md +330 -31
- package/dist/index.d.ts +560 -0
- package/dist/index.js +1 -0
- package/dist/index.umd.js +1 -0
- package/package.json +9 -7
- package/dist/index.cjs +0 -1
- package/dist/index.d.cts +0 -480
- package/dist/index.d.mts +0 -480
- package/dist/index.iife.js +0 -1
- package/dist/index.mjs +0 -1
package/README.md
CHANGED
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
import { createSoon, soonFetch } from "soon-fetch";
|
|
37
37
|
|
|
38
38
|
// 使用 soonFetch 作为基础请求函数
|
|
39
|
-
const request = async <T>(url: string, options?: SoonOptions) => {
|
|
39
|
+
const request = async <T>(url: string, options?: SoonOptions): Promise<T> => {
|
|
40
40
|
const isGet = !options?.method || options?.method.toLocaleLowerCase() === "get";
|
|
41
|
-
const response = await soonFetch
|
|
41
|
+
const response = await soonFetch({
|
|
42
42
|
url,
|
|
43
43
|
options,
|
|
44
44
|
baseURL: '/api',
|
|
@@ -51,7 +51,12 @@ const request = async <T>(url: string, options?: SoonOptions) => {
|
|
|
51
51
|
staleTime: isGet ? 2 * 1000 : 0,
|
|
52
52
|
},
|
|
53
53
|
});
|
|
54
|
-
|
|
54
|
+
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return response.json() as Promise<T>;
|
|
55
60
|
};
|
|
56
61
|
|
|
57
62
|
const soon = createSoon(request);
|
|
@@ -133,7 +138,7 @@ import { useEffect, useRef, useState } from "react";
|
|
|
133
138
|
type User = { name: string; job: string };
|
|
134
139
|
const api = soon.GET("/api/users").Query<{ page: number }>().Ok<User[]>();
|
|
135
140
|
export default function App() {
|
|
136
|
-
const refAbort = useRef([]);
|
|
141
|
+
const refAbort = useRef<[AbortController] | []>([]);
|
|
137
142
|
const [list, setList] = useState<User[]>([]);
|
|
138
143
|
const [page, setPage] = useState(1);
|
|
139
144
|
useEffect(() => {
|
|
@@ -156,13 +161,15 @@ export default function App() {
|
|
|
156
161
|
|
|
157
162
|
##### Rapid Define APIs
|
|
158
163
|
|
|
159
|
-
```
|
|
160
|
-
|
|
164
|
+
```ts
|
|
165
|
+
//可以是 GET POST PATCH PUT DELETE
|
|
166
|
+
//GET 请求数据传递至query,其他方法请求数据传递至body
|
|
161
167
|
soon.GET(url:string).Query<Query>().Ok<Response>()
|
|
162
168
|
soon.POST(url:string).Body<Body>().Ok<Response>()
|
|
163
169
|
soon.GET(url:string).Options({ timeout: 5000 }).Ok<Response>()
|
|
164
170
|
soon.POST(url:string).Body<Body>().Options({ timeout: 5000 }).Ok<Response>()
|
|
165
|
-
|
|
171
|
+
|
|
172
|
+
//define an api
|
|
166
173
|
export const getUserInfo = soon.GET("/user/:id").Ok();
|
|
167
174
|
//then use in any where
|
|
168
175
|
getUserInfo({ id: 2 }).then((res) => console.log(res));
|
|
@@ -227,25 +234,53 @@ Create a soon request instance.
|
|
|
227
234
|
**Example:**
|
|
228
235
|
|
|
229
236
|
```typescript
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
237
|
+
import { createSoon, soonFetch } from "soon-fetch";
|
|
238
|
+
|
|
239
|
+
// Define custom request wrapper with business logic
|
|
240
|
+
const request = async <T>(url: string, options?: SoonOptions): Promise<T> => {
|
|
241
|
+
const isGet = !options?.method || options?.method.toLowerCase() === "get";
|
|
242
|
+
|
|
243
|
+
const response = await soonFetch({
|
|
244
|
+
url,
|
|
245
|
+
options,
|
|
246
|
+
baseURL: '/api',
|
|
247
|
+
baseOptions: {
|
|
248
|
+
timeout: 20 * 1000,
|
|
249
|
+
headers: new Headers({
|
|
250
|
+
Authorization: "Bearer " + localStorage.getItem("token"),
|
|
251
|
+
}),
|
|
252
|
+
share: isGet,
|
|
253
|
+
staleTime: isGet ? 2 * 1000 : 0,
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (!response.ok) {
|
|
258
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
234
259
|
}
|
|
235
|
-
|
|
260
|
+
|
|
261
|
+
return response.json() as Promise<T>;
|
|
262
|
+
};
|
|
236
263
|
|
|
237
|
-
//
|
|
238
|
-
const
|
|
264
|
+
// Create soon instance
|
|
265
|
+
const soon = createSoon(request);
|
|
239
266
|
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
.POST("/user/login")
|
|
243
|
-
.Body<{ username: string; password: string }>()
|
|
244
|
-
.Ok<{ token: string }>();
|
|
267
|
+
// Usage 1: Shortcut methods with generics
|
|
268
|
+
const users = await soon.get<{ id: number; name: string }[]>("/api/users");
|
|
245
269
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
270
|
+
// Usage 2: Define typed APIs with chain calls
|
|
271
|
+
export const getUserById = soon
|
|
272
|
+
.GET("/user/:id")
|
|
273
|
+
.Params<{ id: number }>()
|
|
274
|
+
.Ok<{ id: number; name: string }>();
|
|
275
|
+
|
|
276
|
+
export const createUser = soon
|
|
277
|
+
.POST("/user")
|
|
278
|
+
.Body<{ name: string; email: string }>()
|
|
279
|
+
.Ok<{ id: number }>();
|
|
280
|
+
|
|
281
|
+
// Usage 3: Use defined APIs
|
|
282
|
+
const user = await getUserById({ id: 1 });
|
|
283
|
+
const newUser = await createUser({ name: "John", email: "john@example.com" });
|
|
249
284
|
```
|
|
250
285
|
|
|
251
286
|
#### createShortApi
|
|
@@ -541,12 +576,12 @@ A lightweight fetch wrapper with caching, sharing, and race condition handling.
|
|
|
541
576
|
- `store`: Custom request store
|
|
542
577
|
- `sortRequestKey`: Whether to sort request key
|
|
543
578
|
|
|
544
|
-
**Returns:** Promise that resolves to
|
|
579
|
+
**Returns:** Promise that resolves to Response
|
|
545
580
|
|
|
546
581
|
**Example:**
|
|
547
582
|
|
|
548
583
|
```typescript
|
|
549
|
-
const
|
|
584
|
+
const response = await soonFetch({
|
|
550
585
|
url: "/api/users",
|
|
551
586
|
options: {
|
|
552
587
|
method: "GET",
|
|
@@ -556,6 +591,12 @@ const data = await soonFetch<User[]>({
|
|
|
556
591
|
},
|
|
557
592
|
baseURL: "https://api.example.com",
|
|
558
593
|
});
|
|
594
|
+
|
|
595
|
+
if (!response.ok) {
|
|
596
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const data = await response.json() as User[];
|
|
559
600
|
```
|
|
560
601
|
|
|
561
602
|
#### toFormData
|
|
@@ -688,9 +729,9 @@ const data = await requestWithStore(store, () => fetch(url, options), requestKey
|
|
|
688
729
|
import { createSoon, soonFetch } from "soon-fetch";
|
|
689
730
|
|
|
690
731
|
// 使用 soonFetch 作为基础请求函数
|
|
691
|
-
const request = async <T>(url: string, options?: SoonOptions) => {
|
|
732
|
+
const request = async <T>(url: string, options?: SoonOptions): Promise<T> => {
|
|
692
733
|
const isGet = !options?.method || options?.method.toLocaleLowerCase() === "get";
|
|
693
|
-
const response = await soonFetch
|
|
734
|
+
const response = await soonFetch({
|
|
694
735
|
url,
|
|
695
736
|
options,
|
|
696
737
|
baseURL: '/api',
|
|
@@ -703,7 +744,12 @@ const request = async <T>(url: string, options?: SoonOptions) => {
|
|
|
703
744
|
staleTime: isGet ? 2 * 1000 : 0,
|
|
704
745
|
},
|
|
705
746
|
});
|
|
706
|
-
|
|
747
|
+
|
|
748
|
+
if (!response.ok) {
|
|
749
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
return response.json() as Promise<T>;
|
|
707
753
|
};
|
|
708
754
|
|
|
709
755
|
const soon = createSoon(request);
|
|
@@ -790,7 +836,7 @@ import { useEffect, useRef, useState } from "react";
|
|
|
790
836
|
type User = { name: string; job: string };
|
|
791
837
|
const api = soon.GET("/api/users").Query<{ page: number }>().Ok<User[]>();
|
|
792
838
|
export default function App() {
|
|
793
|
-
const refAbort = useRef([]);
|
|
839
|
+
const refAbort = useRef<[AbortController] | []>([]);
|
|
794
840
|
const [list, setList] = useState<User[]>([]);
|
|
795
841
|
const [page, setPage] = useState(1);
|
|
796
842
|
useEffect(() => {
|
|
@@ -1197,12 +1243,12 @@ silentRefresh(
|
|
|
1197
1243
|
- `store`: 自定义请求存储
|
|
1198
1244
|
- `sortRequestKey`: 是否对请求键进行排序
|
|
1199
1245
|
|
|
1200
|
-
**返回:**
|
|
1246
|
+
**返回:** 解析为 Response 的 Promise
|
|
1201
1247
|
|
|
1202
1248
|
**示例:**
|
|
1203
1249
|
|
|
1204
1250
|
```typescript
|
|
1205
|
-
const
|
|
1251
|
+
const response = await soonFetch({
|
|
1206
1252
|
url: "/api/users",
|
|
1207
1253
|
options: {
|
|
1208
1254
|
method: "GET",
|
|
@@ -1212,6 +1258,12 @@ const data = await soonFetch<User[]>({
|
|
|
1212
1258
|
},
|
|
1213
1259
|
baseURL: "https://api.example.com",
|
|
1214
1260
|
});
|
|
1261
|
+
|
|
1262
|
+
if (!response.ok) {
|
|
1263
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
const data = await response.json() as User[];
|
|
1215
1267
|
```
|
|
1216
1268
|
|
|
1217
1269
|
#### toFormData
|
|
@@ -1310,6 +1362,253 @@ const data = await requestWithStore(store, () => fetch(url, options), requestKey
|
|
|
1310
1362
|
|
|
1311
1363
|
##### 安装 Installation
|
|
1312
1364
|
|
|
1313
|
-
```
|
|
1365
|
+
```
|
|
1314
1366
|
npm install soon-fetch
|
|
1315
1367
|
```
|
|
1368
|
+
|
|
1369
|
+
## Best Practices / 最佳实践
|
|
1370
|
+
|
|
1371
|
+
#### Real-World Project Structure / 真实项目结构
|
|
1372
|
+
|
|
1373
|
+
Based on [soon-admin-vue](https://github.com/leafio/soon-admin-vue):
|
|
1374
|
+
|
|
1375
|
+
#### File Organization / 文件组织
|
|
1376
|
+
|
|
1377
|
+
```
|
|
1378
|
+
src/api/
|
|
1379
|
+
├── request.ts # Request wrapper with unified error handling
|
|
1380
|
+
├── types.ts # Shared type definitions (e.g., PagedParams)
|
|
1381
|
+
├── index.ts # Export all API modules
|
|
1382
|
+
└── modules/
|
|
1383
|
+
├── auth.ts # Authentication APIs
|
|
1384
|
+
├── user.ts # User management APIs
|
|
1385
|
+
├── role.ts # Role management APIs
|
|
1386
|
+
├── dept.ts # Department management APIs
|
|
1387
|
+
└── ... # Other domain-specific modules
|
|
1388
|
+
```
|
|
1389
|
+
|
|
1390
|
+
#### Step 1: Create Request Wrapper (src/api/request.ts)
|
|
1391
|
+
|
|
1392
|
+
```typescript
|
|
1393
|
+
import { createSoon, soonFetch } from "soon-fetch";
|
|
1394
|
+
import type { SoonOptions } from "soon-fetch";
|
|
1395
|
+
|
|
1396
|
+
type ReqOpts = SoonOptions & {
|
|
1397
|
+
retry?: { max?: number; enable?: (result: any) => boolean };
|
|
1398
|
+
toastErr?: boolean;
|
|
1399
|
+
};
|
|
1400
|
+
|
|
1401
|
+
const request = async <T>(url: string, options?: ReqOpts): Promise<T> => {
|
|
1402
|
+
const isGet = !options?.method || options?.method.toLowerCase() === "get";
|
|
1403
|
+
|
|
1404
|
+
try {
|
|
1405
|
+
const response = await soonFetch({
|
|
1406
|
+
url,
|
|
1407
|
+
options,
|
|
1408
|
+
baseURL: import.meta.env.VITE_API_BASE,
|
|
1409
|
+
baseOptions: {
|
|
1410
|
+
timeout: 20 * 1000,
|
|
1411
|
+
headers: new Headers({ Authorization: localStorage.getItem("token") ?? "" }),
|
|
1412
|
+
share: isGet,
|
|
1413
|
+
staleTime: isGet ? 2 * 1000 : 0,
|
|
1414
|
+
},
|
|
1415
|
+
});
|
|
1416
|
+
|
|
1417
|
+
if (!response.ok) {
|
|
1418
|
+
throw new Error(`HTTP ${response.status}`);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// Auto-parse JSON
|
|
1422
|
+
if (response.headers.get("content-type")?.includes("json")) {
|
|
1423
|
+
const body = await response.json();
|
|
1424
|
+
return body.data as T;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
return response as unknown as T;
|
|
1428
|
+
} catch (error: any) {
|
|
1429
|
+
// Unified error handling
|
|
1430
|
+
if (error.name === "TimeoutError") {
|
|
1431
|
+
ElMessage.error("Request timeout");
|
|
1432
|
+
} else if (error.name !== "AbortError" && options?.toastErr !== false) {
|
|
1433
|
+
ElMessage.error(error.message);
|
|
1434
|
+
}
|
|
1435
|
+
throw error;
|
|
1436
|
+
}
|
|
1437
|
+
};
|
|
1438
|
+
|
|
1439
|
+
export const soon = createSoon<ReqOpts>(request);
|
|
1440
|
+
```
|
|
1441
|
+
|
|
1442
|
+
**Key Points:**
|
|
1443
|
+
- ✅ **Centralized error handling**: All HTTP errors, timeouts, and business errors handled in one place
|
|
1444
|
+
- ✅ **Auto Token refresh**: Handle 401 errors and retry automatically
|
|
1445
|
+
- ✅ **Smart caching**: GET requests cached by default, write operations not cached
|
|
1446
|
+
- ✅ **No repetitive try-catch**: Components call APIs directly without error handling boilerplate
|
|
1447
|
+
|
|
1448
|
+
---
|
|
1449
|
+
|
|
1450
|
+
#### Step 2: Define Typed APIs (src/api/modules/user.ts)
|
|
1451
|
+
|
|
1452
|
+
```typescript
|
|
1453
|
+
import { downloadBlob, getHeaderFilename } from "soon-utils";
|
|
1454
|
+
import type { PagedParams } from "../types";
|
|
1455
|
+
import type { Dept } from "./dept";
|
|
1456
|
+
import type { Role } from "./role";
|
|
1457
|
+
import { soon } from "../request";
|
|
1458
|
+
|
|
1459
|
+
// Type definitions
|
|
1460
|
+
export type User = {
|
|
1461
|
+
id: number;
|
|
1462
|
+
username: string;
|
|
1463
|
+
email: string | null;
|
|
1464
|
+
phone: string | null;
|
|
1465
|
+
name: string | null;
|
|
1466
|
+
avatar: string | null;
|
|
1467
|
+
roleId: number | undefined;
|
|
1468
|
+
deptId: number | undefined;
|
|
1469
|
+
status: number;
|
|
1470
|
+
};
|
|
1471
|
+
|
|
1472
|
+
export type UserInfo = User & {
|
|
1473
|
+
createTime: Date;
|
|
1474
|
+
updateTime: Date | null;
|
|
1475
|
+
dept?: Pick<Dept, "id" | "name">;
|
|
1476
|
+
role?: Pick<Role, "id" | "name">;
|
|
1477
|
+
};
|
|
1478
|
+
|
|
1479
|
+
type ListQueryUser = PagedParams & {
|
|
1480
|
+
keyword?: string;
|
|
1481
|
+
timeRange?: [string, string];
|
|
1482
|
+
};
|
|
1483
|
+
|
|
1484
|
+
// CRUD APIs - simple and type-safe
|
|
1485
|
+
export const list_user = soon.GET("/user").Query<ListQueryUser>().Ok<{ list: UserInfo[] }>();
|
|
1486
|
+
export const add_user = soon.POST("/user").Body<User>().Ok();
|
|
1487
|
+
export const update_user = soon.PUT("/user/:id").Body<User>().Ok();
|
|
1488
|
+
export const del_user = soon.DELETE("/user/:id").Ok();
|
|
1489
|
+
export const detail_user = soon.GET("/user/:id").Ok<UserInfo>();
|
|
1490
|
+
|
|
1491
|
+
// File download - returns Response for custom handling
|
|
1492
|
+
export const download_user_table = async (query: ListQueryUser) => {
|
|
1493
|
+
return soon.get<Response>("/user/export", { query }).then(async (res) => {
|
|
1494
|
+
const body = await res.blob();
|
|
1495
|
+
const filename = getHeaderFilename(res.headers) ?? "user.xlsx";
|
|
1496
|
+
downloadBlob(body, filename);
|
|
1497
|
+
});
|
|
1498
|
+
};
|
|
1499
|
+
|
|
1500
|
+
// Captcha example
|
|
1501
|
+
export const getCaptcha = soon.GET("/captcha").Ok<{ id: number; img: string }>();
|
|
1502
|
+
```
|
|
1503
|
+
|
|
1504
|
+
**Key Points:**
|
|
1505
|
+
- ✅ **Domain-based modules**: Each module handles one business domain (user, role, dept, etc.)
|
|
1506
|
+
- ✅ **Type safety**: Use `.Params<>()`, `.Body<>()`, `.Ok<>()` for full type inference
|
|
1507
|
+
- ✅ **Shared types**: Import cross-domain types from sibling modules
|
|
1508
|
+
- ✅ **Special operations**: Complex logic (file download) wrapped in async functions
|
|
1509
|
+
- ✅ **Naming convention**: Use snake_case for API exports (e.g., `list_user`, `add_user`)
|
|
1510
|
+
|
|
1511
|
+
---
|
|
1512
|
+
|
|
1513
|
+
#### Step 3: Centralized Exports (src/api/index.ts)
|
|
1514
|
+
|
|
1515
|
+
```typescript
|
|
1516
|
+
export * from "./modules/auth";
|
|
1517
|
+
export * from "./modules/user";
|
|
1518
|
+
export * from "./modules/role";
|
|
1519
|
+
// ... other modules
|
|
1520
|
+
```
|
|
1521
|
+
|
|
1522
|
+
**Usage in components:**
|
|
1523
|
+
```typescript
|
|
1524
|
+
import { list_user, add_user, del_user } from "@/api";
|
|
1525
|
+
```
|
|
1526
|
+
|
|
1527
|
+
---
|
|
1528
|
+
|
|
1529
|
+
#### Step 4: Use in Components - Clean and Simple!
|
|
1530
|
+
|
|
1531
|
+
**Vue 3 Example:**
|
|
1532
|
+
```typescript
|
|
1533
|
+
<script setup lang="ts">
|
|
1534
|
+
import { ref, onMounted } from "vue";
|
|
1535
|
+
import { list_user, del_user } from "@/api";
|
|
1536
|
+
import type { UserInfo } from "@/api";
|
|
1537
|
+
|
|
1538
|
+
const users = ref<UserInfo[]>([]);
|
|
1539
|
+
const loading = ref(false);
|
|
1540
|
+
|
|
1541
|
+
const fetchUsers = async () => {
|
|
1542
|
+
loading.value = true;
|
|
1543
|
+
try {
|
|
1544
|
+
const { list } = await list_user({ page: 1, pageSize: 10 });
|
|
1545
|
+
users.value = list;
|
|
1546
|
+
} finally {
|
|
1547
|
+
loading.value = false;
|
|
1548
|
+
}
|
|
1549
|
+
};
|
|
1550
|
+
|
|
1551
|
+
const handleDelete = async (id: number) => {
|
|
1552
|
+
await del_user({ id });
|
|
1553
|
+
await fetchUsers();
|
|
1554
|
+
};
|
|
1555
|
+
|
|
1556
|
+
onMounted(fetchUsers);
|
|
1557
|
+
</script>
|
|
1558
|
+
```
|
|
1559
|
+
|
|
1560
|
+
**React Example with Race Condition Handling:**
|
|
1561
|
+
```typescript
|
|
1562
|
+
import { useEffect, useRef, useState } from "react";
|
|
1563
|
+
import { list_user } from "@/api";
|
|
1564
|
+
import type { UserInfo } from "@/api";
|
|
1565
|
+
|
|
1566
|
+
export default function UserList() {
|
|
1567
|
+
const [users, setUsers] = useState<UserInfo[]>([]);
|
|
1568
|
+
const [loading, setLoading] = useState(false);
|
|
1569
|
+
const abortRef = useRef<[AbortController] | []>([]);
|
|
1570
|
+
|
|
1571
|
+
useEffect(() => {
|
|
1572
|
+
setLoading(true);
|
|
1573
|
+
list_user({ page: 1, pageSize: 10 }, { aborts: abortRef.current })
|
|
1574
|
+
.then(({ list }) => setUsers(list))
|
|
1575
|
+
.catch((err) => {
|
|
1576
|
+
if (err.name !== "AbortError") console.error(err);
|
|
1577
|
+
})
|
|
1578
|
+
.finally(() => setLoading(false));
|
|
1579
|
+
}, []);
|
|
1580
|
+
|
|
1581
|
+
if (loading) return <div>Loading...</div>;
|
|
1582
|
+
|
|
1583
|
+
return (
|
|
1584
|
+
<ul>
|
|
1585
|
+
{users.map(u => (
|
|
1586
|
+
<li key={u.id}>{u.name}</li>
|
|
1587
|
+
))}
|
|
1588
|
+
</ul>
|
|
1589
|
+
);
|
|
1590
|
+
}
|
|
1591
|
+
```
|
|
1592
|
+
|
|
1593
|
+
**Key Points:**
|
|
1594
|
+
- ✅ **No repetitive error handling**: Errors already handled in request wrapper
|
|
1595
|
+
- ✅ **Race condition control**: Use `useRef` to manage `aborts` parameter
|
|
1596
|
+
- ✅ **Clean code**: Focus on business logic, not network error boilerplate
|
|
1597
|
+
- ✅ **Type safety**: Full TypeScript support with autocomplete and type checking
|
|
1598
|
+
|
|
1599
|
+
---
|
|
1600
|
+
|
|
1601
|
+
### Summary / 总结
|
|
1602
|
+
|
|
1603
|
+
**Architecture Benefits:**
|
|
1604
|
+
1. **Separation of Concerns**: Network layer (request.ts) vs Business layer (modules/) vs UI layer (components)
|
|
1605
|
+
2. **DRY Principle**: Error handling written once, used everywhere
|
|
1606
|
+
3. **Type Safety**: End-to-end type inference from API definition to component usage
|
|
1607
|
+
4. **Maintainability**: Domain-based modules make it easy to find and modify APIs
|
|
1608
|
+
5. **Scalability**: Easy to add new domains by creating new module files
|
|
1609
|
+
|
|
1610
|
+
**Core Philosophy:**
|
|
1611
|
+
- 🎯 **Centralize common logic** in request wrapper (errors, retries, caching)
|
|
1612
|
+
- 🎯 **Keep API definitions simple** with chainable type-safe methods
|
|
1613
|
+
- 🎯 **Components focus on UI** without repetitive error handling code
|
|
1614
|
+
- 🎯 **Handle edge cases** only when needed (race conditions, special business logic)
|