kmod-cli 1.8.2 → 1.9.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.
|
@@ -135,10 +135,30 @@ export interface DataProviderError {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
export interface DataProviderOptions {
|
|
138
|
+
/**
|
|
139
|
+
* Time to live for cached data (default: 5 * 60 * 1000) - 5 minutes
|
|
140
|
+
*/
|
|
138
141
|
cacheTime?: number;
|
|
142
|
+
/**
|
|
143
|
+
* Time to live for stale data (default: 5 * 60 * 1000) - 5 minutes
|
|
144
|
+
*/
|
|
145
|
+
staleTime?: number;
|
|
146
|
+
/**
|
|
147
|
+
* Retry count (default: 1)
|
|
148
|
+
*/
|
|
139
149
|
retryCount?: number;
|
|
150
|
+
/**
|
|
151
|
+
* Retry delay (ms) (default: 1000) - 1 second
|
|
152
|
+
*/
|
|
140
153
|
retryDelay?: number;
|
|
154
|
+
/**
|
|
155
|
+
* Debug mode (default: false)
|
|
156
|
+
*/
|
|
141
157
|
debug?: boolean;
|
|
158
|
+
/**
|
|
159
|
+
* Persist data in memory, local storage, or session storage (default: memory)
|
|
160
|
+
*/
|
|
161
|
+
persist?: "memory" | "local" | "session";
|
|
142
162
|
}
|
|
143
163
|
|
|
144
164
|
interface CacheItem<T = any> {
|
|
@@ -149,6 +169,7 @@ interface CacheItem<T = any> {
|
|
|
149
169
|
export interface UseListOptions {
|
|
150
170
|
refetchInterval?: number;
|
|
151
171
|
enabled?: boolean;
|
|
172
|
+
cache?: boolean;
|
|
152
173
|
}
|
|
153
174
|
|
|
154
175
|
export interface UseOneOptions {
|
|
@@ -221,6 +242,8 @@ class DataProvider {
|
|
|
221
242
|
private cache: Map<string, CacheItem>;
|
|
222
243
|
private options: Required<DataProviderOptions>;
|
|
223
244
|
|
|
245
|
+
private cachePrefix = "__DP_CACHE__:";
|
|
246
|
+
|
|
224
247
|
constructor(
|
|
225
248
|
httpClient: AxiosInstance = axios.create(),
|
|
226
249
|
options: DataProviderOptions = {},
|
|
@@ -239,11 +262,31 @@ class DataProvider {
|
|
|
239
262
|
this.cache = new Map();
|
|
240
263
|
this.options = {
|
|
241
264
|
cacheTime: 5 * 60 * 1000,
|
|
265
|
+
staleTime: 30 * 1000,
|
|
266
|
+
persist: "memory",
|
|
242
267
|
retryCount: 1,
|
|
243
268
|
retryDelay: 1000,
|
|
244
269
|
debug: false,
|
|
245
270
|
...options,
|
|
246
271
|
};
|
|
272
|
+
|
|
273
|
+
this.cache = new Map();
|
|
274
|
+
this.loadPersistCache();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private loadPersistCache() {
|
|
278
|
+
if (this.options.persist === "memory") return;
|
|
279
|
+
|
|
280
|
+
const storage =
|
|
281
|
+
this.options.persist === "local" ? localStorage : sessionStorage;
|
|
282
|
+
|
|
283
|
+
Object.keys(storage).forEach((key) => {
|
|
284
|
+
if (!key.startsWith(this.cachePrefix)) return;
|
|
285
|
+
try {
|
|
286
|
+
const value = JSON.parse(storage.getItem(key)!);
|
|
287
|
+
this.cache.set(key.replace(this.cachePrefix, ""), value);
|
|
288
|
+
} catch {}
|
|
289
|
+
});
|
|
247
290
|
}
|
|
248
291
|
|
|
249
292
|
private log(message: string, data?: any): void {
|
|
@@ -257,27 +300,40 @@ class DataProvider {
|
|
|
257
300
|
return `${resource}:${JSON.stringify(params || {})}`;
|
|
258
301
|
}
|
|
259
302
|
|
|
260
|
-
private getCache<T
|
|
303
|
+
private getCache<T>(key: string): {
|
|
304
|
+
data: T;
|
|
305
|
+
isStale: boolean;
|
|
306
|
+
} | null {
|
|
261
307
|
const cached = this.cache.get(key);
|
|
262
308
|
if (!cached) return null;
|
|
263
309
|
|
|
264
|
-
const
|
|
265
|
-
|
|
310
|
+
const age = Date.now() - cached.timestamp;
|
|
311
|
+
|
|
312
|
+
if (age > this.options.cacheTime) {
|
|
266
313
|
this.cache.delete(key);
|
|
267
|
-
// this.log('Cache expired', key);
|
|
268
314
|
return null;
|
|
269
315
|
}
|
|
270
316
|
|
|
271
|
-
|
|
272
|
-
|
|
317
|
+
return {
|
|
318
|
+
data: cached.data as T,
|
|
319
|
+
isStale: age > this.options.staleTime,
|
|
320
|
+
};
|
|
273
321
|
}
|
|
274
322
|
|
|
275
|
-
private setCache<T
|
|
276
|
-
|
|
323
|
+
private setCache<T>(key: string, data: T) {
|
|
324
|
+
const item = {
|
|
277
325
|
data,
|
|
278
326
|
timestamp: Date.now(),
|
|
279
|
-
}
|
|
280
|
-
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
this.cache.set(key, item);
|
|
330
|
+
|
|
331
|
+
if (this.options.persist !== "memory") {
|
|
332
|
+
const storage =
|
|
333
|
+
this.options.persist === "local" ? localStorage : sessionStorage;
|
|
334
|
+
|
|
335
|
+
storage.setItem(this.cachePrefix + key, JSON.stringify(item));
|
|
336
|
+
}
|
|
281
337
|
}
|
|
282
338
|
|
|
283
339
|
public invalidateCache(resource: string, id?: string | number): void {
|
|
@@ -347,10 +403,21 @@ class DataProvider {
|
|
|
347
403
|
useCache: boolean = true,
|
|
348
404
|
): Promise<GetListResponse<T>> {
|
|
349
405
|
const cacheKey = this.getCacheKey(resource, params);
|
|
406
|
+
const cached = useCache
|
|
407
|
+
? this.getCache<GetListResponse<T>>(cacheKey)
|
|
408
|
+
: null;
|
|
409
|
+
|
|
410
|
+
if (cached) {
|
|
411
|
+
if (!cached.isStale) {
|
|
412
|
+
return cached.data;
|
|
413
|
+
}
|
|
350
414
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
415
|
+
// stale → trả data cũ + background refetch
|
|
416
|
+
this.getList(resource, params, false).then((fresh) => {
|
|
417
|
+
this.setCache(cacheKey, fresh);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
return cached.data;
|
|
354
421
|
}
|
|
355
422
|
|
|
356
423
|
const { pagination, filters, sorters, meta } = params;
|
|
@@ -437,10 +504,16 @@ class DataProvider {
|
|
|
437
504
|
): Promise<GetOneResponse<T>> {
|
|
438
505
|
const { id, meta } = params;
|
|
439
506
|
const cacheKey = this.getCacheKey(`${resource}/${id}`, {});
|
|
507
|
+
const cached = useCache ? this.getCache<GetOneResponse<T>>(cacheKey) : null;
|
|
508
|
+
|
|
509
|
+
if (cached) {
|
|
510
|
+
if (!cached.isStale) return cached.data;
|
|
511
|
+
|
|
512
|
+
this.getOne(resource, params, false).then((fresh) => {
|
|
513
|
+
this.setCache(cacheKey, fresh);
|
|
514
|
+
});
|
|
440
515
|
|
|
441
|
-
|
|
442
|
-
const cached = this.getCache<GetOneResponse<T>>(cacheKey);
|
|
443
|
-
if (cached) return cached;
|
|
516
|
+
return cached.data;
|
|
444
517
|
}
|
|
445
518
|
|
|
446
519
|
const url = `${this.apiUrl}/${resource}/${id}`;
|
|
@@ -743,32 +816,36 @@ export function useList<T = any>(
|
|
|
743
816
|
|
|
744
817
|
const dataProvider = useDataProvider();
|
|
745
818
|
|
|
746
|
-
const { refetchInterval, enabled = true } = options;
|
|
819
|
+
const { refetchInterval, enabled = true, cache = true } = options;
|
|
747
820
|
const paramsStr = JSON.stringify(params);
|
|
748
821
|
|
|
749
|
-
const refetch = useCallback(
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
822
|
+
const refetch = useCallback(
|
|
823
|
+
async (force = false) => {
|
|
824
|
+
if (!enabled) {
|
|
825
|
+
setLoading(false);
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
754
828
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
829
|
+
try {
|
|
830
|
+
setLoading(true);
|
|
831
|
+
setError(null);
|
|
832
|
+
const result = await dataProvider.getList<T>(
|
|
833
|
+
resource,
|
|
834
|
+
JSON.parse(paramsStr),
|
|
835
|
+
!force,
|
|
836
|
+
);
|
|
837
|
+
setData(result.data || []);
|
|
838
|
+
setTotal(result.total || 0);
|
|
839
|
+
} catch (err) {
|
|
840
|
+
setError(err as DataProviderError);
|
|
841
|
+
setData([]);
|
|
842
|
+
setTotal(0);
|
|
843
|
+
} finally {
|
|
844
|
+
setLoading(false);
|
|
845
|
+
}
|
|
846
|
+
},
|
|
847
|
+
[dataProvider, resource, paramsStr, enabled],
|
|
848
|
+
);
|
|
772
849
|
|
|
773
850
|
useEffect(() => {
|
|
774
851
|
refetch();
|
|
@@ -797,24 +874,27 @@ export function useOne<T = any>(
|
|
|
797
874
|
|
|
798
875
|
const { enabled = true } = options;
|
|
799
876
|
|
|
800
|
-
const refetch = useCallback(
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
877
|
+
const refetch = useCallback(
|
|
878
|
+
async (force = false) => {
|
|
879
|
+
if (!enabled || !id) {
|
|
880
|
+
setLoading(false);
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
805
883
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
884
|
+
try {
|
|
885
|
+
setLoading(true);
|
|
886
|
+
setError(null);
|
|
887
|
+
const result = await dataProvider.getOne<T>(resource, { id }, !force);
|
|
888
|
+
setData(result.data);
|
|
889
|
+
} catch (err) {
|
|
890
|
+
setError(err as DataProviderError);
|
|
891
|
+
setData(null);
|
|
892
|
+
} finally {
|
|
893
|
+
setLoading(false);
|
|
894
|
+
}
|
|
895
|
+
},
|
|
896
|
+
[dataProvider, resource, id, enabled],
|
|
897
|
+
);
|
|
818
898
|
|
|
819
899
|
useEffect(() => {
|
|
820
900
|
refetch();
|
|
@@ -1290,78 +1370,158 @@ export const cookiesProvider = {
|
|
|
1290
1370
|
},
|
|
1291
1371
|
};
|
|
1292
1372
|
|
|
1293
|
-
// ===================
|
|
1294
|
-
|
|
1295
|
-
// create httpClient - (can create multiple httpClients for different apis)
|
|
1373
|
+
// =================== 1. Create httpClient ===================
|
|
1296
1374
|
|
|
1297
1375
|
// const TOKEN = "token";
|
|
1298
1376
|
|
|
1299
1377
|
// const httpClient = createHttpClient({
|
|
1300
|
-
// url:
|
|
1378
|
+
// url: process.env.NEXT_PUBLIC_API_URL,
|
|
1301
1379
|
// options: {
|
|
1302
1380
|
// authorizationType: "Bearer",
|
|
1303
1381
|
// tokenName: TOKEN,
|
|
1304
|
-
// tokenStorage: "cookie",
|
|
1305
|
-
// withCredentials: true,
|
|
1382
|
+
// tokenStorage: "cookie", // or "http-only" | "local" | "session"
|
|
1383
|
+
// withCredentials: true, // optional (auto true if tokenStorage = "http-only")
|
|
1306
1384
|
// },
|
|
1307
1385
|
// });
|
|
1308
1386
|
|
|
1309
|
-
//
|
|
1387
|
+
// =================== 2. Create DataProvider ===================
|
|
1310
1388
|
|
|
1311
|
-
//
|
|
1389
|
+
// IMPORTANT:
|
|
1390
|
+
// persist !== "memory" --> to CACHE data and prevent F5 from calling API again
|
|
1391
|
+
|
|
1392
|
+
// const dataProvider = new DataProvider(httpClient, {
|
|
1393
|
+
// persist: "session", // "memory" | "session" | "local"
|
|
1394
|
+
// cacheTime: 5 * 60 * 1000, // 5 minutes
|
|
1395
|
+
// staleTime: 30 * 1000, // 30 seconds
|
|
1396
|
+
// });
|
|
1397
|
+
|
|
1398
|
+
// =================== 3. Auth Provider URLs ===================
|
|
1312
1399
|
|
|
1313
1400
|
// const urls = {
|
|
1314
|
-
//
|
|
1315
|
-
//
|
|
1316
|
-
//
|
|
1317
|
-
//
|
|
1401
|
+
// loginUrl: "/auth/login", // POST
|
|
1402
|
+
// logoutUrl: "/auth/logout", // POST
|
|
1403
|
+
// meUrl: "/auth/me", // GET (by token)
|
|
1404
|
+
// };
|
|
1318
1405
|
|
|
1319
|
-
//
|
|
1406
|
+
// const keysRemoveOnLogout = [TOKEN, "refreshToken", "user"];
|
|
1320
1407
|
|
|
1321
|
-
//
|
|
1322
|
-
|
|
1408
|
+
// =================== 4. Wrap Providers ===================
|
|
1409
|
+
|
|
1410
|
+
// <DataProviderContainer dataProvider={dataProvider}>
|
|
1323
1411
|
// <AuthProvider
|
|
1324
|
-
//
|
|
1325
|
-
//
|
|
1326
|
-
//
|
|
1412
|
+
// urls={urls}
|
|
1413
|
+
// tokenKey={TOKEN}
|
|
1414
|
+
// keysCleanUpOnLogout={keysRemoveOnLogout}
|
|
1327
1415
|
// >
|
|
1328
1416
|
// <App />
|
|
1329
1417
|
// </AuthProvider>
|
|
1330
|
-
// </
|
|
1418
|
+
// </DataProviderContainer>
|
|
1419
|
+
|
|
1420
|
+
// =================== 5. Auth Hooks ===================
|
|
1421
|
+
|
|
1422
|
+
// const { login, logout, getMe, isAuthenticated } = useAuth();
|
|
1423
|
+
|
|
1424
|
+
// =================== 6. useList ===================
|
|
1425
|
+
|
|
1426
|
+
// Auto load on mount
|
|
1427
|
+
// Use cache if exists (F5 will NOT refetch if persist !== memory)
|
|
1428
|
+
|
|
1429
|
+
// const {
|
|
1430
|
+
// data,
|
|
1431
|
+
// total,
|
|
1432
|
+
// loading,
|
|
1433
|
+
// error,
|
|
1434
|
+
// refetch,
|
|
1435
|
+
// } = useList<User>(
|
|
1436
|
+
// "users",
|
|
1437
|
+
// {
|
|
1438
|
+
// meta: {
|
|
1439
|
+
// params: {
|
|
1440
|
+
// role: "admin",
|
|
1441
|
+
// },
|
|
1442
|
+
// },
|
|
1443
|
+
// },
|
|
1444
|
+
// {
|
|
1445
|
+
// cache: true,
|
|
1446
|
+
// },
|
|
1447
|
+
// );
|
|
1448
|
+
|
|
1449
|
+
// Force refetch (bypass cache)
|
|
1450
|
+
// refetch(true);
|
|
1451
|
+
|
|
1452
|
+
// =================== 7. useOne ===================
|
|
1453
|
+
|
|
1454
|
+
// const {
|
|
1455
|
+
// data,
|
|
1456
|
+
// loading,
|
|
1457
|
+
// error,
|
|
1458
|
+
// refetch,
|
|
1459
|
+
// } = useOne<User>(
|
|
1460
|
+
// "users",
|
|
1461
|
+
// userId,
|
|
1462
|
+
// {
|
|
1463
|
+
// enabled: !!userId,
|
|
1464
|
+
// },
|
|
1465
|
+
// );
|
|
1331
1466
|
|
|
1332
|
-
//
|
|
1467
|
+
// Force refetch (ignore cache)
|
|
1468
|
+
// refetch(true);
|
|
1333
1469
|
|
|
1334
|
-
//
|
|
1470
|
+
// =================== 8. useCreate ===================
|
|
1335
1471
|
|
|
1336
|
-
//
|
|
1472
|
+
// const { mutate, loading, error } = useCreate<User, CreateUserPayload>(
|
|
1473
|
+
// "users",
|
|
1474
|
+
// {
|
|
1475
|
+
// onSuccess: (data) => {
|
|
1476
|
+
// console.log("Created:", data);
|
|
1477
|
+
// },
|
|
1478
|
+
// },
|
|
1479
|
+
// );
|
|
1337
1480
|
|
|
1338
|
-
//
|
|
1339
|
-
//
|
|
1481
|
+
// mutate({
|
|
1482
|
+
// name: "John",
|
|
1483
|
+
// email: "john@mail.com",
|
|
1340
1484
|
// });
|
|
1341
1485
|
|
|
1342
|
-
//
|
|
1343
|
-
// url: '/route_name/:id',
|
|
1344
|
-
// id: 1,
|
|
1345
|
-
// });
|
|
1486
|
+
// =================== 9. useUpdate ===================
|
|
1346
1487
|
|
|
1347
|
-
// const {
|
|
1348
|
-
//
|
|
1349
|
-
//
|
|
1350
|
-
//
|
|
1488
|
+
// const { mutate, loading, error } = useUpdate<User, UpdateUserPayload>(
|
|
1489
|
+
// "users",
|
|
1490
|
+
// {
|
|
1491
|
+
// onSuccess: (data) => {
|
|
1492
|
+
// console.log("Updated:", data);
|
|
1493
|
+
// },
|
|
1494
|
+
// },
|
|
1495
|
+
// );
|
|
1351
1496
|
|
|
1352
|
-
//
|
|
1353
|
-
//
|
|
1354
|
-
// id: 1,
|
|
1355
|
-
// payload: {},
|
|
1497
|
+
// mutate(1, {
|
|
1498
|
+
// name: "New name",
|
|
1356
1499
|
// });
|
|
1357
1500
|
|
|
1358
|
-
//
|
|
1359
|
-
// url: '/route_name/:id',
|
|
1360
|
-
// id: 1,
|
|
1361
|
-
// });
|
|
1501
|
+
// =================== 10. useDelete ===================
|
|
1362
1502
|
|
|
1363
|
-
// const {
|
|
1364
|
-
//
|
|
1365
|
-
//
|
|
1366
|
-
//
|
|
1367
|
-
//
|
|
1503
|
+
// const { mutate, loading } = useDelete<User>(
|
|
1504
|
+
// "users",
|
|
1505
|
+
// {
|
|
1506
|
+
// onSuccess: () => {
|
|
1507
|
+
// console.log("Deleted");
|
|
1508
|
+
// },
|
|
1509
|
+
// },
|
|
1510
|
+
// );
|
|
1511
|
+
|
|
1512
|
+
// mutate(1);
|
|
1513
|
+
|
|
1514
|
+
// =================== 11. useCustom ===================
|
|
1515
|
+
|
|
1516
|
+
// const { mutate, data, loading, error } = useCustom<CustomResponse>(
|
|
1517
|
+
// "/reports/export",
|
|
1518
|
+
// {
|
|
1519
|
+
// method: "post",
|
|
1520
|
+
// payload: {
|
|
1521
|
+
// from: "2024-01-01",
|
|
1522
|
+
// to: "2024-12-31",
|
|
1523
|
+
// },
|
|
1524
|
+
// },
|
|
1525
|
+
// );
|
|
1526
|
+
|
|
1527
|
+
// mutate();
|